diff --git a/README.md b/README.md
index d08c0bf..c7d5c2b 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,13 @@
# StatusBarTweak
-StatusBarTweak is an Xposed/LSPosed module for Samsung SystemUI. It focuses on
-making the status bar, lockscreen, and Always On Display more configurable while
-still keeping Samsung's own icons and visual style where possible.
+StatusBarTweak is an Xposed/LSPosed module originally built for Samsung
+SystemUI. It focuses on making the status bar, lockscreen, and Always On
+Display more configurable while still keeping the system's own icons and visual
+style where possible.
-The project is currently tested only on a Samsung Galaxy Z Fold5 running One UI
-8.0 / Android 16. Expect device- and One UI-version-specific behaviour.
+The project is physically tested only on a Samsung Galaxy Z Fold5 running One UI
+8.0 / Android 16. Other SystemUI builds have emulator-tested support as shown
+in the compatibility matrix.
## AI Assistance
@@ -14,7 +16,8 @@ decisions, physical device testing, and final behaviour are still manually revie
## What It Does
-- Custom status bar layout for the unlocked screen, lockscreen, and AOD.
+- Custom status bar layout for the unlocked screen, lockscreen, and AOD where
+ supported by the SystemUI build.
- Independent placement of clock, carrier text, notification icons, status
icons, and Samsung status chips.
- Separate notification display handling for Samsung's cards, icons, and dot
@@ -52,15 +55,16 @@ The in-app clock page has the full syntax help and examples.
## Requirements
-- A rooted Android device or emulator.
+- A rooted Android 14+ device or emulator. Actual feature support depends on
+ the SystemUI implementation; see the compatibility matrix.
- A modern Xposed implementation such as LSPosed or another libxposed-compatible runtime.
- Xposed API 101 or newer.
- SystemUI access for the module scope.
-The module is currently developed and manually tested on a Samsung Galaxy Z
-Fold5 running One UI 8.0 / Android 16. Other Android versions and non-Samsung
-SystemUI builds are being investigated, but should be treated as experimental
-until the compatibility matrix says otherwise.
+The module is developed and manually tested on a Samsung Galaxy Z Fold5 running
+One UI 8.0 / Android 16. Non-Samsung builds have emulator-tested support as
+shown below, but hardware-dependent behavior remains experimental unless it has
+also been verified on physical hardware.
## Device Compatibility
@@ -74,35 +78,35 @@ expose the required hardware or state for real verification.
| 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 |
-| Settings app opens and writes settings | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
-| Custom unlocked statusbar layout | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
-| Custom lockscreen statusbar layout | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
-| Custom AOD statusbar layout | ✅ | Untested | Untested | ⚠️ Experimental | Untested | Untested | Untested | Untested |
-| Notification icons mode | ✅ | Untested | Untested | ✅ AOD experimental | Untested | Untested | Untested | Untested |
-| Notification cards mode | ✅ | Untested | Untested | ❌ | Untested | Untested | Untested | Untested |
-| Notification dot mode and read-state behavior | ✅ | Untested | Untested | ❌ | Untested | Untested | Untested | Untested |
-| Hide notification icons by app | ✅ | Untested | Untested | ✅ AOD experimental | Untested | Untested | Untested | Untested |
-| Hide status icons | ✅ | Untested | Untested | ✅ AOD experimental | Untested | Untested | Untested | Untested |
-| Dual-SIM status icon handling | ✅ | Untested | Untested | ⚠️ Experimental | Untested | Untested | Untested | Untested |
-| Samsung status chips: media/navigation/call | ✅ | Untested | Untested | ❌ | Untested | Untested | Untested | Untested |
-| Samsung charging chip handling | ✅ | Untested | Untested | ❌ | Untested | Untested | Untested | Untested |
-| Privacy indicator stability | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
-| Custom statusbar clock text | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
-| Custom drawer clock/date text | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
-| Stored clock pattern previews | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
-| Battery bar: base statusbar modes | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
-| Battery bar: fullscreen and transparent statusbar modes | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
-| Battery bar: curved top-edge geometry | ✅ | Untested | Untested | ⚠️ Experimental | Untested | Untested | Untested | Untested |
-| Battery bar threshold colours and colour math | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
-| Camera cutout and under-display-camera handling | ✅ | Untested | Untested | ⚠️ Experimental | Untested | Untested | Untested | Untested |
-| Rotation and fold/unfold handling | ✅ | Untested | Untested | ✅ rotation verified | Untested | Untested | Untested | Untested |
-| Drawer scrolling and background-work performance | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
-| Debug visualizers and debug notifications | ✅ | Untested | Untested | ✅ | Untested | Untested | Untested | Untested |
+| Module loads in SystemUI | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Settings app opens and writes settings | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Custom unlocked statusbar layout | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Custom lockscreen statusbar layout | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Custom AOD statusbar layout | ✅ | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental |
+| Notification icons mode | ✅ | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental |
+| Notification cards mode | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| Notification dot mode and read-state behavior | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| Hide notification icons by app | ✅ | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental |
+| Hide status icons | ✅ | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental | ✅ AOD experimental |
+| Dual-SIM status icon handling | ✅ | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental |
+| Samsung status chips: media/navigation/call | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| Samsung charging chip handling | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+| Privacy indicator stability | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Custom statusbar clock text | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Custom drawer clock/date text | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Stored clock pattern previews | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Battery bar: base statusbar modes | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Battery bar: fullscreen and transparent statusbar modes | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Battery bar: curved top-edge geometry | ✅ | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental |
+| Battery bar threshold colours and colour math | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Camera cutout and under-display-camera handling | ✅ | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental | ⚠️ Experimental |
+| Rotation and fold/unfold handling | ✅ | ✅ rotation verified | ✅ rotation verified | ✅ rotation verified | ✅ rotation verified | ✅ rotation verified | ✅ rotation verified | ✅ rotation verified |
+| Drawer scrolling and background-work performance | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Debug visualizers and debug notifications | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-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
-optimistic: until a target has been tested, it remains `Untested`.
+Current emulator targets use rooted LSPosed snapshots. They are useful for
+compatibility work, but hardware-specific entries remain marked as experimental
+when the emulator cannot expose the required state.
## Build
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 61ce1a5..dd21bc7 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -69,7 +69,7 @@ android {
defaultConfig {
applicationId = "se.ajpanton.statusbartweak"
- minSdk = 35
+ minSdk = 34
targetSdk = 36
val buildVersionName = project.findProperty("sbtVersionName")?.toString()
?: fallbackVersionName
diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/hooks/XposedHookSupport.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/hooks/XposedHookSupport.java
index e1cc039..daee6c4 100644
--- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/hooks/XposedHookSupport.java
+++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/hooks/XposedHookSupport.java
@@ -2,6 +2,7 @@ package se.ajpanton.statusbartweak.runtime.hooks;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -34,6 +35,9 @@ public final class XposedHookSupport {
if (!methodName.equals(method.getName())) {
continue;
}
+ if (Modifier.isAbstract(method.getModifiers())) {
+ continue;
+ }
method.setAccessible(true);
handles.add(framework.hook(method).intercept(hooker));
}
@@ -59,6 +63,9 @@ public final class XposedHookSupport {
if (!methodName.equals(method.getName())) {
continue;
}
+ if (Modifier.isAbstract(method.getModifiers())) {
+ continue;
+ }
method.setAccessible(true);
handles.add(framework.hook(method).intercept(hooker));
}
@@ -83,4 +90,20 @@ public final class XposedHookSupport {
}
return handles;
}
+
+ public static List hookAllConstructorsIfExists(
+ XposedInterface framework,
+ Class> type,
+ XposedInterface.Hooker hooker
+ ) {
+ Objects.requireNonNull(framework, "framework");
+ Objects.requireNonNull(type, "type");
+ Objects.requireNonNull(hooker, "hooker");
+ List handles = new ArrayList<>();
+ for (Constructor> constructor : type.getDeclaredConstructors()) {
+ constructor.setAccessible(true);
+ handles.add(framework.hook(constructor).intercept(hooker));
+ }
+ return handles;
+ }
}
diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/DarkIconDispatcherGuard.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/DarkIconDispatcherGuard.java
index 4652e40..dba4c52 100644
--- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/DarkIconDispatcherGuard.java
+++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/DarkIconDispatcherGuard.java
@@ -33,7 +33,7 @@ final class DarkIconDispatcherGuard {
}
void install(Class> darkIconDispatcherClass) {
- XposedHookSupport.hookAllConstructors(
+ XposedHookSupport.hookAllConstructorsIfExists(
runtimeContext.getFramework(),
darkIconDispatcherClass,
chain -> {
diff --git a/test-harness/src/main/AndroidManifest.xml b/test-harness/src/main/AndroidManifest.xml
index d29f03e..6ef01f2 100644
--- a/test-harness/src/main/AndroidManifest.xml
+++ b/test-harness/src/main/AndroidManifest.xml
@@ -27,7 +27,8 @@
+ android:exported="true"
+ android:launchMode="singleTop" />
diff --git a/test-harness/src/main/java/se/ajpanton/statusbartweak/harness/ScenarioActivity.java b/test-harness/src/main/java/se/ajpanton/statusbartweak/harness/ScenarioActivity.java
index 79b8399..26cc2b7 100644
--- a/test-harness/src/main/java/se/ajpanton/statusbartweak/harness/ScenarioActivity.java
+++ b/test-harness/src/main/java/se/ajpanton/statusbartweak/harness/ScenarioActivity.java
@@ -2,6 +2,7 @@ package se.ajpanton.statusbartweak.harness;
import android.Manifest;
import android.app.Activity;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
@@ -51,7 +52,20 @@ public final class ScenarioActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- String scenario = getIntent().getStringExtra(EXTRA_SCENARIO);
+ applyScenario(getIntent());
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ stopCamera();
+ stopMicrophone();
+ applyScenario(intent);
+ }
+
+ private void applyScenario(Intent intent) {
+ String scenario = intent != null ? intent.getStringExtra(EXTRA_SCENARIO) : null;
if (scenario == null) {
scenario = SCENARIO_OPAQUE_LIGHT;
}