Add initial AOSP statusbar compatibility

This commit is contained in:
ajp_anton
2026-07-01 12:52:44 +00:00
parent 861ffd52d5
commit 42449f8c6a
5 changed files with 93 additions and 32 deletions
+18 -18
View File
@@ -73,31 +73,31 @@ should.
| Feature | Galaxy Z Fold5<br>One UI 8 / Android 16 | AOSP API 34<br>Android 14 | AOSP API 35<br>Android 15 | AOSP API 36<br>Android 16 | Google APIs API 35<br>Android 15 | Google APIs API 36<br>Android 16 | LineageOS 22<br>Android 15 | LineageOS 23<br>Android 16 | | Feature | Galaxy Z Fold5<br>One UI 8 / Android 16 | AOSP API 34<br>Android 14 | AOSP API 35<br>Android 15 | AOSP API 36<br>Android 16 | Google APIs API 35<br>Android 15 | Google APIs API 36<br>Android 16 | LineageOS 22<br>Android 15 | LineageOS 23<br>Android 16 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Module loads in SystemUI | ✅ | 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 | 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 | Untested | 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 lockscreen statusbar layout | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested |
| Custom AOD statusbar layout | ✅ | 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 | Untested | Untested | Untested | Untested | Untested | | Notification icons mode | ✅ | Untested | Untested | ✅ unlocked icons verified; lockscreen/AOD untested | Untested | Untested | Untested | Untested |
| Notification cards mode | ✅ | Untested | Untested | 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 | 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 notification icons by app | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested |
| Hide status icons | ✅ | 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 | 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 | Untested | Untested | Untested | Untested | Untested | | Samsung status chips: media/navigation/call | ✅ | Untested | Untested | ❌ OneUI-only | Untested | Untested | Untested | Untested |
| Samsung charging chip handling | ✅ | Untested | Untested | Untested | 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 | | Privacy indicator stability | ✅ | Untested | Untested | Untested | Untested | Untested | Untested | Untested |
| Custom statusbar clock 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 | Untested | 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 | | 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: base statusbar modes | ✅ | Untested | Untested | ✅ unlocked verified; lockscreen/AOD untested | Untested | Untested | Untested | Untested |
| Battery bar: fullscreen and transparent statusbar modes | ✅ | Untested | Untested | 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 | 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 | | 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 | | 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 | | 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 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 are useful for compatibility work, but the matrix above is intentionally not
@@ -38,8 +38,10 @@ import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class StockClockController { final class StockClockController {
private static final String CLOCK_CLASS_NAME = private static final String[] CLOCK_CLASS_NAMES = {
"com.android.systemui.statusbar.policy.QSClockIndicatorView"; "com.android.systemui.statusbar.policy.QSClockIndicatorView",
"com.android.systemui.statusbar.policy.Clock"
};
private final RuntimeContext runtimeContext; private final RuntimeContext runtimeContext;
private final Set<TextView> trackedClocks = private final Set<TextView> trackedClocks =
@@ -64,8 +66,25 @@ final class StockClockController {
return; return;
} }
registerLayoutModeListenerIfNeeded(); registerLayoutModeListenerIfNeeded();
Class<?> clockClass = ReflectionSupport.requireClass(CLOCK_CLASS_NAME, classLoader); ArrayList<Class<?>> 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(); 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 -> { XposedHookSupport.hookAllMethodsIfExists(framework, clockClass, "onAttachedToWindow", chain -> {
Object result = chain.proceed(); Object result = chain.proceed();
Object target = chain.getThisObject(); Object target = chain.getThisObject();
@@ -104,7 +123,7 @@ final class StockClockController {
} }
return result; return result;
}); });
XposedHookSupport.hookAllMethods(framework, clockClass, "notifyTimeChanged", chain -> { XposedHookSupport.hookAllMethodsIfExists(framework, clockClass, "notifyTimeChanged", chain -> {
Object result = chain.proceed(); Object result = chain.proceed();
Object target = chain.getThisObject(); Object target = chain.getThisObject();
if (!(target instanceof TextView)) { if (!(target instanceof TextView)) {
@@ -123,6 +142,14 @@ final class StockClockController {
trackAndApply(clockView, bellSound); trackAndApply(clockView, bellSound);
return result; 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 -> { XposedHookSupport.hookAllMethodsIfExists(framework, clockClass, "setTextColor", chain -> {
Object result = chain.proceed(); Object result = chain.proceed();
if (chain.getThisObject() instanceof TextView clockView) { if (chain.getThisObject() instanceof TextView clockView) {
@@ -137,7 +164,6 @@ final class StockClockController {
} }
return result; return result;
}); });
hooksInstalled = true;
} }
private void reapplyClockIfCustomOrOwned(TextView clockView) { private void reapplyClockIfCustomOrOwned(TextView clockView) {
@@ -162,6 +188,9 @@ final class StockClockController {
} }
private void trackAndApply(TextView clockView, Object bellSound) { private void trackAndApply(TextView clockView, Object bellSound) {
if (isNotificationShadeDescendant(clockView)) {
return;
}
trackedClocks.add(clockView); trackedClocks.add(clockView);
registerReceiverIfNeeded(clockView.getContext()); registerReceiverIfNeeded(clockView.getContext());
if (bellSound != null) { if (bellSound != null) {
@@ -219,6 +248,10 @@ final class StockClockController {
} }
private void applyClockText(TextView clockView) { private void applyClockText(TextView clockView) {
if (isNotificationShadeDescendant(clockView)) {
restoreOwnedClock(clockView);
return;
}
SbtSettings settings = RuntimeSettingsCache.get(clockView.getContext()); SbtSettings settings = RuntimeSettingsCache.get(clockView.getContext());
boolean layoutEnabled = settings.clockEnabled; boolean layoutEnabled = settings.clockEnabled;
if (layoutEnabled) { if (layoutEnabled) {
@@ -330,6 +363,18 @@ final class StockClockController {
|| (settings.clockCustomFormatEnabled && !pattern.isEmpty()); || (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) { private void applyClockLineMode(TextView clockView) {
clockView.setSingleLine(true); clockView.setSingleLine(true);
clockView.setHorizontallyScrolling(false); clockView.setHorizontallyScrolling(false);
@@ -43,8 +43,11 @@ final class StockUnlockedNotificationIconsController {
if (hooksInstalled || classLoader == null) { if (hooksInstalled || classLoader == null) {
return; return;
} }
Class<?> containerClass = ReflectionSupport.requireClass(CONTAINER_CLASS_NAME, classLoader); Class<?> containerClass = ReflectionSupport.findClassIfExists(CONTAINER_CLASS_NAME, classLoader);
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "onAttachedToWindow", chain -> { if (containerClass == null) {
return;
}
XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), containerClass, "onAttachedToWindow", chain -> {
Object result = chain.proceed(); Object result = chain.proceed();
if (chain.getThisObject() instanceof View view) { if (chain.getThisObject() instanceof View view) {
if (shouldManageContainer(view)) { if (shouldManageContainer(view)) {
@@ -54,7 +57,7 @@ final class StockUnlockedNotificationIconsController {
} }
return result; return result;
}); });
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "calculateIconXTranslations", chain -> { XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), containerClass, "calculateIconXTranslations", chain -> {
if (chain.getThisObject() instanceof View view) { if (chain.getThisObject() instanceof View view) {
if (shouldManageContainer(view)) { if (shouldManageContainer(view)) {
trackContainer(view); trackContainer(view);
@@ -63,7 +66,7 @@ final class StockUnlockedNotificationIconsController {
} }
return chain.proceed(); return chain.proceed();
}); });
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "onLayout", chain -> { XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), containerClass, "onLayout", chain -> {
Object result = chain.proceed(); Object result = chain.proceed();
if (chain.getThisObject() instanceof View view) { if (chain.getThisObject() instanceof View view) {
if (shouldManageContainer(view)) { if (shouldManageContainer(view)) {
@@ -75,7 +78,7 @@ final class StockUnlockedNotificationIconsController {
} }
return result; return result;
}); });
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "shouldForceOverflow", chain -> { XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), containerClass, "shouldForceOverflow", chain -> {
if (!(chain.getThisObject() instanceof View view)) { if (!(chain.getThisObject() instanceof View view)) {
return chain.proceed(); return chain.proceed();
} }
@@ -94,6 +94,7 @@ final class StockLayoutCanvasController {
private static final Set<String> TARGET_CLASS_SUBSTRINGS = Set.of( private static final Set<String> TARGET_CLASS_SUBSTRINGS = Set.of(
"QSClockIndicatorView", "QSClockIndicatorView",
"statusbar.policy.Clock",
"SecShelfNotificationIconContainer" "SecShelfNotificationIconContainer"
); );
@@ -638,13 +639,19 @@ final class StockLayoutCanvasController {
}); });
} }
Class<?> clockClass = ReflectionSupport.findClassIfExists( String[] clockClassNames = {
"com.android.systemui.statusbar.policy.QSClockIndicatorView", "com.android.systemui.statusbar.policy.QSClockIndicatorView",
classLoader); "com.android.systemui.statusbar.policy.Clock"
if (clockClass != null) { };
for (String className : clockClassNames) {
Class<?> clockClass = ReflectionSupport.findClassIfExists(className, classLoader);
if (clockClass == null) {
continue;
}
hookViewAppearanceInvalidation(clockClass, "onDarkChanged"); hookViewAppearanceInvalidation(clockClass, "onDarkChanged");
hookViewAppearanceInvalidation(clockClass, "setTextColor"); hookViewAppearanceInvalidation(clockClass, "setTextColor");
hookClockTextInvalidation(clockClass, "notifyTimeChanged"); hookClockTextInvalidation(clockClass, "notifyTimeChanged");
hookClockTextInvalidation(clockClass, "updateClock");
hookClockTextInvalidation(clockClass, "setText"); hookClockTextInvalidation(clockClass, "setText");
hookViewLayoutInvalidation(clockClass, "onLayout"); hookViewLayoutInvalidation(clockClass, "onLayout");
} }
@@ -317,7 +317,7 @@ final class UnlockedStockSourceCollector {
} }
if (clockView == null if (clockView == null
&& view instanceof TextView textView && view instanceof TextView textView
&& view.getClass().getName().contains("QSClockIndicatorView")) { && isStatusbarClockView(textView)) {
clockView = textView; clockView = textView;
} }
if (statusEndContent == null && idName.equals("status_bar_end_side_content")) { if (statusEndContent == null && idName.equals("status_bar_end_side_content")) {
@@ -370,6 +370,12 @@ final class UnlockedStockSourceCollector {
carrierView); 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) { private boolean isCarrierTextView(TextView view) {
if (view == null) { if (view == null) {
return false; return false;