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;