From 42449f8c6a392a473c44644e011a918f26795b27 Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Wed, 1 Jul 2026 12:52:44 +0000 Subject: [PATCH] Add initial AOSP statusbar compatibility --- README.md | 36 ++++++------ .../features/clock/StockClockController.java | 55 +++++++++++++++++-- ...ckUnlockedNotificationIconsController.java | 13 +++-- .../layout/StockLayoutCanvasController.java | 13 ++++- .../render/UnlockedStockSourceCollector.java | 8 ++- 5 files changed, 93 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 61de508..7a5c8cc 100644 --- a/README.md +++ b/README.md @@ -73,31 +73,31 @@ should. | Feature | Galaxy Z Fold5
One UI 8 / Android 16 | AOSP API 34
Android 14 | AOSP API 35
Android 15 | AOSP API 36
Android 16 | Google APIs API 35
Android 15 | Google APIs API 36
Android 16 | LineageOS 22
Android 15 | LineageOS 23
Android 16 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| Module loads in SystemUI | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Settings app opens and writes settings | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Custom unlocked statusbar layout | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | +| Module loads in SystemUI | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested | +| Settings app opens and writes settings | ✅ | Untested | Untested | ✅ opens; settings provider applies | Untested | Untested | Untested | Untested | +| Custom unlocked statusbar layout | ✅ | Untested | Untested | ✅ basic unlocked layout verified | Untested | Untested | Untested | Untested | | Custom lockscreen statusbar layout | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Custom AOD statusbar layout | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Notification icons mode | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Notification cards mode | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Notification dot mode and read-state behavior | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | +| Custom AOD statusbar layout | ✅ | Untested | Untested | ❌ OneUI AOD hooks only | Untested | Untested | Untested | Untested | +| Notification icons mode | ✅ | Untested | Untested | ✅ unlocked icons verified; lockscreen/AOD untested | Untested | Untested | Untested | Untested | +| Notification cards mode | ✅ | Untested | Untested | ❌ OneUI cards mode only | Untested | Untested | Untested | Untested | +| Notification dot mode and read-state behavior | ✅ | Untested | Untested | Untested; lockscreen/AOD style detection is OneUI-specific | Untested | Untested | Untested | Untested | | Hide notification icons by app | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Hide status icons | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Dual-SIM status icon handling | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Samsung status chips: media/navigation/call | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Samsung charging chip handling | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | +| Hide status icons | ✅ | Untested | Untested | ✅ unlocked Wi-Fi slot verified | Untested | Untested | Untested | Untested | +| Dual-SIM status icon handling | ✅ | Untested | Untested | Untested; emulator has no dual-SIM signal state | Untested | Untested | Untested | Untested | +| Samsung status chips: media/navigation/call | ✅ | Untested | Untested | ❌ OneUI-only | Untested | Untested | Untested | Untested | +| Samsung charging chip handling | ✅ | Untested | Untested | ❌ OneUI-only | Untested | Untested | Untested | Untested | | Privacy indicator stability | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Custom statusbar clock text | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Custom drawer clock/date text | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | +| Custom statusbar clock text | ✅ | Untested | Untested | ✅ layout-on and layout-off clocks verified | Untested | Untested | Untested | Untested | +| Custom drawer clock/date text | ✅ | Untested | Untested | ✅ expanded QS clock/date verified | Untested | Untested | Untested | Untested | | Stored clock pattern previews | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Battery bar: base statusbar modes | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Battery bar: fullscreen and transparent statusbar modes | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Battery bar: curved top-edge geometry | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | +| Battery bar: base statusbar modes | ✅ | Untested | Untested | ✅ unlocked verified; lockscreen/AOD untested | Untested | Untested | Untested | Untested | +| Battery bar: fullscreen and transparent statusbar modes | ✅ | Untested | Untested | ✅ fullscreen root verified; transparent untested | Untested | Untested | Untested | Untested | +| Battery bar: curved top-edge geometry | ✅ | Untested | Untested | Untested; emulator has no curved screen edge | Untested | Untested | Untested | Untested | | Battery bar threshold colours and colour math | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Camera cutout and under-display-camera handling | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | +| Camera cutout and under-display-camera handling | ✅ | Untested | Untested | Untested; emulator has no cutout/UDC | Untested | Untested | Untested | Untested | | Rotation and fold/unfold handling | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | | Drawer scrolling and background-work performance | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | -| Debug visualizers and debug notifications | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested | +| Debug visualizers and debug notifications | ✅ | Untested | Untested | ✅ unlocked visualizers verified; debug notifications untested | Untested | Untested | Untested | Untested | Current emulator targets use rooted LSPosed snapshots in a local test lab. They are useful for compatibility work, but the matrix above is intentionally not diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/StockClockController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/StockClockController.java index a3e7ea9..8440473 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/StockClockController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/StockClockController.java @@ -38,8 +38,10 @@ import se.ajpanton.statusbartweak.shell.settings.SbtSettings; final class StockClockController { - private static final String CLOCK_CLASS_NAME = - "com.android.systemui.statusbar.policy.QSClockIndicatorView"; + private static final String[] CLOCK_CLASS_NAMES = { + "com.android.systemui.statusbar.policy.QSClockIndicatorView", + "com.android.systemui.statusbar.policy.Clock" + }; private final RuntimeContext runtimeContext; private final Set trackedClocks = @@ -64,8 +66,25 @@ final class StockClockController { return; } registerLayoutModeListenerIfNeeded(); - Class clockClass = ReflectionSupport.requireClass(CLOCK_CLASS_NAME, classLoader); + ArrayList> clockClasses = new ArrayList<>(); + for (String className : CLOCK_CLASS_NAMES) { + Class clockClass = ReflectionSupport.findClassIfExists(className, classLoader); + if (clockClass != null) { + clockClasses.add(clockClass); + } + } + if (clockClasses.isEmpty()) { + hooksInstalled = true; + return; + } XposedInterface framework = runtimeContext.getFramework(); + for (Class clockClass : clockClasses) { + hookClockClass(framework, clockClass); + } + hooksInstalled = true; + } + + private void hookClockClass(XposedInterface framework, Class clockClass) { XposedHookSupport.hookAllMethodsIfExists(framework, clockClass, "onAttachedToWindow", chain -> { Object result = chain.proceed(); Object target = chain.getThisObject(); @@ -104,7 +123,7 @@ final class StockClockController { } return result; }); - XposedHookSupport.hookAllMethods(framework, clockClass, "notifyTimeChanged", chain -> { + XposedHookSupport.hookAllMethodsIfExists(framework, clockClass, "notifyTimeChanged", chain -> { Object result = chain.proceed(); Object target = chain.getThisObject(); if (!(target instanceof TextView)) { @@ -123,6 +142,14 @@ final class StockClockController { trackAndApply(clockView, bellSound); return result; }); + XposedHookSupport.hookAllMethodsIfExists(framework, clockClass, "updateClock", chain -> { + Object result = chain.proceed(); + Object target = chain.getThisObject(); + if (target instanceof TextView clockView) { + trackAndApply(clockView, null); + } + return result; + }); XposedHookSupport.hookAllMethodsIfExists(framework, clockClass, "setTextColor", chain -> { Object result = chain.proceed(); if (chain.getThisObject() instanceof TextView clockView) { @@ -137,7 +164,6 @@ final class StockClockController { } return result; }); - hooksInstalled = true; } private void reapplyClockIfCustomOrOwned(TextView clockView) { @@ -162,6 +188,9 @@ final class StockClockController { } private void trackAndApply(TextView clockView, Object bellSound) { + if (isNotificationShadeDescendant(clockView)) { + return; + } trackedClocks.add(clockView); registerReceiverIfNeeded(clockView.getContext()); if (bellSound != null) { @@ -219,6 +248,10 @@ final class StockClockController { } private void applyClockText(TextView clockView) { + if (isNotificationShadeDescendant(clockView)) { + restoreOwnedClock(clockView); + return; + } SbtSettings settings = RuntimeSettingsCache.get(clockView.getContext()); boolean layoutEnabled = settings.clockEnabled; if (layoutEnabled) { @@ -330,6 +363,18 @@ final class StockClockController { || (settings.clockCustomFormatEnabled && !pattern.isEmpty()); } + private boolean isNotificationShadeDescendant(View view) { + View current = view; + while (current != null) { + if (current.getClass().getName().contains("NotificationShadeWindowView")) { + return true; + } + Object parent = current.getParent(); + current = parent instanceof View ? (View) parent : null; + } + return false; + } + private void applyClockLineMode(TextView clockView) { clockView.setSingleLine(true); clockView.setHorizontallyScrolling(false); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/notifications/StockUnlockedNotificationIconsController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/notifications/StockUnlockedNotificationIconsController.java index 2f994e9..a661625 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/notifications/StockUnlockedNotificationIconsController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/notifications/StockUnlockedNotificationIconsController.java @@ -43,8 +43,11 @@ final class StockUnlockedNotificationIconsController { if (hooksInstalled || classLoader == null) { return; } - Class containerClass = ReflectionSupport.requireClass(CONTAINER_CLASS_NAME, classLoader); - XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "onAttachedToWindow", chain -> { + Class containerClass = ReflectionSupport.findClassIfExists(CONTAINER_CLASS_NAME, classLoader); + if (containerClass == null) { + return; + } + XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), containerClass, "onAttachedToWindow", chain -> { Object result = chain.proceed(); if (chain.getThisObject() instanceof View view) { if (shouldManageContainer(view)) { @@ -54,7 +57,7 @@ final class StockUnlockedNotificationIconsController { } return result; }); - XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "calculateIconXTranslations", chain -> { + XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), containerClass, "calculateIconXTranslations", chain -> { if (chain.getThisObject() instanceof View view) { if (shouldManageContainer(view)) { trackContainer(view); @@ -63,7 +66,7 @@ final class StockUnlockedNotificationIconsController { } return chain.proceed(); }); - XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "onLayout", chain -> { + XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), containerClass, "onLayout", chain -> { Object result = chain.proceed(); if (chain.getThisObject() instanceof View view) { if (shouldManageContainer(view)) { @@ -75,7 +78,7 @@ final class StockUnlockedNotificationIconsController { } return result; }); - XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "shouldForceOverflow", chain -> { + XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), containerClass, "shouldForceOverflow", chain -> { if (!(chain.getThisObject() instanceof View view)) { return chain.proceed(); } 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 6c87c65..d7146d5 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 @@ -94,6 +94,7 @@ final class StockLayoutCanvasController { private static final Set TARGET_CLASS_SUBSTRINGS = Set.of( "QSClockIndicatorView", + "statusbar.policy.Clock", "SecShelfNotificationIconContainer" ); @@ -638,13 +639,19 @@ final class StockLayoutCanvasController { }); } - Class clockClass = ReflectionSupport.findClassIfExists( + String[] clockClassNames = { "com.android.systemui.statusbar.policy.QSClockIndicatorView", - classLoader); - if (clockClass != null) { + "com.android.systemui.statusbar.policy.Clock" + }; + for (String className : clockClassNames) { + Class clockClass = ReflectionSupport.findClassIfExists(className, classLoader); + if (clockClass == null) { + continue; + } hookViewAppearanceInvalidation(clockClass, "onDarkChanged"); hookViewAppearanceInvalidation(clockClass, "setTextColor"); hookClockTextInvalidation(clockClass, "notifyTimeChanged"); + hookClockTextInvalidation(clockClass, "updateClock"); hookClockTextInvalidation(clockClass, "setText"); hookViewLayoutInvalidation(clockClass, "onLayout"); } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java index da913ac..7c52ec7 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedStockSourceCollector.java @@ -317,7 +317,7 @@ final class UnlockedStockSourceCollector { } if (clockView == null && view instanceof TextView textView - && view.getClass().getName().contains("QSClockIndicatorView")) { + && isStatusbarClockView(textView)) { clockView = textView; } if (statusEndContent == null && idName.equals("status_bar_end_side_content")) { @@ -370,6 +370,12 @@ final class UnlockedStockSourceCollector { carrierView); } + private boolean isStatusbarClockView(TextView view) { + String className = view.getClass().getName(); + return className.contains("QSClockIndicatorView") + || className.equals("com.android.systemui.statusbar.policy.Clock"); + } + private boolean isCarrierTextView(TextView view) { if (view == null) { return false;