Improve chip rendering and debug overlays

This commit is contained in:
ajp_anton
2026-06-04 11:20:21 +00:00
parent 9279492f2a
commit ec6cb91b14
3 changed files with 133 additions and 83 deletions
@@ -140,6 +140,8 @@ final class StockLayoutCanvasController {
Collections.newSetFromMap(new WeakHashMap<>());
private final Set<View> dirtyUnlockedLayoutRoots =
Collections.newSetFromMap(new WeakHashMap<>());
private final Set<View> pendingDrawerStatusChipRefreshRoots =
Collections.newSetFromMap(new WeakHashMap<>());
private final Set<ClassLoader> hookedPluginClassLoaders =
Collections.newSetFromMap(new IdentityHashMap<>());
private final Set<ClassLoader> pendingPluginClassLoaders =
@@ -288,6 +290,7 @@ final class StockLayoutCanvasController {
}
if (expansion <= 0.01f) {
notificationDrawerWasOpen = false;
flushDeferredDrawerStatusChipRefreshes();
return;
}
if (expansion <= 0.05f) {
@@ -337,6 +340,7 @@ final class StockLayoutCanvasController {
} else if (previousState == STATUS_BAR_STATE_SHADE_LOCKED
&& currentState != STATUS_BAR_STATE_SHADE_LOCKED) {
notificationDrawerWasOpen = false;
flushDeferredDrawerStatusChipRefreshes();
}
}
@@ -1153,9 +1157,6 @@ final class StockLayoutCanvasController {
if (activeScene == null) {
return;
}
if (!runtimeContext.getModeStateRepository().isSystemUiShadeLocked() && !activeScene.isAod()) {
notificationDrawerWasOpen = false;
}
if (isNotificationShadeWindowRoot(root)) {
if (shouldPrepareCarrierlessAodNotificationTransition(settings, activeScene)) {
prepareCarrierlessAodNotificationTransition(root);
@@ -1331,8 +1332,8 @@ final class StockLayoutCanvasController {
if (activeScene != null && activeScene.isUnlocked()) {
if (isKnownRootGeometryChanged(root)) {
ownedIconHostManager.setUnlockedHostsVisible(root, false);
removeDebugOverlay(root);
}
removeDebugOverlay(root);
}
}
@@ -1385,13 +1386,19 @@ final class StockLayoutCanvasController {
private void markStatusChipLifecycleDirtyIfNeeded(View view, View fallbackRoot) {
if (view == null || !isStatusChipCandidate(view)) {
View root = view != null ? unlockedStatusBarRootFor(view) : fallbackRoot;
if (deferDrawerStatusChipRefresh(root)) {
return;
}
requestUnlockedLayoutRefresh(root);
return;
}
View root = unlockedStatusBarRootFor(view);
if (deferDrawerStatusChipRefresh(root != null ? root : fallbackRoot)) {
return;
}
int signature = statusChipLayoutSignature(view);
Integer previous = statusChipLifecycleLayoutSignatureByView.put(view, signature);
if (previous == null || previous != signature) {
View root = unlockedStatusBarRootFor(view);
requestUnlockedLayoutRefresh(root != null ? root : fallbackRoot);
}
}
@@ -1408,6 +1415,9 @@ final class StockLayoutCanvasController {
if (root == null || !root.isAttachedToWindow()) {
return;
}
if (deferDrawerStatusChipRefresh(root)) {
return;
}
int signature = statusChipContentSignature(chipSource);
Integer previous = statusChipContentSignatureByRoot.put(root, signature);
if (previous != null && previous == signature) {
@@ -1434,10 +1444,39 @@ final class StockLayoutCanvasController {
requestUnlockedLayoutRefresh(root);
}
private boolean deferDrawerStatusChipRefresh(View root) {
if (!notificationDrawerWasOpen || root == null || !root.isAttachedToWindow()) {
return false;
}
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
if (activeScene == null || !activeScene.isUnlocked()) {
return false;
}
pendingDrawerStatusChipRefreshRoots.add(root);
return true;
}
private void flushDeferredDrawerStatusChipRefreshes() {
if (pendingDrawerStatusChipRefreshRoots.isEmpty()) {
return;
}
ArrayDeque<View> roots = new ArrayDeque<>(pendingDrawerStatusChipRefreshRoots);
pendingDrawerStatusChipRefreshRoots.clear();
while (!roots.isEmpty()) {
View root = roots.removeFirst();
if (root != null && root.isAttachedToWindow()) {
requestStatusChipRefresh(root);
}
}
}
private void requestStatusChipRefresh(View root) {
if (root == null || !root.isAttachedToWindow()) {
return;
}
if (deferDrawerStatusChipRefresh(root)) {
return;
}
unlockedIconRenderController.forceChipRefresh(root);
requestUnlockedLayoutRefresh(root);
}
@@ -1517,7 +1556,8 @@ final class StockLayoutCanvasController {
return 0;
}
int result = 17;
result = 31 * result + System.identityHashCode(view);
result = 31 * result + view.getClass().getName().hashCode();
result = 31 * result + view.getId();
result = 31 * result + view.getVisibility();
result = 31 * result + view.getWidth();
result = 31 * result + view.getHeight();
@@ -1528,8 +1568,10 @@ final class StockLayoutCanvasController {
}
if (view instanceof ImageView imageView) {
Drawable drawable = imageView.getDrawable();
result = 31 * result + System.identityHashCode(drawable);
if (drawable != null) {
result = 31 * result + drawable.getClass().getName().hashCode();
result = 31 * result + drawable.getIntrinsicWidth();
result = 31 * result + drawable.getIntrinsicHeight();
result = 31 * result + drawable.getBounds().hashCode();
}
}
@@ -1559,8 +1601,10 @@ final class StockLayoutCanvasController {
}
if (view instanceof ImageView imageView) {
Drawable drawable = imageView.getDrawable();
result = 31 * result + System.identityHashCode(drawable);
if (drawable != null) {
result = 31 * result + drawable.getClass().getName().hashCode();
result = 31 * result + drawable.getIntrinsicWidth();
result = 31 * result + drawable.getIntrinsicHeight();
result = 31 * result + drawable.getAlpha();
result = 31 * result + drawable.getLevel();
result = 31 * result + drawable.getBounds().hashCode();
@@ -1936,13 +1980,13 @@ final class StockLayoutCanvasController {
if (!aodScene && renderResult.shouldOwnLockscreen()) {
hideOrDiscoverLockedStatusBarIconSurfaces(root);
}
if (aodScene) {
if (aodScene || renderResult.shouldOwnLockscreen()) {
applyDebugOverlayIfNeeded(
root,
true,
settings,
renderResult.layoutChanged(),
true,
aodScene,
activeScene);
} else {
removeDebugOverlay(root);
@@ -2069,6 +2113,7 @@ final class StockLayoutCanvasController {
) {
int result = 17;
result = 31 * result + (layoutEnabled ? 1 : 0);
result = 31 * result + (scene != null ? scene.hashCode() : 0);
result = 31 * result + (root != null ? root.getWidth() : 0);
result = 31 * result + (root != null ? root.getHeight() : 0);
if (settings != null) {
@@ -3874,6 +3919,7 @@ final class StockLayoutCanvasController {
statusChipContentSignatureByRoot.clear();
statusChipLayoutSignatureByRoot.clear();
statusChipLifecycleLayoutSignatureByView.clear();
pendingDrawerStatusChipRefreshRoots.clear();
}
private static boolean containsAny(String value, Set<String> tokens) {
@@ -107,9 +107,8 @@ public final class DebugBoundsOverlayController {
removeOverlays(parent);
return;
}
ViewGroup overlayParent = overlayParent(root, parent, layoutEnabled, aodStockVisualizersEnabled, settings);
removeOverlayFromOtherParent(parent, overlayParent);
OverlayView host = ensureOverlay(overlayParent);
removeOverlaysFromOwnedHosts(parent);
OverlayView host = ensureOverlay(parent);
if (host == null) {
return;
}
@@ -122,7 +121,7 @@ public final class DebugBoundsOverlayController {
host.layout(0, 0, width, height);
}
host.setSpecs(specs);
if (overlayParent.getChildAt(overlayParent.getChildCount() - 1) != host) {
if (parent.getChildAt(parent.getChildCount() - 1) != host) {
host.bringToFront();
}
}
@@ -134,18 +133,22 @@ public final class DebugBoundsOverlayController {
boolean aodStockVisualizersEnabled,
SceneKey scene
) {
if (!layoutEnabled || root == null || settings == null
|| !aodStockVisualizersEnabled
|| !settings.debugVisualizeAodStockContainers) {
if (!layoutEnabled || root == null || settings == null) {
return 0;
}
int result = 17;
ViewGroup notificationContainer = aodNotificationContainer(root, scene);
result = 31 * result + signature(
aodNotificationBounds(root, scene));
result = 31 * result + signature(liveAodStatusIconsBounds(root));
result = 31 * result + signature(
derivedAodStatusbarBounds(root, notificationContainer, settings, scene));
if (aodStockVisualizersEnabled && settings.debugVisualizeAodStockContainers) {
ViewGroup notificationContainer = aodNotificationContainer(root, scene);
result = 31 * result + signature(
aodNotificationBounds(root, scene));
result = 31 * result + signature(liveAodStatusIconsBounds(root));
result = 31 * result + signature(
derivedAodStatusbarBounds(root, notificationContainer, settings, scene));
}
result = 31 * result + hostSignature(root, OwnedIconHostManager.TAG_UNLOCKED_NOTIFICATION_HOST);
result = 31 * result + hostSignature(root, OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST);
result = 31 * result + hostSignature(root, OwnedIconHostManager.TAG_UNLOCKED_CLOCK_HOST);
result = 31 * result + hostSignature(root, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST);
return result;
}
@@ -255,6 +258,12 @@ public final class DebugBoundsOverlayController {
}
int right = bounds.left + bounds.width;
int bottom = bounds.top + bounds.height;
if (bounds.left == 0
&& bounds.top == 0
&& bounds.width >= root.getWidth()
&& bounds.height >= root.getHeight()) {
return false;
}
return bounds.left >= 0
&& bounds.top >= 0
&& right <= root.getWidth()
@@ -410,49 +419,15 @@ public final class DebugBoundsOverlayController {
return host;
}
private ViewGroup overlayParent(
View root,
ViewGroup rootParent,
boolean layoutEnabled,
boolean aodStockVisualizersEnabled,
SbtSettings settings
) {
if (layoutEnabled
&& aodStockVisualizersEnabled
&& settings != null
&& settings.debugVisualizeAodStockContainers) {
View chipHost = findDirectTaggedChild(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST);
if (chipHost instanceof ViewGroup chipHostGroup) {
return chipHostGroup;
}
View statusHost = findDirectTaggedChild(rootParent, OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST);
if (statusHost instanceof ViewGroup statusHostGroup) {
return statusHostGroup;
}
}
return rootParent;
}
private void removeOverlayFromOtherParent(ViewGroup rootParent, ViewGroup overlayParent) {
private void removeOverlaysFromOwnedHosts(ViewGroup rootParent) {
if (rootParent == null) {
return;
}
removeOverlayIfParentDiffers(rootParent, overlayParent);
removeOverlayIfParentDiffers(
directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST),
overlayParent);
removeOverlayIfParentDiffers(
directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_NOTIFICATION_HOST),
overlayParent);
removeOverlayIfParentDiffers(
directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CLOCK_HOST),
overlayParent);
removeOverlayIfParentDiffers(
directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CARRIER_HOST),
overlayParent);
removeOverlayIfParentDiffers(
directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST),
overlayParent);
removeOverlay(directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST));
removeOverlay(directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_NOTIFICATION_HOST));
removeOverlay(directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CLOCK_HOST));
removeOverlay(directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CARRIER_HOST));
removeOverlay(directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST));
}
private void removeOverlays(ViewGroup rootParent) {
@@ -467,12 +442,6 @@ public final class DebugBoundsOverlayController {
removeOverlay(directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST));
}
private void removeOverlayIfParentDiffers(ViewGroup parent, ViewGroup expectedParent) {
if (parent != null && parent != expectedParent) {
removeOverlay(parent);
}
}
private void removeOverlay(ViewGroup parent) {
if (parent == null) {
return;
@@ -511,6 +480,19 @@ public final class DebugBoundsOverlayController {
return null;
}
private int hostSignature(View root, String tag) {
if (!(root instanceof ViewGroup parent)) {
return 0;
}
View host = findDirectTaggedChild(parent, tag);
Bounds bounds = boundsRelativeTo(root, host);
int result = 17;
result = 31 * result + signature(bounds);
result = 31 * result + (host instanceof ViewGroup group ? group.getChildCount() : 0);
result = 31 * result + taggedContainerBounds(host).hashCode();
return result;
}
private Bounds boundsRelativeTo(View root, View view) {
if (root == null || view == null) {
return null;
@@ -10,6 +10,7 @@ import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.os.SystemClock;
import android.view.View;
import android.view.ViewGroup;
@@ -946,7 +947,7 @@ final class SnapshotRenderer {
int oldMeasuredWidth = view.getMeasuredWidth();
int oldMeasuredHeight = view.getMeasuredHeight();
ViewFrameState frameState = captureFrameState(view);
IdentityHashMap<TextView, Integer> oldMaxWidths = clearTextMaxWidths(view);
IdentityHashMap<TextView, TextSnapshotState> textSnapshotStates = prepareTextSnapshotState(view);
try {
int widthSpec = View.MeasureSpec.makeMeasureSpec(Math.max(1, width), View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(Math.max(1, height), View.MeasureSpec.EXACTLY);
@@ -955,7 +956,7 @@ final class SnapshotRenderer {
clampDescendantsToParent(view);
view.draw(canvas);
} finally {
restoreTextMaxWidths(oldMaxWidths);
restoreTextSnapshotState(textSnapshotStates);
restoreFrameState(frameState);
if (oldMeasuredWidth > 0 && oldMeasuredHeight > 0) {
view.measure(
@@ -1052,32 +1053,53 @@ final class SnapshotRenderer {
) {
}
private IdentityHashMap<TextView, Integer> clearTextMaxWidths(View view) {
IdentityHashMap<TextView, Integer> oldMaxWidths = new IdentityHashMap<>();
collectTextMaxWidths(view, oldMaxWidths);
for (Map.Entry<TextView, Integer> entry : oldMaxWidths.entrySet()) {
entry.getKey().setMaxWidth(Integer.MAX_VALUE);
private IdentityHashMap<TextView, TextSnapshotState> prepareTextSnapshotState(View view) {
IdentityHashMap<TextView, TextSnapshotState> states = new IdentityHashMap<>();
collectTextSnapshotState(view, states);
for (Map.Entry<TextView, TextSnapshotState> entry : states.entrySet()) {
TextView textView = entry.getKey();
textView.setMaxWidth(Integer.MAX_VALUE);
textView.setHorizontalFadingEdgeEnabled(false);
textView.setSelected(false);
textView.setEllipsize(null);
}
return oldMaxWidths;
return states;
}
private void collectTextMaxWidths(View view, IdentityHashMap<TextView, Integer> out) {
private void collectTextSnapshotState(View view, IdentityHashMap<TextView, TextSnapshotState> out) {
if (view instanceof TextView textView) {
out.put(textView, textView.getMaxWidth());
out.put(textView, new TextSnapshotState(
textView.getMaxWidth(),
textView.isHorizontalFadingEdgeEnabled(),
textView.isSelected(),
textView.getEllipsize()));
}
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
collectTextMaxWidths(group.getChildAt(i), out);
collectTextSnapshotState(group.getChildAt(i), out);
}
}
}
private void restoreTextMaxWidths(IdentityHashMap<TextView, Integer> oldMaxWidths) {
for (Map.Entry<TextView, Integer> entry : oldMaxWidths.entrySet()) {
entry.getKey().setMaxWidth(entry.getValue());
private void restoreTextSnapshotState(IdentityHashMap<TextView, TextSnapshotState> states) {
for (Map.Entry<TextView, TextSnapshotState> entry : states.entrySet()) {
TextView textView = entry.getKey();
TextSnapshotState state = entry.getValue();
textView.setMaxWidth(state.maxWidth);
textView.setHorizontalFadingEdgeEnabled(state.horizontalFadingEdgeEnabled);
textView.setSelected(state.selected);
textView.setEllipsize(state.ellipsize);
}
}
private record TextSnapshotState(
int maxWidth,
boolean horizontalFadingEdgeEnabled,
boolean selected,
TextUtils.TruncateAt ellipsize
) {
}
private void drawAppIcon(Context context, SnapshotSource source, Bitmap bitmap) {
bitmap.eraseColor(Color.TRANSPARENT);
if (context == null || source == null || source.keyHint() == null) {