From 9dfca417bdb4f3c54b5aa55d5e09e3041a44a003 Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Tue, 30 Jun 2026 18:16:51 +0000 Subject: [PATCH] Mirror stock statusbar transitions --- .../layout/StockLayoutCanvasController.java | 169 +++++++++++++++++- .../runtime/render/OwnedIconHostManager.java | 36 ++++ 2 files changed, 199 insertions(+), 6 deletions(-) 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 42d39eb..6c87c65 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 @@ -13,6 +13,7 @@ import android.service.notification.StatusBarNotification; import android.telecom.TelecomManager; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.widget.ImageView; import android.widget.TextView; @@ -189,6 +190,7 @@ final class StockLayoutCanvasController { private int notificationDrawerCloseGeneration; private boolean notificationDrawerClosePending; private int aodVisualAlphaGeneration; + private int systemBarRequestedVisibleTypes = WindowInsets.Type.statusBars(); StockLayoutCanvasController(RuntimeContext runtimeContext) { this.runtimeContext = runtimeContext; @@ -217,10 +219,44 @@ final class StockLayoutCanvasController { statusIconModelTracker.installHooks(classLoader); installShadeExpansionHooks(classLoader); installShadeHeaderAnimationGuardHook(classLoader); + installSystemBarAttributeListener(classLoader); installViewRootHook(classLoader); hooksInstalled = true; } + private void installSystemBarAttributeListener(ClassLoader classLoader) { + Class commandQueueClass = ReflectionSupport.findClassIfExists( + "com.android.systemui.statusbar.CommandQueue", + classLoader); + if (commandQueueClass == null) { + return; + } + XposedHookSupport.hookAllMethodsIfExists( + runtimeContext.getFramework(), + commandQueueClass, + "onSystemBarAttributesChanged", + chain -> { + Object result = chain.proceed(); + updateSystemBarRequestedVisibleTypes(chain.getArgs()); + return result; + }); + } + + private void updateSystemBarRequestedVisibleTypes(List args) { + if (args == null || args.size() <= 5) { + return; + } + Integer displayId = asInteger(args.get(0)); + Integer requestedVisibleTypes = asInteger(args.get(5)); + if (displayId == null || displayId != 0 || requestedVisibleTypes == null) { + return; + } + if (systemBarRequestedVisibleTypes == requestedVisibleTypes) { + return; + } + systemBarRequestedVisibleTypes = requestedVisibleTypes; + } + private void installNotificationCollectionHooks(ClassLoader classLoader) { for (String className : List.of( "com.android.systemui.statusbar.notification.collection.NotifCollection", @@ -428,7 +464,11 @@ final class StockLayoutCanvasController { rememberAodPluginVisualAlpha(view, alpha); } } - return chain.proceed(); + Object result = chain.proceed(); + if (target instanceof View view) { + syncUnlockedHostsFromStockStatusBar(view); + } + return result; }); XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), viewClass, "setVisibility", chain -> { Object target = chain.getThisObject(); @@ -444,6 +484,7 @@ final class StockLayoutCanvasController { Object result = chain.proceed(); if (target instanceof View view && visibilityArg instanceof Integer visibility) { scheduleStatusChipRefreshIfContentChanged(view); + syncUnlockedHostsFromStockStatusBar(view); } return result; }); @@ -455,7 +496,11 @@ final class StockLayoutCanvasController { return null; } } - return chain.proceed(); + Object result = chain.proceed(); + if (target instanceof View view) { + syncUnlockedHostsFromStockStatusBar(view); + } + return result; }); XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), viewClass, "setTranslationX", chain -> { Object target = chain.getThisObject(); @@ -465,7 +510,11 @@ final class StockLayoutCanvasController { return null; } } - return chain.proceed(); + Object result = chain.proceed(); + if (target instanceof View view) { + syncUnlockedHostsFromStockStatusBar(view); + } + return result; }); XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), viewClass, "layout", chain -> { Object target = chain.getThisObject(); @@ -482,15 +531,21 @@ final class StockLayoutCanvasController { right, bottom); if (override != null) { - return chain.proceed(new Object[]{ + Object result = chain.proceed(new Object[]{ override.left, override.top, override.right, override.bottom }); + syncUnlockedHostsFromStockStatusBar(view); + return result; } } - return chain.proceed(); + Object result = chain.proceed(); + if (target instanceof View view) { + syncUnlockedHostsFromStockStatusBar(view); + } + return result; }); } @@ -2137,6 +2192,83 @@ final class StockLayoutCanvasController { debugBoundsOverlayController.bringToFront(root); } + private void syncUnlockedHostsFromStockStatusBar(View view) { + if (!isPhoneStatusBarRoot(view)) { + return; + } + View root = unlockedStatusBarRootWithUnlockedHostsFor(view); + if (root == null) { + return; + } + syncUnlockedHostsToStockStatusBar( + root, + runtimeContext.getModeStateRepository().getActiveScene()); + } + + private void syncUnlockedHostsToStockStatusBar(View root, SceneKey activeScene) { + if (root == null || !hasDirectUnlockedLayoutHost(root)) { + return; + } + if (activeScene == null || !activeScene.isUnlocked()) { + ownedIconHostManager.setUnlockedHostsTransform(root, 1f, 0f, 0f); + return; + } + View stockStatusBar = phoneStatusBarRootIn(root); + if (stockStatusBar == null || stockStatusBar == root) { + ownedIconHostManager.setUnlockedHostsTransform(root, 1f, 0f, 0f); + return; + } + float alpha = stockStatusBar.getVisibility() == View.VISIBLE + ? clampAlpha(stockStatusBar.getAlpha()) + : 0f; + float translationX = stockStatusBar.getLeft() + stockStatusBar.getTranslationX(); + float translationY = stockStatusBar.getTop() + stockStatusBar.getTranslationY(); + ownedIconHostManager.setUnlockedHostsTransform( + root, + alpha, + translationX, + translationY); + } + + private View unlockedStatusBarRootWithUnlockedHostsFor(View view) { + View current = view; + while (current != null) { + if (isUnlockedStatusBarRoot(current) && hasDirectUnlockedLayoutHost(current)) { + return current; + } + Object parent = current.getParent(); + current = parent instanceof View parentView ? parentView : null; + } + View root = view.getRootView(); + return isUnlockedStatusBarRoot(root) && hasDirectUnlockedLayoutHost(root) ? root : null; + } + + private View phoneStatusBarRootIn(View root) { + if (root == null) { + return null; + } + if (isPhoneStatusBarRoot(root)) { + return root; + } + if (!(root instanceof ViewGroup group)) { + return null; + } + ArrayDeque queue = new ArrayDeque<>(); + queue.add(group); + while (!queue.isEmpty()) { + View current = queue.removeFirst(); + if (current != root && isPhoneStatusBarRoot(current)) { + return current; + } + if (current instanceof ViewGroup childGroup) { + for (int i = 0; i < childGroup.getChildCount(); i++) { + queue.addLast(childGroup.getChildAt(i)); + } + } + } + return null; + } + private void applyToRootInternal(View root) { if (root == null) { return; @@ -2249,11 +2381,14 @@ final class StockLayoutCanvasController { activeScene, rootDirty, lockedCarrierTextSource()); - boolean shouldShowUnlockedHosts = unlockedScene || aodScene || renderResult.shouldOwnLockscreen(); + boolean shouldShowUnlockedHosts = unlockedScene + || aodScene + || (!unlockedScene && renderResult.shouldOwnLockscreen()); ownedIconHostManager.setUnlockedHostsVisible(root, shouldShowUnlockedHosts); if (shouldShowUnlockedHosts) { bringUnlockedHostsAndDebugOverlayToFront(root); } + syncUnlockedHostsToStockStatusBar(root, activeScene); syncAodOwnedHostVisualState(root, unlockedHosts); } } @@ -2353,6 +2488,17 @@ final class StockLayoutCanvasController { || findDirectOwnedHost(group, OwnedIconHostManager.TAG_AOD_CARDS_NOTIFICATION_HOST) != null; } + private boolean hasDirectUnlockedLayoutHost(View root) { + if (!(root instanceof ViewGroup group)) { + return false; + } + return findDirectOwnedHost(group, OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST) != null + || findDirectOwnedHost(group, OwnedIconHostManager.TAG_UNLOCKED_NOTIFICATION_HOST) != null + || findDirectOwnedHost(group, OwnedIconHostManager.TAG_UNLOCKED_CLOCK_HOST) != null + || findDirectOwnedHost(group, OwnedIconHostManager.TAG_UNLOCKED_CARRIER_HOST) != null + || findDirectOwnedHost(group, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST) != null; + } + private boolean hasLockscreenCardsNotificationHost(View root) { return root instanceof ViewGroup group && findDirectOwnedHost( @@ -3388,6 +3534,9 @@ final class StockLayoutCanvasController { || ongoingPrivacyChipInRoot(view) == null) { return false; } + if (alpha <= 0f && !statusBarsRequestedVisible()) { + return false; + } return true; } @@ -3402,6 +3551,14 @@ final class StockLayoutCanvasController { return true; } + private boolean statusBarsRequestedVisible() { + return (systemBarRequestedVisibleTypes & WindowInsets.Type.statusBars()) != 0; + } + + private static Integer asInteger(Object value) { + return value instanceof Integer integer ? integer : null; + } + private boolean isPhoneStatusBarRoot(View view) { return view != null && view.getClass().getName().contains("PhoneStatusBarView"); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/OwnedIconHostManager.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/OwnedIconHostManager.java index b218eb5..1685962 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/OwnedIconHostManager.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/OwnedIconHostManager.java @@ -119,6 +119,17 @@ public final class OwnedIconHostManager { } } + public void setUnlockedHostsTransform( + View root, + float alpha, + float translationX, + float translationY + ) { + for (String tag : UNLOCKED_HOST_ORDER) { + setHostTransform(root, tag, alpha, translationX, translationY); + } + } + public void bringUnlockedHostsToFront(View root) { if (!(root instanceof ViewGroup parent)) { return; @@ -169,6 +180,31 @@ public final class OwnedIconHostManager { } } + private void setHostTransform( + View root, + String tag, + float alpha, + float translationX, + float translationY + ) { + if (!(root instanceof ViewGroup parent) || tag == null) { + return; + } + FrameLayout host = findDirectHost(parent, tag); + if (host == null) { + return; + } + if (host.getAlpha() != alpha) { + host.setAlpha(alpha); + } + if (host.getTranslationX() != translationX) { + host.setTranslationX(translationX); + } + if (host.getTranslationY() != translationY) { + host.setTranslationY(translationY); + } + } + private ArrayList unlockedHosts(ViewGroup parent) { ArrayList hosts = new ArrayList<>(); for (String tag : UNLOCKED_HOST_ORDER) {