Fix call chip rendering and placement stability

This commit is contained in:
ajp_anton
2026-06-22 11:29:09 +00:00
parent de1c03b20f
commit 8e7e825516
5 changed files with 97 additions and 13 deletions
@@ -0,0 +1,27 @@
package se.ajpanton.statusbartweak.runtime;
public final class RuntimeMutationGuard {
private static final ThreadLocal<Integer> SNAPSHOT_RENDER_DEPTH =
ThreadLocal.withInitial(() -> 0);
private RuntimeMutationGuard() {
}
public static void enterSnapshotRenderMutation() {
SNAPSHOT_RENDER_DEPTH.set(SNAPSHOT_RENDER_DEPTH.get() + 1);
}
public static void exitSnapshotRenderMutation() {
int depth = SNAPSHOT_RENDER_DEPTH.get() - 1;
if (depth <= 0) {
SNAPSHOT_RENDER_DEPTH.remove();
} else {
SNAPSHOT_RENDER_DEPTH.set(depth);
}
}
public static boolean isSnapshotRenderMutation() {
Integer depth = SNAPSHOT_RENDER_DEPTH.get();
return depth != null && depth > 0;
}
}
@@ -34,6 +34,7 @@ import java.util.WeakHashMap;
import java.util.function.Function;
import se.ajpanton.statusbartweak.runtime.NotificationKeyReflection;
import se.ajpanton.statusbartweak.runtime.RuntimeMutationGuard;
import se.ajpanton.statusbartweak.runtime.ViewIdNames;
import se.ajpanton.statusbartweak.runtime.features.RuntimeContext;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
@@ -1491,6 +1492,9 @@ final class StockLayoutCanvasController {
}
private void markStatusChipLifecycleDirtyIfNeeded(View view, View fallbackRoot) {
if (RuntimeMutationGuard.isSnapshotRenderMutation()) {
return;
}
if (view == null || !isStatusChipCandidate(view)) {
View root = view != null ? unlockedStatusBarRootFor(view) : fallbackRoot;
if (deferDrawerStatusChipRefresh(root)) {
@@ -2989,6 +2993,7 @@ final class StockLayoutCanvasController {
private boolean isTrackedStatusChipWriteTarget(View view) {
if (view == null
|| ownHiddenViewAlphaWriteDepth > 0
|| RuntimeMutationGuard.isSnapshotRenderMutation()
|| !trackedStatusChipSources.contains(view)
|| !hiddenViewStates.containsKey(view)
|| !view.isAttachedToWindow()
@@ -3008,6 +3013,7 @@ final class StockLayoutCanvasController {
private boolean isStatusChipShellWriteTarget(View view) {
if (view == null
|| ownHiddenViewAlphaWriteDepth > 0
|| RuntimeMutationGuard.isSnapshotRenderMutation()
|| !view.isAttachedToWindow()
|| !isLayoutEnabled(view.getContext())
|| !isStatusChipShell(view)) {
@@ -35,6 +35,7 @@ import java.util.Objects;
import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.NotificationKeyReflection;
import se.ajpanton.statusbartweak.runtime.RuntimeMutationGuard;
import se.ajpanton.statusbartweak.runtime.ViewIdNames;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
@@ -748,16 +749,26 @@ final class SnapshotRenderer {
bitmap.getHeight())) {
return;
}
sourceView.setVisibility(View.VISIBLE);
sourceView.setAlpha(1f);
if (source.mode() == SnapshotMode.STATUS_CHIP) {
drawTemporarilySizedView(sourceView, canvas, bitmap.getWidth(), bitmap.getHeight());
} else {
sourceView.draw(canvas);
RuntimeMutationGuard.enterSnapshotRenderMutation();
try {
sourceView.setVisibility(View.VISIBLE);
sourceView.setAlpha(1f);
if (source.mode() == SnapshotMode.STATUS_CHIP) {
drawTemporarilySizedView(sourceView, canvas, bitmap.getWidth(), bitmap.getHeight());
} else {
sourceView.draw(canvas);
}
} finally {
RuntimeMutationGuard.exitSnapshotRenderMutation();
}
} finally {
sourceView.setAlpha(oldAlpha);
sourceView.setVisibility(oldVisibility);
RuntimeMutationGuard.enterSnapshotRenderMutation();
try {
sourceView.setAlpha(oldAlpha);
sourceView.setVisibility(oldVisibility);
} finally {
RuntimeMutationGuard.exitSnapshotRenderMutation();
}
}
}
@@ -1183,7 +1194,9 @@ final class SnapshotRenderer {
view.getRight(),
view.getBottom(),
view.getMeasuredWidth(),
view.getMeasuredHeight()));
view.getMeasuredHeight(),
view.getVisibility(),
view.getAlpha()));
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
captureFrameState(group.getChildAt(i), state);
@@ -1207,6 +1220,12 @@ final class SnapshotRenderer {
View.MeasureSpec.makeMeasureSpec(frame.measuredHeight, View.MeasureSpec.EXACTLY));
}
view.layout(frame.left, frame.top, frame.right, frame.bottom);
if (view.getAlpha() != frame.alpha) {
view.setAlpha(frame.alpha);
}
if (view.getVisibility() != frame.visibility) {
view.setVisibility(frame.visibility);
}
}
}
@@ -1221,7 +1240,9 @@ final class SnapshotRenderer {
int right,
int bottom,
int measuredWidth,
int measuredHeight
int measuredHeight,
int visibility,
float alpha
) {
}
@@ -21,6 +21,10 @@ final class UnlockedChipPlacementController {
}
void clear() {
resetMissingChipFallback();
}
private void resetMissingChipFallback() {
heldPlacements = null;
lastCollisionBounds = null;
lastRootWidth = 0;
@@ -47,10 +51,11 @@ final class UnlockedChipPlacementController {
return rawPlacements;
}
ArrayList<SnapshotPlacement> placements = isAnyChipHidingEnabled(settings)
boolean hasMetricSources = metricSources != null && !metricSources.isEmpty();
ArrayList<SnapshotPlacement> placements = isAnyChipHidingEnabled(settings) || !hasMetricSources
? new ArrayList<>()
: heldPlacements(root);
if (placements.isEmpty()) {
if (placements.isEmpty() && hasMetricSources) {
placements = emptyPlacement(root, metricSources);
}
return placements;
@@ -88,7 +88,13 @@ final class UnlockedLayoutPlanner {
Bounds aodStatusbarBounds = scene != null && scene.isAod()
? aodStatusbarBounds(root, settings, aodNotificationBounds, aodStatusAnchorBounds)
: null;
LayoutEdges edges = layoutEdges(root, settings, anchors, scene, aodStatusAnchorBounds);
LayoutEdges edges = layoutEdges(
root,
settings,
anchors,
collectedIcons,
scene,
aodStatusAnchorBounds);
int statusbarHeight = statusbarHeight(root, anchors, scene, aodStatusContentBounds);
if (clockEnabledForScene(settings, scene) && clockLayout != null && clockLayout.width() > 0) {
bands.add(UnlockedLayoutBand.clock(
@@ -1393,6 +1399,7 @@ final class UnlockedLayoutPlanner {
View root,
SbtSettings settings,
RootAnchors anchors,
CollectedIcons collectedIcons,
SceneKey scene,
Bounds aodStatusAnchorBounds
) {
@@ -1419,11 +1426,29 @@ final class UnlockedLayoutPlanner {
if (settings.layoutPaddingRightAddStock) {
right -= stockInsets.right;
}
if (scene != null && scene.isUnlocked() && hasStatusChipSources(collectedIcons)) {
LayoutEdges recentEdges = recentNonAodEdgesByRootWidth.get(rootWidth);
if (isReusableUnlockedEdge(left, recentEdges)) {
left = recentEdges.left();
}
}
LayoutEdges edges = new LayoutEdges(left, right);
rememberNonAodEdges(rootWidth, scene, edges);
return edges;
}
private boolean hasStatusChipSources(CollectedIcons icons) {
return icons != null
&& ((icons.statusChips != null && !icons.statusChips.isEmpty())
|| (icons.statusChipMetrics != null && !icons.statusChipMetrics.isEmpty()));
}
private boolean isReusableUnlockedEdge(int currentLeft, LayoutEdges recentEdges) {
return recentEdges != null
&& recentEdges.right() > recentEdges.left()
&& Math.abs(currentLeft - recentEdges.left()) <= 8;
}
private LayoutEdges stableAodEdges(int rootWidth, LayoutEdges aodEdges) {
if (rootWidth <= 0) {
return aodEdges;