From 8e7e8255163d91ade65829c4bc806ed3587ed0d3 Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Mon, 22 Jun 2026 11:29:09 +0000 Subject: [PATCH] Fix call chip rendering and placement stability --- .../runtime/RuntimeMutationGuard.java | 27 ++++++++++++ .../layout/StockLayoutCanvasController.java | 6 +++ .../runtime/render/SnapshotRenderer.java | 41 ++++++++++++++----- .../UnlockedChipPlacementController.java | 9 +++- .../runtime/render/UnlockedLayoutPlanner.java | 27 +++++++++++- 5 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/se/ajpanton/statusbartweak/runtime/RuntimeMutationGuard.java diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/RuntimeMutationGuard.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/RuntimeMutationGuard.java new file mode 100644 index 0000000..b42d7c8 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/RuntimeMutationGuard.java @@ -0,0 +1,27 @@ +package se.ajpanton.statusbartweak.runtime; + +public final class RuntimeMutationGuard { + private static final ThreadLocal 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; + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java index ac6f7d9..c5b1a04 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java @@ -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)) { diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java index 1cfc5b8..6faeaae 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java @@ -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 ) { } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedChipPlacementController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedChipPlacementController.java index edde4ad..0f91e27 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedChipPlacementController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedChipPlacementController.java @@ -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 placements = isAnyChipHidingEnabled(settings) + boolean hasMetricSources = metricSources != null && !metricSources.isEmpty(); + ArrayList placements = isAnyChipHidingEnabled(settings) || !hasMetricSources ? new ArrayList<>() : heldPlacements(root); - if (placements.isEmpty()) { + if (placements.isEmpty() && hasMetricSources) { placements = emptyPlacement(root, metricSources); } return placements; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java index 24bb0ab..28e059d 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java @@ -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;