diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8adee41..52115d9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,9 +1,12 @@ - + - + + android:exported="true" + tools:ignore="ExportedContentProvider" /> diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/NotificationKeyReflection.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/NotificationKeyReflection.java new file mode 100644 index 0000000..e2ce979 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/NotificationKeyReflection.java @@ -0,0 +1,102 @@ +package se.ajpanton.statusbartweak.runtime; + +import android.view.View; + +import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; +import se.ajpanton.statusbartweak.shell.settings.AppIconRules; + +public final class NotificationKeyReflection { + private static final String[] ENTRY_KEY_FIELD_NAMES = { + "mKey", + "key", + "notificationKey", + "mNotificationKey" + }; + private static final String[] ICON_VIEW_KEY_FIELD_NAMES = { + "mNotificationKey", + "notificationKey", + "mKey", + "key" + }; + + private NotificationKeyReflection() { + } + + public static String rowFilterKey(Object target) { + String key = nonEmptyStringFromMethod(target, "getKey"); + if (key != null) { + return key; + } + key = nonEmptyStringFromMethod(target, "getNotificationKey"); + if (key != null) { + return key; + } + return nonEmptyStringFromFields(target, "mKey", "key"); + } + + public static String keyForEntry(Object entry) { + if (entry == null) { + return null; + } + String key = nonEmptyStringFromMethod(entry, "getKey"); + if (key != null) { + return key; + } + return nonEmptyStringFromFields(entry, ENTRY_KEY_FIELD_NAMES); + } + + public static String keyForIconView(View view) { + if (view == null) { + return null; + } + String key = nonEmptyStringFromMethod(view, "getNotificationKey"); + if (key != null) { + return key; + } + return nonEmptyStringFromFields(view, ICON_VIEW_KEY_FIELD_NAMES); + } + + public static Object entryForRow(View row) { + Object value = primaryEntryForRow(row); + return value != null ? value : ReflectionSupport.getFieldValue(row, "entry"); + } + + public static Object primaryEntryForRow(View row) { + Object value = ReflectionSupport.invokeMethod(row, "getEntry"); + if (value != null) { + return value; + } + return ReflectionSupport.getFieldValue(row, "mEntry"); + } + + public static String packageFromNotificationKey(String key) { + if (key == null) { + return null; + } + String normalized = key.trim(); + int suffix = normalized.indexOf('@'); + if (suffix >= 0) { + normalized = normalized.substring(0, suffix); + } + int slash = normalized.indexOf('/'); + if (slash >= 0) { + normalized = normalized.substring(0, slash); + } + return AppIconRules.isValidPackageName(normalized) ? normalized : null; + } + + private static String nonEmptyStringFromMethod(Object target, String methodName) { + Object value = ReflectionSupport.invokeMethod(target, methodName); + return value instanceof String string && !string.isEmpty() ? string : null; + } + + private static String nonEmptyStringFromFields(Object target, String... fieldNames) { + for (String fieldName : fieldNames) { + Object value = ReflectionSupport.getFieldValue(target, fieldName); + if (value instanceof String string && !string.isEmpty()) { + return string; + } + } + return null; + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/SystemContextProvider.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/SystemContextProvider.java new file mode 100644 index 0000000..12c4915 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/SystemContextProvider.java @@ -0,0 +1,24 @@ +package se.ajpanton.statusbartweak.runtime; + +import android.annotation.SuppressLint; +import android.content.Context; + +import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; + +public final class SystemContextProvider { + private SystemContextProvider() { + } + + @SuppressLint("PrivateApi") + public static Context currentApplication() { + try { + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + Object app = ReflectionSupport.invokeStaticMethod( + activityThreadClass, + "currentApplication"); + return app instanceof Context context ? context : null; + } catch (Throwable ignored) { + return null; + } + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/ViewIdNames.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/ViewIdNames.java new file mode 100644 index 0000000..8f7fb1a --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/ViewIdNames.java @@ -0,0 +1,24 @@ +package se.ajpanton.statusbartweak.runtime; + +import android.content.res.Resources; +import android.view.View; + +public final class ViewIdNames { + private ViewIdNames() { + } + + public static String idName(View view) { + if (view == null || view.getId() == View.NO_ID) { + return ""; + } + try { + return view.getResources().getResourceEntryName(view.getId()); + } catch (Resources.NotFoundException ignored) { + return ""; + } + } + + public static boolean hasIdName(View view, String expectedIdName) { + return expectedIdName != null && expectedIdName.equals(idName(view)); + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeContext.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeContext.java index ef8f133..abfaa5b 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeContext.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeContext.java @@ -41,10 +41,6 @@ public final class RuntimeContext { return RuntimeLogGate.isEnabled(); } - public void logInfo(String message) { - logSplit(Log.INFO, message, null); - } - public void logWarning(String message, Throwable throwable) { logSplit(Log.WARN, message, throwable); } @@ -54,7 +50,7 @@ public final class RuntimeContext { } private void logSplit(int priority, String message, Throwable throwable) { - if (!RuntimeLogGate.isEnabled()) { + if (!isLogEnabled()) { return; } String safeMessage = message != null ? message : ""; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeFeature.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeFeature.java index 16977e5..a82a0c8 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeFeature.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeFeature.java @@ -1,6 +1,7 @@ package se.ajpanton.statusbartweak.runtime.features; import io.github.libxposed.api.XposedModuleInterface; +import se.ajpanton.statusbartweak.shell.settings.SbtSettings; /** * Small independent runtime slice. @@ -13,6 +14,10 @@ public interface RuntimeFeature { String getName(); + static boolean isSystemUiPackage(XposedModuleInterface.PackageReadyParam param) { + return param != null && SbtSettings.PACKAGE_SYSTEMUI.equals(param.getPackageName()); + } + default void onModuleLoaded( RuntimeContext context, XposedModuleInterface.ModuleLoadedParam param diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeLogGate.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeLogGate.java index d1dd720..dd7ff1d 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeLogGate.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/RuntimeLogGate.java @@ -1,12 +1,9 @@ package se.ajpanton.statusbartweak.runtime.features; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Build; -import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; +import se.ajpanton.statusbartweak.runtime.SystemContextProvider; +import se.ajpanton.statusbartweak.runtime.settings.BroadcastReceiverSupport; import se.ajpanton.statusbartweak.shell.settings.SbtDefaults; import se.ajpanton.statusbartweak.shell.settings.SbtSettings; @@ -25,7 +22,7 @@ final class RuntimeLogGate { if (enabled != null) { return enabled; } - Context context = getSystemContext(); + Context context = SystemContextProvider.currentApplication(); if (context == null) { return SbtDefaults.DEBUG_WRITE_LOGS_DEFAULT; } @@ -34,7 +31,7 @@ final class RuntimeLogGate { if (cachedEnabled == null) { cachedEnabled = SbtSettings.isDebugLogWritingEnabled(context); } - return cachedEnabled != null ? cachedEnabled : SbtDefaults.DEBUG_WRITE_LOGS_DEFAULT; + return cachedEnabled; } } @@ -52,37 +49,12 @@ final class RuntimeLogGate { if (receiverRegistered) { return; } - Context appContext = context.getApplicationContext(); - if (appContext == null) { - appContext = context; - } - BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - invalidate(); - } - }; - IntentFilter filter = new IntentFilter(SbtSettings.ACTION_SETTINGS_CHANGED); - if (Build.VERSION.SDK_INT >= 33) { - appContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED); - } else { - appContext.registerReceiver(receiver, filter); - } + BroadcastReceiverSupport.registerInvalidatingReceiver( + context, + RuntimeLogGate::invalidate, + BroadcastReceiverSupport.filter(SbtSettings.ACTION_SETTINGS_CHANGED)); receiverRegistered = true; } } - private static Context getSystemContext() { - try { - Class activityThreadClass = Class.forName("android.app.ActivityThread"); - Object app = ReflectionSupport.invokeStaticMethod( - activityThreadClass, - "currentApplication", - new Class[0] - ); - return app instanceof Context context ? context : null; - } catch (Throwable ignored) { - return null; - } - } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarController.java index c283a7a..b861787 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarController.java @@ -3,14 +3,11 @@ package se.ajpanton.statusbartweak.runtime.features.battery; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.os.BatteryManager; -import android.os.Build; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -24,9 +21,11 @@ import java.util.Set; import java.util.WeakHashMap; import io.github.libxposed.api.XposedInterface; +import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; +import se.ajpanton.statusbartweak.runtime.settings.BroadcastReceiverSupport; import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache; import se.ajpanton.statusbartweak.shell.settings.BatteryBarStyle; import se.ajpanton.statusbartweak.shell.settings.SbtSettings; @@ -116,7 +115,6 @@ final class BatteryBarController { root.removeOnLayoutChangeListener(listener); } roots.remove(root); - lastSignatures.remove(root); removeBatteryBarView(root); } @@ -124,10 +122,6 @@ final class BatteryBarController { if (context == null || receiverRegistered) { return; } - Context appContext = context.getApplicationContext(); - if (appContext == null) { - appContext = context; - } BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context ctx, Intent intent) { @@ -141,16 +135,12 @@ final class BatteryBarController { } } }; - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_USER_UNLOCKED); - filter.addAction(SbtSettings.ACTION_SETTINGS_CHANGED); - Intent sticky; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - sticky = appContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED); - } else { - sticky = appContext.registerReceiver(receiver, filter); - } + Intent sticky = BroadcastReceiverSupport.registerExportedOnApplicationContext( + context, + receiver, + BroadcastReceiverSupport.filter( + Intent.ACTION_BATTERY_CHANGED, + SbtSettings.ACTION_SETTINGS_CHANGED)); receiverRegistered = true; if (sticky != null) { updateBatteryState(sticky); @@ -353,15 +343,7 @@ final class BatteryBarController { } private static boolean matchesClockId(View view) { - int id = view.getId(); - if (id == View.NO_ID) { - return false; - } - try { - return "clock".equals(view.getResources().getResourceEntryName(id)); - } catch (Resources.NotFoundException ignored) { - return false; - } + return ViewIdNames.hasIdName(view, "clock"); } private static int dpToPx(Context context, int dp) { diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarFeature.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarFeature.java index 615d99e..3dc9674 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarFeature.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarFeature.java @@ -25,7 +25,7 @@ public final class BatteryBarFeature implements RuntimeFeature { RuntimeContext context, XposedModuleInterface.PackageReadyParam param ) { - if (param == null || !"com.android.systemui".equals(param.getPackageName())) { + if (!RuntimeFeature.isSystemUiPackage(param)) { return; } installController(context, param.getClassLoader()); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/chips/StatusChipHider.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/chips/StatusChipHider.java index ff5ece0..0b09675 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/chips/StatusChipHider.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/chips/StatusChipHider.java @@ -3,8 +3,6 @@ package se.ajpanton.statusbartweak.runtime.features.chips; import android.app.KeyguardManager; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.os.Build; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -12,13 +10,17 @@ import android.widget.TextView; import java.util.ArrayDeque; import java.util.Collections; +import java.util.Locale; import java.util.Set; import java.util.WeakHashMap; +import se.ajpanton.statusbartweak.runtime.SystemContextProvider; +import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; import se.ajpanton.statusbartweak.runtime.hooks.InstanceStateStore; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; +import se.ajpanton.statusbartweak.runtime.settings.BroadcastReceiverSupport; import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache; import se.ajpanton.statusbartweak.shell.settings.SbtSettings; @@ -64,7 +66,7 @@ final class StatusChipHider { if (hooksInstalled) { return; } - installBroadcastReceiver(getSystemContext()); + installBroadcastReceiver(SystemContextProvider.currentApplication()); refreshSettings(); installOngoingActivityHooks(classLoader); installTouchInterceptHooks(classLoader); @@ -148,7 +150,7 @@ final class StatusChipHider { return result; } if (scannedRoots.add(root)) { - applyToViewTree(root, false); + applyToViewTree(root); } else { keepForcedHiddenViewsHidden(); } @@ -173,7 +175,7 @@ final class StatusChipHider { return result; } if (view.getWidth() > 0 && view.getHeight() > 0) { - applySourceDecision(view, false); + applySourceDecision(view); } return result; }); @@ -206,7 +208,7 @@ final class StatusChipHider { return result; } if (view.getWidth() > 0 && view.getHeight() > 0) { - applySourceDecision(view, false); + applySourceDecision(view); } return result; }); @@ -219,12 +221,6 @@ final class StatusChipHider { if (context == null) { return; } - Context appContext = context.getApplicationContext(); - if (appContext == null) { - appContext = context; - } - IntentFilter filter = new IntentFilter(SbtSettings.ACTION_SETTINGS_CHANGED); - filter.addAction(Intent.ACTION_USER_UNLOCKED); android.content.BroadcastReceiver receiver = new android.content.BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -235,16 +231,17 @@ final class StatusChipHider { rescanTrackedRoots(); } }; - if (Build.VERSION.SDK_INT >= 33) { - appContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED); - } else { - appContext.registerReceiver(receiver, filter); - } + BroadcastReceiverSupport.registerExportedOnApplicationContext( + context, + receiver, + BroadcastReceiverSupport.filter( + SbtSettings.ACTION_SETTINGS_CHANGED, + Intent.ACTION_USER_UNLOCKED)); broadcastInstalled = true; } private void refreshSettings() { - Context context = getSystemContext(); + Context context = SystemContextProvider.currentApplication(); if (context == null) { hideAll = false; hideMediaUnlocked = false; @@ -271,7 +268,7 @@ final class StatusChipHider { for (View root : trackedRoots) { if (root != null) { scannedRoots.add(root); - applyToViewTree(root, false); + applyToViewTree(root); } } } @@ -302,7 +299,7 @@ final class StatusChipHider { userManager ); ReflectionSupport.invokeMethod(controller, "updateParentViewVisibility", new Class[]{boolean.class}, false); - ReflectionSupport.invokeMethod(controller, "updateAdapter", new Class[0]); + ReflectionSupport.invokeMethod(controller, "updateAdapter"); } private boolean shouldSuppressTopOngoingController() { @@ -325,7 +322,7 @@ final class StatusChipHider { return sawItem; } - private void applyToViewTree(View root, boolean restoreOnly) { + private void applyToViewTree(View root) { ArrayDeque stack = new ArrayDeque<>(); stack.push(root); while (!stack.isEmpty()) { @@ -338,18 +335,14 @@ final class StatusChipHider { stack.push(group.getChildAt(index)); } } - applySourceDecision(source, restoreOnly); + applySourceDecision(source); } } - private boolean applySourceDecision(View source, boolean restoreOnly) { + private boolean applySourceDecision(View source) { if (source == null) { return false; } - if (restoreOnly) { - restoreIfForcedHidden(source); - return false; - } int zone = detectZone(source); if (zone == ZONE_NONE || !isSourceCandidate(source)) { restoreIfForcedHidden(source); @@ -371,7 +364,7 @@ final class StatusChipHider { String classNames = target == source ? collectClassNames(source) : collectClassNames(source) + " " + collectClassNames(target); - String lowerText = kindText.toLowerCase(); + String lowerText = kindText.toLowerCase(Locale.ROOT); if (looksLikePrivacy(target, kindText) || isDefinitelyNotChipText(lowerText)) { restoreIfForcedHidden(target); return false; @@ -415,7 +408,7 @@ final class StatusChipHider { return false; } String sourceText = collectText(source); - String lowerText = sourceText.toLowerCase(); + String lowerText = sourceText.toLowerCase(Locale.ROOT); if (looksLikePrivacy(source, sourceText) || isDefinitelyNotChipText(lowerText)) { return false; } @@ -427,17 +420,7 @@ final class StatusChipHider { } private boolean isSourceCandidate(View view) { - if (isOngoingActivityCapsule(view)) { - return false; - } - if (!isSystemUiView(view) || !looksLikeChipGeometry(view)) { - return false; - } - String className = className(view).toLowerCase(); - if (className.contains("privacy") || isDefinitelyNotChipClass(className)) { - return false; - } - return isChipClassName(className); + return isTransitionSourceCandidate(view) && looksLikeChipGeometry(view); } private boolean isTransitionSourceCandidate(View view) { @@ -447,7 +430,7 @@ final class StatusChipHider { if (!isSystemUiView(view)) { return false; } - String className = className(view).toLowerCase(); + String className = className(view).toLowerCase(Locale.ROOT); if (className.contains("privacy") || isDefinitelyNotChipClass(className)) { return false; } @@ -455,7 +438,7 @@ final class StatusChipHider { } private View resolveHideTarget(View source, int sourceZone) { - String sourceClass = className(source).toLowerCase(); + String sourceClass = className(source).toLowerCase(Locale.ROOT); if (sourceClass.contains("touchinterceptframelayout")) { return source; } @@ -484,7 +467,7 @@ final class StatusChipHider { if (!looksLikeChipGeometry(candidate)) { return false; } - String className = className(candidate).toLowerCase(); + String className = className(candidate).toLowerCase(Locale.ROOT); if (className.contains("privacy") || isDefinitelyNotChipClass(className)) { return false; } @@ -546,10 +529,10 @@ final class StatusChipHider { } private int classifyKind(View view, String text, String classNames) { - String lowerText = text != null ? text.toLowerCase() : ""; + String lowerText = text != null ? text.toLowerCase(Locale.ROOT) : ""; String lowerClass = classNames != null && !classNames.isEmpty() - ? classNames.toLowerCase() - : (view != null ? className(view).toLowerCase() : ""); + ? classNames.toLowerCase(Locale.ROOT) + : (view != null ? className(view).toLowerCase(Locale.ROOT) : ""); if (containsToken(lowerText, "call") || containsPhrase(lowerText, "ongoing call") || lowerClass.contains("call")) { @@ -598,8 +581,8 @@ final class StatusChipHider { } private boolean looksLikePrivacy(View view, String text) { - String lowerClass = className(view).toLowerCase(); - String lowerText = text != null ? text.toLowerCase() : ""; + String lowerClass = className(view).toLowerCase(Locale.ROOT); + String lowerText = text != null ? text.toLowerCase(Locale.ROOT) : ""; return lowerClass.contains("privacy") || containsToken(lowerText, "camera") || containsToken(lowerText, "microphone") @@ -784,14 +767,7 @@ final class StatusChipHider { } private boolean isOngoingActivityCapsule(View view) { - if (view == null || view.getId() == View.NO_ID) { - return false; - } - try { - return "ongoing_activity_capsule".equals(view.getResources().getResourceEntryName(view.getId())); - } catch (Throwable ignored) { - return false; - } + return ViewIdNames.hasIdName(view, "ongoing_activity_capsule"); } private void appendText(StringBuilder out, CharSequence value) { @@ -878,23 +854,9 @@ final class StatusChipHider { return Math.round(dp * view.getResources().getDisplayMetrics().density); } - private Context getSystemContext() { - try { - Class activityThreadClass = Class.forName("android.app.ActivityThread"); - Object app = ReflectionSupport.invokeStaticMethod( - activityThreadClass, - "currentApplication", - new Class[0] - ); - return app instanceof Context context ? context : null; - } catch (Throwable ignored) { - return null; - } - } - private boolean isKeyguardLocked() { try { - Context context = getSystemContext(); + Context context = SystemContextProvider.currentApplication(); if (context == null) { return false; } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/chips/StatusChipsFeature.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/chips/StatusChipsFeature.java index c2511a6..031edaf 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/chips/StatusChipsFeature.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/chips/StatusChipsFeature.java @@ -25,7 +25,7 @@ public final class StatusChipsFeature implements RuntimeFeature { RuntimeContext context, XposedModuleInterface.PackageReadyParam param ) { - if (param == null || !"com.android.systemui".equals(param.getPackageName())) { + if (!RuntimeFeature.isSystemUiPackage(param)) { return; } installController(context, param.getClassLoader()); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockFeature.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockFeature.java index 8410323..8177dff 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockFeature.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockFeature.java @@ -25,7 +25,7 @@ public final class ClockFeature implements RuntimeFeature { RuntimeContext context, XposedModuleInterface.PackageReadyParam param ) { - if (param == null || !"com.android.systemui".equals(param.getPackageName())) { + if (!RuntimeFeature.isSystemUiPackage(param)) { return; } installController(context, param.getClassLoader()); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockMarkupSupport.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockMarkupSupport.java index 248958e..288e24e 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockMarkupSupport.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockMarkupSupport.java @@ -6,6 +6,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; final class ClockMarkupSupport { @@ -207,8 +209,7 @@ final class ClockMarkupSupport { "$1").toLowerCase(Locale.ROOT); return new ParsedTag(false, true, name, trimmed, TagCategory.OTHER); } - java.util.regex.Matcher matcher = java.util.regex.Pattern - .compile("(?i)<\\s*([a-z0-9_-]+)([^>]*)>") + Matcher matcher = Pattern.compile("(?i)<\\s*([a-z0-9_-]+)([^>]*)>") .matcher(trimmed); if (!matcher.matches()) { return null; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockPatternParser.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockPatternParser.java index f856134..80748e7 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockPatternParser.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/ClockPatternParser.java @@ -3,6 +3,8 @@ package se.ajpanton.statusbartweak.runtime.features.clock; import android.text.TextUtils; import java.util.ArrayList; +import java.util.List; +import java.util.Locale; final class ClockPatternParser { @@ -112,7 +114,7 @@ final class ClockPatternParser { if (TextUtils.isEmpty(markup)) { return ClockParsedPattern.ClockParsedRow.plain("", gapBeforePx); } - java.util.List pipePositions = ClockMarkupSupport.findPipePositions(markup); + List pipePositions = ClockMarkupSupport.findPipePositions(markup); if (pipePositions.size() > 2) { return ClockParsedPattern.ClockParsedRow.syntaxError(gapBeforePx); } @@ -169,7 +171,7 @@ final class ClockPatternParser { new ClockMarkupSupport.ParsedTag( true, false, - name.toLowerCase(java.util.Locale.ROOT), + name.toLowerCase(Locale.ROOT), "", ClockMarkupSupport.TagCategory.OTHER)); continue; @@ -210,7 +212,7 @@ final class ClockPatternParser { new ClockMarkupSupport.ParsedTag( false, false, - token.toLowerCase(java.util.Locale.ROOT), + token.toLowerCase(Locale.ROOT), literal, ClockMarkupSupport.TagCategory.OTHER)); } 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 a528263..79753fc 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 @@ -4,8 +4,6 @@ import android.app.KeyguardManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.os.Build; import android.text.Spanned; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; @@ -24,6 +22,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; @@ -33,6 +32,7 @@ import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; import se.ajpanton.statusbartweak.runtime.mode.SceneKey; +import se.ajpanton.statusbartweak.runtime.settings.BroadcastReceiverSupport; import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache; import se.ajpanton.statusbartweak.shell.settings.SbtSettings; @@ -111,7 +111,7 @@ final class StockClockController { return result; } TextView clockView = (TextView) target; - java.util.List args = chain.getArgs(); + List args = chain.getArgs(); Object bellSound = args != null && !args.isEmpty() ? args.get(0) : null; @@ -125,31 +125,28 @@ final class StockClockController { }); XposedHookSupport.hookAllMethodsIfExists(framework, clockClass, "setTextColor", chain -> { Object result = chain.proceed(); - Object target = chain.getThisObject(); - if (target instanceof TextView) { - TextView clockView = (TextView) target; - if (hasCustomClockTextEnabled(RuntimeSettingsCache.get(clockView.getContext())) - || ownedClocks.contains(clockView)) { - applyClockText(clockView); - } + if (chain.getThisObject() instanceof TextView clockView) { + reapplyClockIfCustomOrOwned(clockView); } return result; }); XposedHookSupport.hookAllMethodsIfExists(framework, clockClass, "onDarkChanged", chain -> { Object result = chain.proceed(); - Object target = chain.getThisObject(); - if (target instanceof TextView) { - TextView clockView = (TextView) target; - if (hasCustomClockTextEnabled(RuntimeSettingsCache.get(clockView.getContext())) - || ownedClocks.contains(clockView)) { - applyClockText(clockView); - } + if (chain.getThisObject() instanceof TextView clockView) { + reapplyClockIfCustomOrOwned(clockView); } return result; }); hooksInstalled = true; } + private void reapplyClockIfCustomOrOwned(TextView clockView) { + if (hasCustomClockTextEnabled(RuntimeSettingsCache.get(clockView.getContext())) + || ownedClocks.contains(clockView)) { + applyClockText(clockView); + } + } + private void registerLayoutModeListenerIfNeeded() { if (layoutModeListenerRegistered) { return; @@ -188,30 +185,24 @@ final class StockClockController { if (context == null || receiverRegistered) { return; } - Context appContext = context.getApplicationContext(); - if (appContext == null) { - appContext = context; - } BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context ctx, Intent intent) { refreshAllClocks(); } }; - IntentFilter filter = new IntentFilter(); - filter.addAction(SbtSettings.ACTION_SETTINGS_CHANGED); - filter.addAction(Intent.ACTION_USER_UNLOCKED); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - filter.addAction(Intent.ACTION_TIME_TICK); - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - filter.addAction(Intent.ACTION_LOCALE_CHANGED); - filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - if (Build.VERSION.SDK_INT >= 33) { - appContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED); - } else { - appContext.registerReceiver(receiver, filter); - } + BroadcastReceiverSupport.registerExportedOnApplicationContext( + context, + receiver, + BroadcastReceiverSupport.filter( + SbtSettings.ACTION_SETTINGS_CHANGED, + Intent.ACTION_USER_UNLOCKED, + Intent.ACTION_BATTERY_CHANGED, + Intent.ACTION_TIME_TICK, + Intent.ACTION_TIME_CHANGED, + Intent.ACTION_TIMEZONE_CHANGED, + Intent.ACTION_LOCALE_CHANGED, + Intent.ACTION_CONFIGURATION_CHANGED)); receiverRegistered = true; } @@ -322,9 +313,6 @@ final class StockClockController { } private boolean hasCustomClockTextEnabled(SbtSettings settings) { - if (settings == null) { - return false; - } String pattern = settings.clockCustomFormat != null ? settings.clockCustomFormat : ""; return settings.clockShowSeconds || settings.clockShowDatePrefix @@ -477,7 +465,7 @@ final class StockClockController { } private void stopTicker(TextView clockView) { - Runnable ticker = tickers.get(clockView); + Runnable ticker = tickers.remove(clockView); if (ticker != null) { clockView.removeCallbacks(ticker); } @@ -715,10 +703,8 @@ final class StockClockController { source.getPaddingTop(), source.getPaddingRight(), source.getPaddingBottom()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - target.setLetterSpacing(source.getLetterSpacing()); - target.setFontFeatureSettings(source.getFontFeatureSettings()); - } + target.setLetterSpacing(source.getLetterSpacing()); + target.setFontFeatureSettings(source.getFontFeatureSettings()); VisualState visualState = resolveVisualState(source); target.setVisibility(visualState.visible ? View.VISIBLE : View.INVISIBLE); target.setAlpha(visualState.alpha); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/debug/DebugNotificationAutoGroupController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/debug/DebugNotificationAutoGroupController.java index c4da9df..951781e 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/debug/DebugNotificationAutoGroupController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/debug/DebugNotificationAutoGroupController.java @@ -111,24 +111,18 @@ final class DebugNotificationAutoGroupController { packageName = candidatePackage; } if (arg != null && arg.getClass().getName().contains("NotificationRecord")) { - Object sbnObj = ReflectionSupport.invokeMethod(arg, "getSbn", new Class[0]); + Object sbnObj = ReflectionSupport.invokeMethod(arg, "getSbn"); if (sbnObj instanceof StatusBarNotification statusBarNotification) { sbn = statusBarNotification; packageName = sbn.getPackageName(); notification = sbn.getNotification(); break; } - Object packageObj = ReflectionSupport.invokeMethod( - arg, - "getPackageName", - new Class[0]); + Object packageObj = ReflectionSupport.invokeMethod(arg, "getPackageName"); if (packageObj instanceof String stringPackage) { packageName = stringPackage; } - Object notificationObj = ReflectionSupport.invokeMethod( - arg, - "getNotification", - new Class[0]); + Object notificationObj = ReflectionSupport.invokeMethod(arg, "getNotification"); if (notificationObj instanceof Notification candidateNotification) { notification = candidateNotification; } @@ -150,20 +144,17 @@ final class DebugNotificationAutoGroupController { if (record == null) { return false; } - Object sbnObj = ReflectionSupport.invokeMethod(record, "getSbn", new Class[0]); + Object sbnObj = ReflectionSupport.invokeMethod(record, "getSbn"); if (sbnObj instanceof StatusBarNotification sbn) { return SbtSettings.MODULE_PACKAGE.equals(sbn.getPackageName()); } if (sbnObj != null) { - Object packageObj = ReflectionSupport.invokeMethod( - sbnObj, - "getPackageName", - new Class[0]); + Object packageObj = ReflectionSupport.invokeMethod(sbnObj, "getPackageName"); if (packageObj instanceof String packageName) { return SbtSettings.MODULE_PACKAGE.equals(packageName); } } - Object packageObj = ReflectionSupport.invokeMethod(record, "getPackageName", new Class[0]); + Object packageObj = ReflectionSupport.invokeMethod(record, "getPackageName"); return packageObj instanceof String packageName && SbtSettings.MODULE_PACKAGE.equals(packageName); } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/AodBottomBatteryFallbackController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/AodBottomBatteryFallbackController.java index 061c877..966e306 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/AodBottomBatteryFallbackController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/AodBottomBatteryFallbackController.java @@ -327,11 +327,11 @@ final class AodBottomBatteryFallbackController { } try { Object panelLazy = ReflectionSupport.getFieldValue(manager, "mPanelViewControllerLazy"); - Object panel = ReflectionSupport.invokeMethod(panelLazy, "get", new Class[0]); + Object panel = ReflectionSupport.invokeMethod(panelLazy, "get"); Object bottomController = ReflectionSupport.getFieldValue(panel, "mKeyguardSecBottomAreaViewController"); - Object bottomView = ReflectionSupport.invokeMethod(bottomController, "getView", new Class[0]); + Object bottomView = ReflectionSupport.invokeMethod(bottomController, "getView"); Object delegate = ReflectionSupport.getFieldValue(bottomView, "bottomDozeArea$delegate"); - Object host = ReflectionSupport.invokeMethod(delegate, "getValue", new Class[0]); + Object host = ReflectionSupport.invokeMethod(delegate, "getValue"); return host instanceof FrameLayout frameLayout ? frameLayout : null; } catch (Throwable ignored) { return null; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/AodCallVisibilityController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/AodCallVisibilityController.java index c1eba7c..d6b4324 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/AodCallVisibilityController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/AodCallVisibilityController.java @@ -1,11 +1,15 @@ package se.ajpanton.statusbartweak.runtime.features.misc; +import android.Manifest; +import android.annotation.SuppressLint; import android.content.Context; +import android.content.pm.PackageManager; import android.media.AudioManager; import android.telecom.TelecomManager; import android.telephony.TelephonyManager; import io.github.libxposed.api.XposedInterface; +import se.ajpanton.statusbartweak.runtime.SystemContextProvider; import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; @@ -68,40 +72,25 @@ final class AodCallVisibilityController { "com.android.systemui.doze.DozeMachine", classLoader ); - Class stateClass = ReflectionSupport.findClassIfExists( - "com.android.systemui.doze.DozeMachine$State", - classLoader - ); - if (dozeMachineClass == null || stateClass == null) { + if (dozeMachineClass == null) { return; } XposedInterface framework = runtimeContext.getFramework(); - XposedHookSupport.hookAllMethods(framework, dozeMachineClass, "requestState", chain -> { - Object state = chain.getArgs().isEmpty() ? null : chain.getArg(0); - if (!(state instanceof Enum target) || !shouldForceAodForCall()) { - return chain.proceed(); - } - Enum replacement = overrideDozeState(target); - if (replacement == null) { - return chain.proceed(); - } - Object[] args = chain.getArgs().toArray(); - args[0] = replacement; - return chain.proceed(args); - }); - XposedHookSupport.hookAllMethods(framework, dozeMachineClass, "transitionTo", chain -> { - Object state = chain.getArgs().isEmpty() ? null : chain.getArg(0); - if (!(state instanceof Enum target) || !shouldForceAodForCall()) { - return chain.proceed(); - } - Enum replacement = overrideDozeState(target); - if (replacement == null) { - return chain.proceed(); - } - Object[] args = chain.getArgs().toArray(); - args[0] = replacement; - return chain.proceed(args); - }); + for (String methodName : new String[]{"requestState", "transitionTo"}) { + XposedHookSupport.hookAllMethods(framework, dozeMachineClass, methodName, chain -> { + Object state = chain.getArgs().isEmpty() ? null : chain.getArg(0); + if (!(state instanceof Enum target) || !shouldForceAodForCall()) { + return chain.proceed(); + } + Enum replacement = overrideDozeState(target); + if (replacement == null) { + return chain.proceed(); + } + Object[] args = chain.getArgs().toArray(); + args[0] = replacement; + return chain.proceed(args); + }); + } } private void hookPluginPhoneStateReadOverride(ClassLoader classLoader) { @@ -164,7 +153,7 @@ final class AodCallVisibilityController { } private boolean shouldForceAodForCall() { - Context context = getSystemContext(); + Context context = SystemContextProvider.currentApplication(); if (context == null) { return false; } @@ -174,21 +163,26 @@ final class AodCallVisibilityController { return isInCallLikeState(context); } + @SuppressLint("MissingPermission") + @SuppressWarnings("deprecation") private boolean isInCallLikeState(Context context) { - try { - TelecomManager telecomManager = context.getSystemService(TelecomManager.class); - if (telecomManager != null && telecomManager.isInCall()) { - return true; + if (context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED) { + try { + TelecomManager telecomManager = context.getSystemService(TelecomManager.class); + if (telecomManager != null && telecomManager.isInCall()) { + return true; + } + } catch (Throwable ignored) { } - } catch (Throwable ignored) { - } - try { - TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); - if (telephonyManager != null - && telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) { - return true; + try { + TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); + if (telephonyManager != null + && telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) { + return true; + } + } catch (Throwable ignored) { } - } catch (Throwable ignored) { } try { AudioManager audioManager = context.getSystemService(AudioManager.class); @@ -202,17 +196,4 @@ final class AodCallVisibilityController { } } - private Context getSystemContext() { - try { - Class activityThreadClass = Class.forName("android.app.ActivityThread"); - Object app = ReflectionSupport.invokeStaticMethod( - activityThreadClass, - "currentApplication", - new Class[0] - ); - return app instanceof Context context ? context : null; - } catch (Throwable ignored) { - return null; - } - } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/BatteryStatusAccess.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/BatteryStatusAccess.java index 47efa83..8727217 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/BatteryStatusAccess.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/BatteryStatusAccess.java @@ -2,10 +2,10 @@ package se.ajpanton.statusbartweak.runtime.features.misc; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.os.BatteryManager; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; +import se.ajpanton.statusbartweak.runtime.settings.BroadcastReceiverSupport; final class BatteryStatusAccess { @@ -16,9 +16,9 @@ final class BatteryStatusAccess { if (context == null) { return null; } - Intent batteryIntent = context.registerReceiver( - null, - new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + Intent batteryIntent = BroadcastReceiverSupport.readExportedSticky( + context, + BroadcastReceiverSupport.filter(Intent.ACTION_BATTERY_CHANGED)); if (batteryIntent == null) { return null; } @@ -41,11 +41,6 @@ final class BatteryStatusAccess { return value instanceof Context context ? context : null; } - static int getIntField(Object target, String fieldName, int fallback) { - Object value = ReflectionSupport.getFieldValue(target, fieldName); - return value instanceof Integer integer ? integer : fallback; - } - static boolean getBooleanField(Object target, String fieldName, boolean fallback) { Object value = ReflectionSupport.getFieldValue(target, fieldName); return value instanceof Boolean bool ? bool : fallback; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/MiscFeature.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/MiscFeature.java index def396e..ba1b4a7 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/MiscFeature.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/misc/MiscFeature.java @@ -24,7 +24,7 @@ public final class MiscFeature implements RuntimeFeature { RuntimeContext context, XposedModuleInterface.PackageReadyParam param ) { - if (param == null || !"com.android.systemui".equals(param.getPackageName())) { + if (!RuntimeFeature.isSystemUiPackage(param)) { return; } installControllers(context, param.getClassLoader()); 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 a5763f0..0859fe6 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 @@ -3,9 +3,6 @@ package se.ajpanton.statusbartweak.runtime.features.notifications; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; -import android.os.Build; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -14,10 +11,12 @@ import java.util.Collections; import java.util.Set; import java.util.WeakHashMap; +import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; import se.ajpanton.statusbartweak.runtime.mode.SceneKey; +import se.ajpanton.statusbartweak.runtime.settings.BroadcastReceiverSupport; import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache; import se.ajpanton.statusbartweak.shell.settings.SbtDefaults; import se.ajpanton.statusbartweak.shell.settings.SbtSettings; @@ -121,23 +120,18 @@ final class StockUnlockedNotificationIconsController { if (receiverRegistered || context == null) { return; } - Context appContext = context.getApplicationContext(); - if (appContext == null) { - appContext = context; - } BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { refreshTrackedContainers(); } }; - IntentFilter filter = new IntentFilter(SbtSettings.ACTION_SETTINGS_CHANGED); - filter.addAction(Intent.ACTION_USER_UNLOCKED); - if (Build.VERSION.SDK_INT >= 33) { - appContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED); - } else { - appContext.registerReceiver(receiver, filter); - } + BroadcastReceiverSupport.registerExportedOnApplicationContext( + context, + receiver, + BroadcastReceiverSupport.filter( + SbtSettings.ACTION_SETTINGS_CHANGED, + Intent.ACTION_USER_UNLOCKED)); receiverRegistered = true; } @@ -213,19 +207,19 @@ final class StockUnlockedNotificationIconsController { } private void tryRecalculateIcons(View view) { - ReflectionSupport.invokeMethod(view, "calculateIconXTranslations", new Class[0]); - ReflectionSupport.invokeMethod(view, "applyIconStates", new Class[0]); - ReflectionSupport.invokeMethod(view, "onNotificationInfoUpdated", new Class[0]); + ReflectionSupport.invokeMethod(view, "calculateIconXTranslations"); + ReflectionSupport.invokeMethod(view, "applyIconStates"); + ReflectionSupport.invokeMethod(view, "onNotificationInfoUpdated"); } private boolean isFeatureEnabled(Context context) { SbtSettings settings = RuntimeSettingsCache.get(context); - return settings != null && !settings.clockEnabled; + return !settings.clockEnabled; } private int resolveUnlockedLimit(Context context) { SbtSettings settings = RuntimeSettingsCache.get(context); - if (settings == null || settings.clockEnabled) { + if (settings.clockEnabled) { return -1; } int value = settings.unlockedMaxIconsPerRow; @@ -253,7 +247,7 @@ final class StockUnlockedNotificationIconsController { boolean inUnlockedStatusBarIconArea = false; int depth = 0; while (parent instanceof View parentView && depth < 8) { - String idName = resolveIdName(parentView); + String idName = ViewIdNames.idName(parentView); if (idName.equals("notification_icon_area") || idName.equals("notification_icon_area_inner")) { inUnlockedStatusBarIconArea = true; @@ -275,14 +269,4 @@ final class StockUnlockedNotificationIconsController { return scene != null && scene.isUnlocked(); } - private String resolveIdName(View view) { - if (view == null || view.getId() == View.NO_ID) { - return ""; - } - try { - return view.getResources().getResourceEntryName(view.getId()); - } catch (Resources.NotFoundException ignored) { - return ""; - } - } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/notifications/StockUnlockedNotificationIconsFeature.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/notifications/StockUnlockedNotificationIconsFeature.java index 3924a6b..d5e37b0 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/notifications/StockUnlockedNotificationIconsFeature.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/notifications/StockUnlockedNotificationIconsFeature.java @@ -19,7 +19,7 @@ public final class StockUnlockedNotificationIconsFeature implements RuntimeFeatu @Override public void onPackageReady(RuntimeContext context, XposedModuleInterface.PackageReadyParam param) { - if (param == null || !"com.android.systemui".equals(param.getPackageName())) { + if (!RuntimeFeature.isSystemUiPackage(param)) { return; } installController(context, param.getClassLoader()); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/hooks/ReflectionSupport.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/hooks/ReflectionSupport.java index adab1a3..bdff494 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/hooks/ReflectionSupport.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/hooks/ReflectionSupport.java @@ -7,6 +7,8 @@ import java.lang.reflect.Method; * Small reflection helpers used by rebuilt runtime slices. */ public final class ReflectionSupport { + private static final Class[] NO_ARGS = new Class[0]; + private ReflectionSupport() { } @@ -120,6 +122,10 @@ public final class ReflectionSupport { } } + public static Object invokeMethod(Object target, String methodName) { + return invokeMethod(target, methodName, NO_ARGS); + } + public static Object invokeStaticMethod( Class type, String methodName, @@ -140,6 +146,10 @@ public final class ReflectionSupport { } } + public static Object invokeStaticMethod(Class type, String methodName) { + return invokeStaticMethod(type, methodName, NO_ARGS); + } + private static Field requireField(Class type, String fieldName) { if (fieldName == null || fieldName.isEmpty()) { throw new IllegalArgumentException("Field name is empty"); 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 new file mode 100644 index 0000000..f0517a0 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/DarkIconDispatcherGuard.java @@ -0,0 +1,141 @@ +package se.ajpanton.statusbartweak.runtime.layout; + +import android.graphics.Color; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.function.BooleanSupplier; + +import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; +import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; +import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; + +final class DarkIconDispatcherGuard { + private final RuntimeContext runtimeContext; + private final BooleanSupplier lockscreenSceneSupplier; + private final Runnable refreshAllRoots; + private final Set dispatchers = + Collections.newSetFromMap(new WeakHashMap<>()); + private final Map forcedStates = new WeakHashMap<>(); + + DarkIconDispatcherGuard( + RuntimeContext runtimeContext, + BooleanSupplier lockscreenSceneSupplier, + Runnable refreshAllRoots + ) { + this.runtimeContext = runtimeContext; + this.lockscreenSceneSupplier = lockscreenSceneSupplier; + this.refreshAllRoots = refreshAllRoots; + } + + void install(Class darkIconDispatcherClass) { + XposedHookSupport.hookAllConstructors( + runtimeContext.getFramework(), + darkIconDispatcherClass, + chain -> { + Object result = chain.proceed(); + track(chain.getThisObject()); + return result; + }); + XposedHookSupport.hookAllMethodsIfExists( + runtimeContext.getFramework(), + darkIconDispatcherClass, + "applyIconTint", + chain -> { + Object dispatcher = chain.getThisObject(); + track(dispatcher); + if (isLockscreenScene()) { + forceLightState(dispatcher); + } + Object result = chain.proceed(); + refreshAllRoots.run(); + return result; + }); + } + + void forceLightStateForAll() { + if (!isLockscreenScene()) { + return; + } + for (Object dispatcher : new ArrayList<>(dispatchers)) { + forceLightState(dispatcher); + ReflectionSupport.invokeMethod(dispatcher, "applyIconTint"); + } + } + + void restoreForcedStates() { + if (forcedStates.isEmpty()) { + return; + } + for (Map.Entry entry : new ArrayList<>(forcedStates.entrySet())) { + Object dispatcher = entry.getKey(); + State state = entry.getValue(); + restoreState(dispatcher, state); + ReflectionSupport.invokeMethod(dispatcher, "applyIconTint"); + } + forcedStates.clear(); + } + + private boolean isLockscreenScene() { + return lockscreenSceneSupplier.getAsBoolean(); + } + + private void track(Object dispatcher) { + if (dispatcher != null) { + dispatchers.add(dispatcher); + } + } + + private State captureState(Object dispatcher) { + return dispatcher != null + ? new State( + ReflectionSupport.getFieldValue(dispatcher, "mDarkIntensity"), + ReflectionSupport.getFieldValue(dispatcher, "mIconTint"), + ReflectionSupport.getFieldValue(dispatcher, "mTint"), + ReflectionSupport.getFieldValue(dispatcher, "mDarkAreas"), + ReflectionSupport.getFieldValue(dispatcher, "mTintAreas")) + : null; + } + + private void forceLightState(Object dispatcher) { + if (dispatcher == null || !isLockscreenScene()) { + return; + } + forcedStates.computeIfAbsent(dispatcher, this::captureState); + setLightState(dispatcher); + } + + private void setLightState(Object dispatcher) { + if (dispatcher == null) { + return; + } + ReflectionSupport.setFieldValue(dispatcher, "mDarkIntensity", 0f); + ReflectionSupport.setFieldValue(dispatcher, "mIconTint", Color.WHITE); + ReflectionSupport.setFieldValue(dispatcher, "mTint", Color.WHITE); + ReflectionSupport.setFieldValue(dispatcher, "mDarkAreas", new ArrayList<>()); + ReflectionSupport.setFieldValue(dispatcher, "mTintAreas", new ArrayList<>()); + } + + private void restoreState(Object dispatcher, State state) { + if (dispatcher == null || state == null) { + return; + } + ReflectionSupport.setFieldValue(dispatcher, "mDarkIntensity", state.darkIntensity); + ReflectionSupport.setFieldValue(dispatcher, "mIconTint", state.iconTint); + ReflectionSupport.setFieldValue(dispatcher, "mTint", state.tint); + ReflectionSupport.setFieldValue(dispatcher, "mDarkAreas", state.darkAreas); + ReflectionSupport.setFieldValue(dispatcher, "mTintAreas", state.tintAreas); + } + + private record State( + Object darkIntensity, + Object iconTint, + Object tint, + Object darkAreas, + Object tintAreas + ) { + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/LockscreenCardsNotificationRowFilter.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/LockscreenCardsNotificationRowFilter.java index 779f602..1b01ada 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/LockscreenCardsNotificationRowFilter.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/LockscreenCardsNotificationRowFilter.java @@ -15,6 +15,7 @@ import java.util.WeakHashMap; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import se.ajpanton.statusbartweak.runtime.NotificationKeyReflection; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle; import se.ajpanton.statusbartweak.runtime.mode.SystemNotificationDisplayStyleDetector; @@ -27,10 +28,6 @@ final class LockscreenCardsNotificationRowFilter { private final Map hiddenRows = new WeakHashMap<>(); - boolean apply(ViewGroup stack) { - return apply(stack, false); - } - boolean apply(ViewGroup stack, boolean allowAfterKeyguardUnlock) { if (stack == null) { return false; @@ -80,16 +77,12 @@ final class LockscreenCardsNotificationRowFilter { } } - boolean isActive(Context context) { - return isActive(context, false); - } - private boolean isActive(Context context, boolean allowAfterKeyguardUnlock) { if (context == null) { return false; } SbtSettings settings = RuntimeSettingsCache.get(context); - if (settings == null || !settings.clockEnabled) { + if (!settings.clockEnabled) { return false; } if (settings.appIconBlockedModes.isEmpty() && settings.appIconExceptionRules.isEmpty()) { @@ -127,7 +120,7 @@ final class LockscreenCardsNotificationRowFilter { } private boolean shouldHideRow(Context context, View row, boolean allowAfterKeyguardUnlock) { - Object entry = entryForRow(row); + Object entry = NotificationKeyReflection.entryForRow(row); if (shouldHideEntry(context, entry, allowAfterKeyguardUnlock)) { return true; } @@ -226,29 +219,20 @@ final class LockscreenCardsNotificationRowFilter { return view != null && view.getClass().getName().contains("ExpandableNotificationRow"); } - private Object entryForRow(View row) { - Object value = ReflectionSupport.invokeMethod(row, "getEntry", new Class[0]); - if (value != null) { - return value; - } - value = ReflectionSupport.getFieldValue(row, "mEntry"); - return value != null ? value : ReflectionSupport.getFieldValue(row, "entry"); - } - private String packageFor(Object target) { if (target == null) { return null; } - StatusBarNotification sbn = statusBarNotification(target); + StatusBarNotification sbn = NotificationReflection.statusBarNotification(target); if (sbn != null && AppIconRules.isValidPackageName(sbn.getPackageName())) { return sbn.getPackageName(); } - String key = notificationKey(target); - String fromKey = packageFromNotificationKey(key); + String key = NotificationKeyReflection.rowFilterKey(target); + String fromKey = NotificationKeyReflection.packageFromNotificationKey(key); if (AppIconRules.isValidPackageName(fromKey)) { return fromKey; } - Object packageName = ReflectionSupport.invokeMethod(target, "getPackageName", new Class[0]); + Object packageName = ReflectionSupport.invokeMethod(target, "getPackageName"); if (packageName instanceof String string && AppIconRules.isValidPackageName(string)) { return string; } @@ -261,14 +245,15 @@ final class LockscreenCardsNotificationRowFilter { return string; } if (target instanceof View view) { - Object entry = entryForRow(view); + Object entry = NotificationKeyReflection.entryForRow(view); String fromEntry = packageFor(entry); if (AppIconRules.isValidPackageName(fromEntry)) { return fromEntry; } CharSequence description = view.getContentDescription(); if (description != null) { - String fromDescription = packageFromNotificationKey(description.toString()); + String fromDescription = NotificationKeyReflection.packageFromNotificationKey( + description.toString()); if (AppIconRules.isValidPackageName(fromDescription)) { return fromDescription; } @@ -277,68 +262,19 @@ final class LockscreenCardsNotificationRowFilter { return null; } - private String notificationKey(Object target) { - Object key = ReflectionSupport.invokeMethod(target, "getKey", new Class[0]); - if (key instanceof String string && !string.isEmpty()) { - return string; - } - key = ReflectionSupport.invokeMethod(target, "getNotificationKey", new Class[0]); - if (key instanceof String string && !string.isEmpty()) { - return string; - } - key = ReflectionSupport.getFieldValue(target, "mKey"); - if (key instanceof String string && !string.isEmpty()) { - return string; - } - key = ReflectionSupport.getFieldValue(target, "key"); - return key instanceof String string && !string.isEmpty() ? string : null; - } - - private StatusBarNotification statusBarNotification(Object target) { - if (target instanceof StatusBarNotification sbn) { - return sbn; - } - Object value = ReflectionSupport.invokeMethod(target, "getSbn", new Class[0]); - if (value instanceof StatusBarNotification sbn) { - return sbn; - } - for (String field : new String[]{"mSbn", "sbn", "mStatusBarNotification", "statusBarNotification"}) { - value = ReflectionSupport.getFieldValue(target, field); - if (value instanceof StatusBarNotification sbn) { - return sbn; - } - } - return null; - } - - private String packageFromNotificationKey(String key) { - if (key == null) { - return null; - } - String normalized = key.trim(); - int suffix = normalized.indexOf('@'); - if (suffix >= 0) { - normalized = normalized.substring(0, suffix); - } - int slash = normalized.indexOf('/'); - if (slash >= 0) { - normalized = normalized.substring(0, slash); - } - return AppIconRules.isValidPackageName(normalized) ? normalized : null; - } - private String matchText(View row, Object entry) { StringBuilder out = new StringBuilder(); - append(out, notificationKey(entry)); - append(out, notificationKey(row)); - appendNotification(out, statusBarNotification(entry)); - appendNotification(out, statusBarNotification(row)); + append(out, NotificationKeyReflection.rowFilterKey(entry)); + append(out, NotificationKeyReflection.rowFilterKey(row)); + appendNotification(out, NotificationReflection.statusBarNotification(entry)); + appendNotification(out, NotificationReflection.statusBarNotification(row)); if (row != null) { append(out, row.getContentDescription()); } return out.toString(); } + @SuppressWarnings("deprecation") private void appendNotification(StringBuilder out, StatusBarNotification sbn) { if (sbn == null) { return; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/NotificationReflection.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/NotificationReflection.java new file mode 100644 index 0000000..3fd8c67 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/NotificationReflection.java @@ -0,0 +1,52 @@ +package se.ajpanton.statusbartweak.runtime.layout; + +import android.service.notification.StatusBarNotification; + +import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; + +final class NotificationReflection { + private static final String[] SBN_FIELD_NAMES = { + "mSbn", + "sbn", + "mStatusBarNotification", + "statusBarNotification" + }; + + private NotificationReflection() { + } + + static StatusBarNotification statusBarNotification(Object target) { + StatusBarNotification sbn = directStatusBarNotification(target); + if (sbn != null) { + return sbn; + } + return statusBarNotificationField(target); + } + + static StatusBarNotification statusBarNotificationEntry(Object target) { + StatusBarNotification sbn = directStatusBarNotification(target); + if (sbn != null) { + return sbn; + } + Object value = ReflectionSupport.invokeMethod(target, "getStatusBarNotification"); + return value instanceof StatusBarNotification found ? found : statusBarNotificationField(target); + } + + private static StatusBarNotification directStatusBarNotification(Object target) { + if (target instanceof StatusBarNotification sbn) { + return sbn; + } + Object value = ReflectionSupport.invokeMethod(target, "getSbn"); + return value instanceof StatusBarNotification sbn ? sbn : null; + } + + private static StatusBarNotification statusBarNotificationField(Object target) { + for (String field : SBN_FIELD_NAMES) { + Object value = ReflectionSupport.getFieldValue(target, field); + if (value instanceof StatusBarNotification sbn) { + return sbn; + } + } + return null; + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StatusIconModelTracker.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StatusIconModelTracker.java new file mode 100644 index 0000000..9fc5560 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StatusIconModelTracker.java @@ -0,0 +1,297 @@ +package se.ajpanton.statusbartweak.runtime.layout; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; +import java.util.WeakHashMap; + +import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; +import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; +import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; +import se.ajpanton.statusbartweak.runtime.mode.SceneKey; +import se.ajpanton.statusbartweak.runtime.render.StatusIconSlotVisibilityStore; + +final class StatusIconModelTracker { + private static final String[] CONTROLLER_CLASS_NAMES = { + "com.android.systemui.statusbar.phone.StatusBarIconControllerImpl", + "com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl" + }; + private static final String[] LIST_CLASS_NAMES = { + "com.android.systemui.statusbar.phone.StatusBarIconList", + "com.android.systemui.statusbar.phone.ui.StatusBarIconList" + }; + private static final String[] ICON_MANAGER_CLASS_NAMES = { + "com.android.systemui.statusbar.phone.StatusBarIconController$IconManager", + "com.android.systemui.statusbar.phone.ui.StatusBarIconController$IconManager", + "com.android.systemui.statusbar.phone.StatusBarIconControllerImpl$IconManager", + "com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl$IconManager" + }; + private static final String[] CONTROLLER_METHOD_NAMES = { + "setIcon", + "setIconVisibility", + "removeIcon", + "addIconGroup", + "removeIconGroup", + "refreshIconGroup" + }; + private static final String[] LIST_METHOD_NAMES = { + "setIcon", + "removeIcon" + }; + private static final String[] ICON_MANAGER_METHOD_NAMES = { + "onIconAdded", + "onSetIcon", + "onRemoveIcon", + "onIconExternal" + }; + private static final String[] MODEL_FIELD_NAMES = { + "mStatusBarIconList", + "mIconList", + "mIconGroups", + "mIconGroup", + "mSlots", + "mStatusIcons" + }; + + private final RuntimeContext runtimeContext; + private final Set knownObjects = + Collections.newSetFromMap(new WeakHashMap<>()); + + private boolean dirty = true; + private SceneKey lastScene; + private boolean lastLayoutEnabled; + + StatusIconModelTracker(RuntimeContext runtimeContext) { + this.runtimeContext = runtimeContext; + } + + void installHooks(ClassLoader classLoader) { + for (String className : CONTROLLER_CLASS_NAMES) { + hookClass(className, classLoader, CONTROLLER_METHOD_NAMES); + } + + for (String className : LIST_CLASS_NAMES) { + hookClass(className, classLoader, LIST_METHOD_NAMES); + } + + for (String className : ICON_MANAGER_CLASS_NAMES) { + hookClass(className, classLoader, ICON_MANAGER_METHOD_NAMES); + } + } + + private void hookClass(String className, ClassLoader classLoader, String[] methodNames) { + Class type = ReflectionSupport.findClassIfExists(className, classLoader); + if (type == null) { + return; + } + hookConstructors(type); + for (String methodName : methodNames) { + hookMethod(type, methodName); + } + } + + boolean updateActiveSlots(boolean layoutEnabled, SceneKey scene) { + if (!dirty + && layoutEnabled == lastLayoutEnabled + && Objects.equals(scene, lastScene)) { + return false; + } + lastLayoutEnabled = layoutEnabled; + lastScene = scene; + if (!layoutEnabled || scene == null || scene.isAod()) { + dirty = false; + return StatusIconSlotVisibilityStore.setActiveSlots(new ArrayList<>()); + } + if (knownObjects.isEmpty()) { + dirty = false; + return false; + } + LinkedHashSet mergedSlots = new LinkedHashSet<>(); + for (Object object : new ArrayList<>(knownObjects)) { + rememberObject(object); + Object iconList = ReflectionSupport.getFieldValue(object, "mStatusBarIconList"); + if (iconList == null) { + continue; + } + Set hiddenSlots = copyStringSet( + ReflectionSupport.getFieldValue(object, "mIconHideList")); + mergedSlots.addAll(activeSlots(iconList, hiddenSlots)); + } + boolean changed = StatusIconSlotVisibilityStore.setActiveSlots(new ArrayList<>(mergedSlots)); + dirty = false; + return changed; + } + + private void hookConstructors(Class type) { + try { + XposedHookSupport.hookAllConstructors( + runtimeContext.getFramework(), + type, + chain -> { + Object result = chain.proceed(); + rememberObject(chain.getThisObject()); + return result; + }); + } catch (Throwable throwable) { + runtimeContext.logWarning( + "Status icon model constructor hook failed for " + type.getName(), + throwable); + } + } + + private void hookMethod(Class type, String methodName) { + XposedHookSupport.hookAllMethodsIfExists( + runtimeContext.getFramework(), + type, + methodName, + chain -> { + rememberObject(chain.getThisObject()); + Object result = chain.proceed(); + rememberObject(chain.getThisObject()); + return result; + }); + } + + private void rememberObject(Object object) { + if (object == null) { + return; + } + knownObjects.add(object); + for (String fieldName : MODEL_FIELD_NAMES) { + rememberField(object, fieldName); + } + dirty = true; + } + + private void rememberField(Object target, String fieldName) { + Object value = ReflectionSupport.getFieldValue(target, fieldName); + if (value == null) { + return; + } + knownObjects.add(value); + if (value instanceof Iterable iterable) { + for (Object item : iterable) { + if (item != null) { + knownObjects.add(item); + } + } + } + } + + private LinkedHashSet activeSlots(Object iconList, Set hiddenSlots) { + LinkedHashSet result = new LinkedHashSet<>(); + addActiveSlots( + result, + ReflectionSupport.getFieldValue(iconList, "mSlots"), + hiddenSlots); + addActiveSlots( + result, + ReflectionSupport.getFieldValue(iconList, "mViewOnlySlots"), + hiddenSlots); + return result; + } + + private void addActiveSlots( + LinkedHashSet out, + Object slotsObj, + Set hiddenSlots + ) { + if (!(slotsObj instanceof Iterable slots)) { + return; + } + for (Object slotObj : slots) { + String name = firstNonEmpty( + reflectiveFieldString(slotObj, "mName"), + reflectiveFieldString(slotObj, "name"), + reflectiveString(slotObj, "getName")); + if (name.isEmpty() + || (hiddenSlots != null && hiddenSlots.contains(name))) { + continue; + } + Object holder = firstNonNull( + ReflectionSupport.getFieldValue(slotObj, "mHolder"), + ReflectionSupport.getFieldValue(slotObj, "holder")); + Object subSlots = ReflectionSupport.getFieldValue(slotObj, "mSubSlots"); + boolean hasSubSlots = subSlots instanceof Iterable iterable && iterable.iterator().hasNext(); + if ((holder != null && isHolderVisible(holder)) || hasSubSlots) { + out.add(name); + } + } + } + + private boolean isHolderVisible(Object holder) { + if (holder == null) { + return false; + } + Object methodVisible = ReflectionSupport.invokeMethod(holder, "isVisible"); + if (methodVisible instanceof Boolean visible) { + return visible; + } + Object visibleField = firstNonNull( + ReflectionSupport.getFieldValue(holder, "mVisible"), + ReflectionSupport.getFieldValue(holder, "visible")); + if (visibleField instanceof Boolean visible) { + return visible; + } + Object icon = firstNonNull( + ReflectionSupport.getFieldValue(holder, "mIcon"), + ReflectionSupport.getFieldValue(holder, "icon"), + ReflectionSupport.invokeMethod(holder, "getIcon")); + Object iconVisible = firstNonNull( + ReflectionSupport.getFieldValue(icon, "visible"), + ReflectionSupport.getFieldValue(icon, "mVisible")); + return !(iconVisible instanceof Boolean) || (Boolean) iconVisible; + } + + private Set copyStringSet(Object value) { + LinkedHashSet result = new LinkedHashSet<>(); + if (!(value instanceof Iterable iterable)) { + return result; + } + for (Object item : iterable) { + if (item != null) { + String text = String.valueOf(item); + if (!text.isEmpty()) { + result.add(text); + } + } + } + return result; + } + + private String reflectiveString(Object target, String methodName) { + Object value = ReflectionSupport.invokeMethod(target, methodName); + return value != null ? String.valueOf(value) : ""; + } + + private String reflectiveFieldString(Object target, String fieldName) { + Object value = ReflectionSupport.getFieldValue(target, fieldName); + return value != null ? String.valueOf(value) : ""; + } + + private Object firstNonNull(Object... values) { + if (values == null) { + return null; + } + for (Object value : values) { + if (value != null) { + return value; + } + } + return null; + } + + private String firstNonEmpty(String... values) { + if (values == null) { + return ""; + } + for (String value : values) { + if (value != null && !value.isEmpty() && !"null".equals(value)) { + return value; + } + } + return ""; + } +} 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 cc1996c..ab727de 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 @@ -1,12 +1,11 @@ package se.ajpanton.statusbartweak.runtime.layout; import android.app.KeyguardManager; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.drawable.Drawable; -import android.content.Context; -import android.content.res.Resources; import android.os.Looper; import android.os.PowerManager; import android.service.notification.StatusBarNotification; @@ -20,15 +19,18 @@ import java.lang.reflect.Field; import java.lang.reflect.Proxy; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.function.Function; -import java.util.function.Predicate; +import se.ajpanton.statusbartweak.runtime.NotificationKeyReflection; +import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; @@ -41,7 +43,6 @@ import se.ajpanton.statusbartweak.runtime.render.DebugBoundsOverlayController; import se.ajpanton.statusbartweak.runtime.render.LockscreenCardsNotificationIconSourceStore; import se.ajpanton.statusbartweak.runtime.render.LockscreenCardsNotificationStripRenderer; import se.ajpanton.statusbartweak.runtime.render.OwnedIconHostManager; -import se.ajpanton.statusbartweak.runtime.render.StatusIconSlotVisibilityStore; import se.ajpanton.statusbartweak.runtime.render.UnlockedIconSnapshotRenderController; import se.ajpanton.statusbartweak.runtime.render.UnlockedIconSnapshotRenderController.RenderResult; import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache; @@ -58,7 +59,17 @@ final class StockLayoutCanvasController { "com.samsung.android.uniform.widget.notification.NotificationIconsOnlyContainer"; private static final String CLASS_LEGACY_NOTIFICATION_ICON_AREA_CONTROLLER = "com.android.systemui.statusbar.phone.LegacyNotificationIconAreaControllerImpl"; - + private static final String[] NOTIF_COLLECTION_METHOD_NAMES = { + "getAllNotifs", + "getAllNotifications" + }; + private static final String[] NOTIF_COLLECTION_FIELD_NAMES = { + "mReadOnlyNotificationSet", + "mNotificationSet", + "mEntryMap", + "mEntries", + "entries" + }; private static final Set TARGET_ID_NAMES = Set.of( "left_clock_container", "notification_icon_area", @@ -77,6 +88,26 @@ final class StockLayoutCanvasController { "SecShelfNotificationIconContainer" ); + private static final Set LAYOUT_OFF_UNLOCKED_ONLY_IDS = Set.of( + "left_clock_container", + "notification_icon_area", + "notification_icon_area_inner", + "notificationIcons" + ); + + private static final Set LAYOUT_OFF_EXTRA_RECOVERABLE_IDS = Set.of( + "system_icon_area", + "statusIcons", + "battery" + ); + + private static final Set LAYOUT_OFF_RECOVERABLE_CLASS_SUBSTRINGS = Set.of( + "NotificationIconContainer", + "StatusIconContainer", + "BatteryMeterView", + "QSClockIndicatorView" + ); + private final RuntimeContext runtimeContext; private final Map hiddenViewStates = new WeakHashMap<>(); private final Map rootGeometries = new WeakHashMap<>(); @@ -113,16 +144,9 @@ final class StockLayoutCanvasController { Collections.newSetFromMap(new IdentityHashMap<>()); private final Set> hookedAodNotificationListenerClasses = Collections.newSetFromMap(new IdentityHashMap<>()); - private final Set darkIconDispatchers = - Collections.newSetFromMap(new WeakHashMap<>()); - private final Set knownStatusIconModelObjects = - Collections.newSetFromMap(new WeakHashMap<>()); - private final Map forcedDarkIconDispatcherStates = - new WeakHashMap<>(); private final Map splitShadeStatusBarCandidateByView = new WeakHashMap<>(); - private boolean statusIconModelDirty = true; - private SceneKey lastStatusIconModelScene; - private boolean lastStatusIconModelLayoutEnabled; + private final StatusIconModelTracker statusIconModelTracker; + private final DarkIconDispatcherGuard darkIconDispatcherGuard; private final Set capturingAodWrapperPackages = Collections.newSetFromMap(new WeakHashMap<>()); private final Set knownAodNotificationContainers = @@ -151,9 +175,14 @@ final class StockLayoutCanvasController { StockLayoutCanvasController(RuntimeContext runtimeContext) { this.runtimeContext = runtimeContext; + statusIconModelTracker = new StatusIconModelTracker(runtimeContext); + darkIconDispatcherGuard = new DarkIconDispatcherGuard( + runtimeContext, + this::isLockscreenScene, + this::scheduleUnlockedAppearanceRefreshForAllRoots); lockscreenCardsNotificationStripRenderer = new LockscreenCardsNotificationStripRenderer(); - unlockedIconRenderController = new UnlockedIconSnapshotRenderController(runtimeContext); + unlockedIconRenderController = new UnlockedIconSnapshotRenderController(); runtimeContext.getModeStateRepository().addSceneListener(this::scheduleSceneRefresh); } @@ -165,277 +194,12 @@ final class StockLayoutCanvasController { installCardsNotificationIconSourceHook(classLoader); installLockscreenCardsNotificationRowFilterHook(classLoader); installUnlockedAppearanceInvalidationHooks(classLoader); - installStatusIconModelHooks(classLoader); + statusIconModelTracker.installHooks(classLoader); installShadeHeaderAnimationGuardHook(classLoader); installViewRootHook(classLoader); hooksInstalled = true; } - private void installStatusIconModelHooks(ClassLoader classLoader) { - String[] controllerClassNames = { - "com.android.systemui.statusbar.phone.StatusBarIconControllerImpl", - "com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl" - }; - for (String className : controllerClassNames) { - Class controllerClass = ReflectionSupport.findClassIfExists(className, classLoader); - if (controllerClass == null) { - continue; - } - hookStatusIconModelConstructors(controllerClass); - hookStatusIconModelMethod(controllerClass, "setIcon"); - hookStatusIconModelMethod(controllerClass, "setIconVisibility"); - hookStatusIconModelMethod(controllerClass, "removeIcon"); - hookStatusIconModelMethod(controllerClass, "addIconGroup"); - hookStatusIconModelMethod(controllerClass, "removeIconGroup"); - hookStatusIconModelMethod(controllerClass, "refreshIconGroup"); - } - - String[] listClassNames = { - "com.android.systemui.statusbar.phone.StatusBarIconList", - "com.android.systemui.statusbar.phone.ui.StatusBarIconList" - }; - for (String className : listClassNames) { - Class listClass = ReflectionSupport.findClassIfExists(className, classLoader); - if (listClass == null) { - continue; - } - hookStatusIconModelConstructors(listClass); - hookStatusIconModelMethod(listClass, "setIcon"); - hookStatusIconModelMethod(listClass, "removeIcon"); - } - - String[] iconManagerClassNames = { - "com.android.systemui.statusbar.phone.StatusBarIconController$IconManager", - "com.android.systemui.statusbar.phone.ui.StatusBarIconController$IconManager", - "com.android.systemui.statusbar.phone.StatusBarIconControllerImpl$IconManager", - "com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl$IconManager" - }; - for (String className : iconManagerClassNames) { - Class managerClass = ReflectionSupport.findClassIfExists(className, classLoader); - if (managerClass == null) { - continue; - } - hookStatusIconModelConstructors(managerClass); - hookStatusIconModelMethod(managerClass, "onIconAdded"); - hookStatusIconModelMethod(managerClass, "onSetIcon"); - hookStatusIconModelMethod(managerClass, "onRemoveIcon"); - hookStatusIconModelMethod(managerClass, "onIconExternal"); - } - } - - private void hookStatusIconModelConstructors(Class type) { - try { - XposedHookSupport.hookAllConstructors( - runtimeContext.getFramework(), - type, - chain -> { - Object result = chain.proceed(); - rememberStatusIconModelObject(chain.getThisObject()); - return result; - }); - } catch (Throwable throwable) { - if (runtimeContext.isLogEnabled()) { - runtimeContext.logWarning( - "Status icon model constructor hook failed for " + type.getName(), - throwable); - } - } - } - - private void hookStatusIconModelMethod(Class type, String methodName) { - XposedHookSupport.hookAllMethodsIfExists( - runtimeContext.getFramework(), - type, - methodName, - chain -> { - rememberStatusIconModelObject(chain.getThisObject()); - Object result = chain.proceed(); - rememberStatusIconModelObject(chain.getThisObject()); - return result; - }); - } - - private void rememberStatusIconModelObject(Object object) { - if (object == null) { - return; - } - knownStatusIconModelObjects.add(object); - rememberStatusIconModelField(object, "mStatusBarIconList"); - rememberStatusIconModelField(object, "mIconList"); - rememberStatusIconModelField(object, "mIconGroups"); - rememberStatusIconModelField(object, "mIconGroup"); - rememberStatusIconModelField(object, "mSlots"); - rememberStatusIconModelField(object, "mStatusIcons"); - statusIconModelDirty = true; - } - - private void rememberStatusIconModelField(Object target, String fieldName) { - Object value = ReflectionSupport.getFieldValue(target, fieldName); - if (value == null) { - return; - } - knownStatusIconModelObjects.add(value); - if (value instanceof Iterable iterable) { - for (Object item : iterable) { - if (item != null) { - knownStatusIconModelObjects.add(item); - } - } - } - } - - private boolean updateActiveSystemIconSlotsForLayout(boolean layoutEnabled, SceneKey scene) { - if (!statusIconModelDirty - && layoutEnabled == lastStatusIconModelLayoutEnabled - && java.util.Objects.equals(scene, lastStatusIconModelScene)) { - return false; - } - lastStatusIconModelLayoutEnabled = layoutEnabled; - lastStatusIconModelScene = scene; - if (!layoutEnabled || scene == null || scene.isAod()) { - statusIconModelDirty = false; - return StatusIconSlotVisibilityStore.setActiveSlots(new ArrayList<>()); - } - if (knownStatusIconModelObjects.isEmpty()) { - statusIconModelDirty = false; - return false; - } - ArrayList mergedSlots = new ArrayList<>(); - for (Object object : new ArrayList<>(knownStatusIconModelObjects)) { - rememberStatusIconModelObject(object); - Object iconList = ReflectionSupport.getFieldValue(object, "mStatusBarIconList"); - if (iconList == null) { - continue; - } - ArrayList hiddenSlots = copyStringList( - ReflectionSupport.getFieldValue(object, "mIconHideList")); - for (String slot : activeSystemIconSlots(iconList, hiddenSlots)) { - if (!mergedSlots.contains(slot)) { - mergedSlots.add(slot); - } - } - } - boolean changed = StatusIconSlotVisibilityStore.setActiveSlots(mergedSlots); - statusIconModelDirty = false; - return changed; - } - - private ArrayList activeSystemIconSlots(Object iconList, ArrayList hiddenSlots) { - ArrayList result = new ArrayList<>(); - addActiveSystemIconSlots( - result, - ReflectionSupport.getFieldValue(iconList, "mSlots"), - hiddenSlots); - addActiveSystemIconSlots( - result, - ReflectionSupport.getFieldValue(iconList, "mViewOnlySlots"), - hiddenSlots); - return result; - } - - private void addActiveSystemIconSlots( - ArrayList out, - Object slotsObj, - ArrayList hiddenSlots - ) { - if (!(slotsObj instanceof Iterable slots)) { - return; - } - for (Object slotObj : slots) { - String name = firstNonEmpty( - reflectiveFieldString(slotObj, "mName"), - reflectiveFieldString(slotObj, "name"), - reflectiveString(slotObj, "getName")); - if (name.isEmpty() - || out.contains(name) - || (hiddenSlots != null && hiddenSlots.contains(name))) { - continue; - } - Object holder = firstNonNull( - ReflectionSupport.getFieldValue(slotObj, "mHolder"), - ReflectionSupport.getFieldValue(slotObj, "holder")); - Object subSlots = ReflectionSupport.getFieldValue(slotObj, "mSubSlots"); - boolean hasSubSlots = subSlots instanceof Iterable iterable && iterable.iterator().hasNext(); - if ((holder != null && isStatusIconHolderVisible(holder)) || hasSubSlots) { - out.add(name); - } - } - } - - private boolean isStatusIconHolderVisible(Object holder) { - if (holder == null) { - return false; - } - Object methodVisible = ReflectionSupport.invokeMethod(holder, "isVisible", new Class[0]); - if (methodVisible instanceof Boolean visible) { - return visible; - } - Object visibleField = firstNonNull( - ReflectionSupport.getFieldValue(holder, "mVisible"), - ReflectionSupport.getFieldValue(holder, "visible")); - if (visibleField instanceof Boolean visible) { - return visible; - } - Object icon = firstNonNull( - ReflectionSupport.getFieldValue(holder, "mIcon"), - ReflectionSupport.getFieldValue(holder, "icon"), - ReflectionSupport.invokeMethod(holder, "getIcon", new Class[0])); - Object iconVisible = firstNonNull( - ReflectionSupport.getFieldValue(icon, "visible"), - ReflectionSupport.getFieldValue(icon, "mVisible")); - return !(iconVisible instanceof Boolean) || (Boolean) iconVisible; - } - - private ArrayList copyStringList(Object value) { - ArrayList result = new ArrayList<>(); - if (!(value instanceof Iterable iterable)) { - return result; - } - for (Object item : iterable) { - if (item != null) { - String text = String.valueOf(item); - if (!text.isEmpty() && !result.contains(text)) { - result.add(text); - } - } - } - return result; - } - - private String reflectiveString(Object target, String methodName) { - Object value = ReflectionSupport.invokeMethod(target, methodName, new Class[0]); - return value != null ? String.valueOf(value) : ""; - } - - private String reflectiveFieldString(Object target, String fieldName) { - Object value = ReflectionSupport.getFieldValue(target, fieldName); - return value != null ? String.valueOf(value) : ""; - } - - private Object firstNonNull(Object... values) { - if (values == null) { - return null; - } - for (Object value : values) { - if (value != null) { - return value; - } - } - return null; - } - - private String firstNonEmpty(String... values) { - if (values == null) { - return ""; - } - for (String value : values) { - if (value != null && !value.isEmpty() && !"null".equals(value)) { - return value; - } - } - return ""; - } - private void installShadeHeaderAnimationGuardHook(ClassLoader classLoader) { Class viewClass = ReflectionSupport.requireClass("android.view.View", classLoader); XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), viewClass, "setAlpha", chain -> { @@ -520,7 +284,7 @@ final class StockLayoutCanvasController { chain -> { Object result = chain.proceed(); if (chain.getThisObject() instanceof ViewGroup stack) { - applyLockscreenCardsNotificationRowFilter(stack); + applyLockscreenCardsNotificationRowFilter(stack, false); } return result; }); @@ -531,7 +295,7 @@ final class StockLayoutCanvasController { chain -> { Object result = chain.proceed(); if (chain.getThisObject() instanceof ViewGroup stack) { - applyLockscreenCardsNotificationRowFilter(stack); + applyLockscreenCardsNotificationRowFilter(stack, false); } return result; }); @@ -544,7 +308,7 @@ final class StockLayoutCanvasController { "com.android.systemui.statusbar.phone.DarkIconDispatcherImpl", classLoader); if (darkIconDispatcherClass != null) { - installDarkIconDispatcherLockscreenGuard(darkIconDispatcherClass); + darkIconDispatcherGuard.install(darkIconDispatcherClass); } Class statusBarIconViewClass = ReflectionSupport.findClassIfExists( @@ -646,104 +410,6 @@ final class StockLayoutCanvasController { hookStatusChipContentInvalidation(ImageView.class, "setImageLevel"); } - private void installDarkIconDispatcherLockscreenGuard(Class darkIconDispatcherClass) { - XposedHookSupport.hookAllConstructors( - runtimeContext.getFramework(), - darkIconDispatcherClass, - chain -> { - Object result = chain.proceed(); - trackDarkIconDispatcher(chain.getThisObject()); - return result; - }); - XposedHookSupport.hookAllMethodsIfExists( - runtimeContext.getFramework(), - darkIconDispatcherClass, - "applyIconTint", - chain -> { - Object dispatcher = chain.getThisObject(); - trackDarkIconDispatcher(dispatcher); - if (isLockscreenScene()) { - forceDarkIconDispatcherLightState(dispatcher); - } - Object result = chain.proceed(); - scheduleUnlockedAppearanceRefreshForAllRoots(); - return result; - }); - } - - private void trackDarkIconDispatcher(Object dispatcher) { - if (dispatcher != null) { - darkIconDispatchers.add(dispatcher); - } - } - - private DarkIconDispatcherState captureDarkIconDispatcherState(Object dispatcher) { - return dispatcher != null - ? new DarkIconDispatcherState( - ReflectionSupport.getFieldValue(dispatcher, "mDarkIntensity"), - ReflectionSupport.getFieldValue(dispatcher, "mIconTint"), - ReflectionSupport.getFieldValue(dispatcher, "mTint"), - ReflectionSupport.getFieldValue(dispatcher, "mDarkAreas"), - ReflectionSupport.getFieldValue(dispatcher, "mTintAreas")) - : null; - } - - private void setDarkIconDispatcherLightState(Object dispatcher) { - if (dispatcher == null) { - return; - } - ReflectionSupport.setFieldValue(dispatcher, "mDarkIntensity", 0f); - ReflectionSupport.setFieldValue(dispatcher, "mIconTint", Color.WHITE); - ReflectionSupport.setFieldValue(dispatcher, "mTint", Color.WHITE); - ReflectionSupport.setFieldValue(dispatcher, "mDarkAreas", new ArrayList<>()); - ReflectionSupport.setFieldValue(dispatcher, "mTintAreas", new ArrayList<>()); - } - - private void forceDarkIconDispatcherLightState(Object dispatcher) { - if (dispatcher == null || !isLockscreenScene()) { - return; - } - forcedDarkIconDispatcherStates.computeIfAbsent( - dispatcher, - this::captureDarkIconDispatcherState); - setDarkIconDispatcherLightState(dispatcher); - } - - private void forceDarkIconDispatchersLightState() { - if (!isLockscreenScene()) { - return; - } - for (Object dispatcher : new ArrayList<>(darkIconDispatchers)) { - forceDarkIconDispatcherLightState(dispatcher); - ReflectionSupport.invokeMethod(dispatcher, "applyIconTint", new Class[0]); - } - } - - private void restoreForcedDarkIconDispatcherStates() { - if (forcedDarkIconDispatcherStates.isEmpty()) { - return; - } - for (Map.Entry entry : - new ArrayList<>(forcedDarkIconDispatcherStates.entrySet())) { - Object dispatcher = entry.getKey(); - DarkIconDispatcherState state = entry.getValue(); - restoreDarkIconDispatcherState(dispatcher, state); - ReflectionSupport.invokeMethod(dispatcher, "applyIconTint", new Class[0]); - } - forcedDarkIconDispatcherStates.clear(); - } - - private void restoreDarkIconDispatcherState(Object dispatcher, DarkIconDispatcherState state) { - if (dispatcher == null || state == null) { - return; - } - ReflectionSupport.setFieldValue(dispatcher, "mDarkIntensity", state.darkIntensity); - ReflectionSupport.setFieldValue(dispatcher, "mIconTint", state.iconTint); - ReflectionSupport.setFieldValue(dispatcher, "mTint", state.tint); - ReflectionSupport.setFieldValue(dispatcher, "mDarkAreas", state.darkAreas); - ReflectionSupport.setFieldValue(dispatcher, "mTintAreas", state.tintAreas); - } - private void hookViewAppearanceInvalidation(Class type, String methodName) { XposedHookSupport.hookAllMethodsIfExists( runtimeContext.getFramework(), @@ -889,7 +555,7 @@ final class StockLayoutCanvasController { ArrayList captured = new ArrayList<>(); Function wrapped = entryObj -> { - String key = LockscreenCardsNotificationIconSourceStore.keyForEntry(entryObj); + String key = NotificationKeyReflection.keyForEntry(entryObj); if (!runtimeContext.getModeStateRepository().isSystemUiShadeLocked() && lockscreenCardsNotificationRowFilter.shouldHideEntry( container.getContext(), @@ -902,7 +568,7 @@ final class StockLayoutCanvasController { } if (result instanceof View view && !isStockOverflowDotView(view)) { if (key == null) { - key = LockscreenCardsNotificationIconSourceStore.keyForIconView(view); + key = NotificationKeyReflection.keyForIconView(view); } captured.add(LockscreenCardsNotificationIconSourceStore.source(view, key)); } @@ -1058,20 +724,14 @@ final class StockLayoutCanvasController { if (collection == null) { return notifications; } - for (String methodName : new String[]{"getAllNotifs", "getAllNotifications"}) { - Object value = ReflectionSupport.invokeMethod(collection, methodName, new Class[0]); + for (String methodName : NOTIF_COLLECTION_METHOD_NAMES) { + Object value = ReflectionSupport.invokeMethod(collection, methodName); appendNotificationsFromNotifCollectionValue(notifications, value); if (!notifications.isEmpty()) { return notifications; } } - for (String fieldName : new String[]{ - "mReadOnlyNotificationSet", - "mNotificationSet", - "mEntryMap", - "mEntries", - "entries" - }) { + for (String fieldName : NOTIF_COLLECTION_FIELD_NAMES) { Object value = ReflectionSupport.getFieldValue(collection, fieldName); appendNotificationsFromNotifCollectionValue(notifications, value); if (!notifications.isEmpty()) { @@ -1120,34 +780,13 @@ final class StockLayoutCanvasController { ArrayList notifications, Object entry ) { - StatusBarNotification sbn = statusBarNotificationFromNotifEntry(entry); + StatusBarNotification sbn = NotificationReflection.statusBarNotificationEntry(entry); if (sbn == null || !AppIconRules.isValidPackageName(sbn.getPackageName())) { return; } notifications.add(sbn); } - private StatusBarNotification statusBarNotificationFromNotifEntry(Object entry) { - if (entry instanceof StatusBarNotification sbn) { - return sbn; - } - Object value = ReflectionSupport.invokeMethod(entry, "getSbn", new Class[0]); - if (value instanceof StatusBarNotification sbn) { - return sbn; - } - value = ReflectionSupport.invokeMethod(entry, "getStatusBarNotification", new Class[0]); - if (value instanceof StatusBarNotification sbn) { - return sbn; - } - for (String fieldName : new String[]{"mSbn", "sbn", "mStatusBarNotification", "statusBarNotification"}) { - value = ReflectionSupport.getFieldValue(entry, fieldName); - if (value instanceof StatusBarNotification sbn) { - return sbn; - } - } - return null; - } - private void installViewRootHook(ClassLoader classLoader) { Class viewRootClass = ReflectionSupport.requireClass("android.view.ViewRootImpl", classLoader); XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), viewRootClass, "performDraw", chain -> { @@ -1392,12 +1031,24 @@ final class StockLayoutCanvasController { break; } } - while (!staleViews.isEmpty()) { - trackedStatusChipSources.remove(staleViews.removeFirst()); - } + removeStaleViews(trackedStatusChipSources, staleViews); return result; } + private void removeStaleViews(Collection views, ArrayDeque staleViews) { + while (!staleViews.isEmpty()) { + views.remove(staleViews.removeFirst()); + } + } + + private void removeStaleAodPluginViews(ArrayDeque staleViews) { + while (!staleViews.isEmpty()) { + View stale = staleViews.removeFirst(); + confirmedAodPluginViews.remove(stale); + aodPluginVisualAlphaByView.remove(stale); + } + } + private boolean isStatusChipTreeMutation(View parent, View child) { if (parent == null || !isLayoutEnabled(parent.getContext()) @@ -1543,11 +1194,11 @@ final class StockLayoutCanvasController { return; } if (previous.isLockscreen() && !current.isLockscreen()) { - restoreForcedDarkIconDispatcherStates(); + darkIconDispatcherGuard.restoreForcedStates(); } boolean lockscreenEntry = current.isLockscreen(); if (lockscreenEntry) { - forceDarkIconDispatchersLightState(); + darkIconDispatcherGuard.forceLightStateForAll(); } ArrayList roots = new ArrayList<>(rootGeometries.keySet()); for (View root : new ArrayList<>(unlockedHostsByRoot.keySet())) { @@ -1718,14 +1369,16 @@ final class StockLayoutCanvasController { runtimeContext.getModeStateRepository().setLayoutEnabled(layoutEnabled); if (!layoutEnabled) { updateActiveScene(root, context); - updateActiveSystemIconSlotsForLayout(false, runtimeContext.getModeStateRepository().getActiveScene()); + statusIconModelTracker.updateActiveSlots( + false, + runtimeContext.getModeStateRepository().getActiveScene()); applyLayoutOffRoot(root, settings, statusBarRoot, runtimeContext.getModeStateRepository().getActiveScene()); return; } installPendingPluginClassLoaderHooks(); layoutOffCleanedRoots.remove(root); updateActiveScene(root, context); - if (updateActiveSystemIconSlotsForLayout( + if (statusIconModelTracker.updateActiveSlots( true, runtimeContext.getModeStateRepository().getActiveScene())) { unlockedIconRenderController.clearRoot(root); @@ -1989,9 +1642,7 @@ final class StockLayoutCanvasController { } hideStatusChipSurface(root, view); } - while (!staleViews.isEmpty()) { - trackedStatusChipSources.remove(staleViews.removeFirst()); - } + removeStaleViews(trackedStatusChipSources, staleViews); } private void trackAndHideStatusChipSources(View root) { @@ -2004,26 +1655,6 @@ final class StockLayoutCanvasController { }); } - private void hideTrackedUnlockedStockSurfaces(View root) { - if (trackedUnlockedStockSurfaces.isEmpty()) { - return; - } - ArrayDeque staleViews = new ArrayDeque<>(); - for (View view : trackedUnlockedStockSurfaces) { - if (view == null - || !view.isAttachedToWindow() - || !isDescendantOf(view, root) - || ownedIconHostManager.isOwnedHost(view)) { - staleViews.add(view); - continue; - } - hideView(view, true); - } - while (!staleViews.isEmpty()) { - trackedUnlockedStockSurfaces.remove(staleViews.removeFirst()); - } - } - private boolean hasTrackedUnlockedStockSurfaces(View root) { if (trackedUnlockedStockSurfaces.isEmpty()) { return false; @@ -2048,9 +1679,7 @@ final class StockLayoutCanvasController { } hideView(view, keepConfirmedAodPluginViewLaidOut(view)); } - while (!staleViews.isEmpty()) { - confirmedAodPluginViews.remove(staleViews.removeFirst()); - } + removeStaleAodPluginViews(staleViews); } private boolean hasVisibleConfirmedAodPluginView() { @@ -2152,11 +1781,7 @@ final class StockLayoutCanvasController { positiveAlpha = Math.min(positiveAlpha, alpha); } } - while (!staleViews.isEmpty()) { - View stale = staleViews.removeFirst(); - confirmedAodPluginViews.remove(stale); - aodPluginVisualAlphaByView.remove(stale); - } + removeStaleAodPluginViews(staleViews); if (!found) { return 1f; } @@ -2306,9 +1931,7 @@ final class StockLayoutCanvasController { } staleViews.add(view); } - while (!staleViews.isEmpty()) { - originalAodNotificationWidgetLayoutWidths.remove(staleViews.removeFirst()); - } + removeStaleViews(originalAodNotificationWidgetLayoutWidths.keySet(), staleViews); } private AodLayoutBounds carrierlessAodIconOnlyAreaLayoutOverride( @@ -2323,7 +1946,7 @@ final class StockLayoutCanvasController { } if (!knownAodNotificationIconOnlyAreas.contains(view)) { if (!(view instanceof android.widget.LinearLayout) - || !"notification_icononly_area".equals(resolveIdName(view))) { + || !"notification_icononly_area".equals(ViewIdNames.idName(view))) { return null; } knownAodNotificationIconOnlyAreas.add(view); @@ -2333,8 +1956,7 @@ final class StockLayoutCanvasController { } SceneKey scene = runtimeContext.getModeStateRepository().getActiveScene(); SbtSettings settings = RuntimeSettingsCache.get(view.getContext()); - if (settings == null - || scene == null + if (scene == null || !scene.isAod() || scene.notificationDisplayStyle() != NotificationDisplayStyle.ICONS || !settings.clockEnabled @@ -2359,7 +1981,7 @@ final class StockLayoutCanvasController { while (!queue.isEmpty()) { View view = queue.removeFirst(); if (view instanceof ViewGroup group - && "keyguard_icononly_container_view".equals(resolveIdName(group))) { + && "keyguard_icononly_container_view".equals(ViewIdNames.idName(group))) { return group; } if (view instanceof ViewGroup group) { @@ -2377,7 +1999,7 @@ final class StockLayoutCanvasController { private View commonNotificationWidgetFor(View view) { View current = view; while (current != null) { - if ("common_notification_widget".equals(resolveIdName(current))) { + if ("common_notification_widget".equals(ViewIdNames.idName(current))) { return current; } Object parent = current.getParent(); @@ -2389,7 +2011,7 @@ final class StockLayoutCanvasController { private View notificationIconOnlyAreaFor(View view) { View current = view; while (current != null) { - if ("notification_icononly_area".equals(resolveIdName(current))) { + if ("notification_icononly_area".equals(ViewIdNames.idName(current))) { return current; } Object parent = current.getParent(); @@ -2433,7 +2055,7 @@ final class StockLayoutCanvasController { if (isLockedStatusBarIconSurface(view)) { rememberLockedCarrierTextSource(view); trackedLockedStatusBarIconSurfaces.add(view); - hideView(view, keepLockedSurfaceLaidOut(view)); + hideView(view, isCarrierView(view)); } }); } @@ -2463,12 +2085,10 @@ final class StockLayoutCanvasController { } if (root == null || isDescendantOf(view, root)) { foundInRoot = true; - hideView(view, keepLockedSurfaceLaidOut(view)); + hideView(view, isCarrierView(view)); } } - while (!staleViews.isEmpty()) { - trackedLockedStatusBarIconSurfaces.remove(staleViews.removeFirst()); - } + removeStaleViews(trackedLockedStatusBarIconSurfaces, staleViews); return foundInRoot; } @@ -2583,7 +2203,7 @@ final class StockLayoutCanvasController { if (cached != null) { return cached; } - boolean candidate = "split_shade_status_bar".equals(resolveIdName(view)); + boolean candidate = "split_shade_status_bar".equals(ViewIdNames.idName(view)); splitShadeStatusBarCandidateByView.put(view, candidate); return candidate; } @@ -2631,35 +2251,14 @@ final class StockLayoutCanvasController { return false; } - private void hideTrackedViews( - Set trackedViews, - Predicate keepTracked, - boolean keepLaidOut - ) { - if (trackedViews.isEmpty()) { - return; - } - ArrayDeque staleViews = new ArrayDeque<>(); - for (View view : trackedViews) { - if (view == null || !view.isAttachedToWindow() || !keepTracked.test(view)) { - staleViews.add(view); - continue; - } - hideView(view, keepLaidOut); - } - while (!staleViews.isEmpty()) { - trackedViews.remove(staleViews.removeFirst()); - } - } - private boolean isLockedStatusBarIconSurface(View view) { if (view == null || ownedIconHostManager.isOwnedHost(view)) { return false; } - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); String viewClassName = view.getClass().getName(); - boolean carrierSurface = idName.toLowerCase(java.util.Locale.ROOT).contains("carrier") - || viewClassName.toLowerCase(java.util.Locale.ROOT).contains("carrier"); + boolean carrierSurface = idName.toLowerCase(Locale.ROOT).contains("carrier") + || viewClassName.toLowerCase(Locale.ROOT).contains("carrier"); boolean shelfSurface = isCardsNotificationIconSurface(view); boolean shelfBackgroundSurface = isCardsNotificationShelfBackgroundSurface(view); boolean shadeIconOnlySurface = "keyguard_icononly_container_view".equals(idName); @@ -2679,7 +2278,7 @@ final class StockLayoutCanvasController { Object current = view; while (current instanceof View currentView) { String className = currentView.getClass().getName(); - if ("split_shade_status_bar".equals(resolveIdName(currentView))) { + if ("split_shade_status_bar".equals(ViewIdNames.idName(currentView))) { return false; } if (className.contains("KeyguardStatusBarView") @@ -2704,28 +2303,16 @@ final class StockLayoutCanvasController { return false; } - private boolean keepLockedSurfaceLaidOut(View view) { - return isCarrierView(view); - } - private boolean isCardsNotificationIconSurface(View view) { if (view == null) { return false; } - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); String className = view.getClass().getName(); return className.contains("SecShelfNotificationIconContainer") || "keyguard_icononly_container_view".equals(idName); } - private boolean isCardsNotificationShelfSurface(View view) { - if (view == null) { - return false; - } - String className = view.getClass().getName(); - return className.contains("SecShelfNotificationIconContainer"); - } - private boolean isCardsNotificationShelfBackgroundSurface(View view) { if (view == null) { return false; @@ -2759,8 +2346,8 @@ final class StockLayoutCanvasController { if (view == null) { return false; } - String idName = resolveIdName(view).toLowerCase(java.util.Locale.ROOT); - String className = view.getClass().getName().toLowerCase(java.util.Locale.ROOT); + String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT); + String className = view.getClass().getName().toLowerCase(Locale.ROOT); return idName.contains("overflow") || idName.contains("dot") || className.contains("overflow") @@ -2771,8 +2358,8 @@ final class StockLayoutCanvasController { if (view == null) { return false; } - String idName = resolveIdName(view).toLowerCase(java.util.Locale.ROOT); - String className = view.getClass().getName().toLowerCase(java.util.Locale.ROOT); + String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT); + String className = view.getClass().getName().toLowerCase(Locale.ROOT); return idName.contains("carrier") || className.contains("carrier"); } @@ -2799,7 +2386,7 @@ final class StockLayoutCanvasController { if (lockedCarrierTextSource() != null || !(view instanceof TextView textView)) { return; } - String idName = resolveIdName(textView); + String idName = ViewIdNames.idName(textView); String className = textView.getClass().getName(); if ("keyguard_carrier_text".equals(idName) || className.contains("CarrierText")) { lockedCarrierTextSource = new WeakReference<>(textView); @@ -2827,23 +2414,18 @@ final class StockLayoutCanvasController { if (confirmedAodPluginViews.contains(view)) { return true; } - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); if (TARGET_ID_NAMES.contains(idName)) { return true; } String className = view.getClass().getName(); - if (className.toLowerCase(java.util.Locale.ROOT).contains("carrier")) { + if (className.toLowerCase(Locale.ROOT).contains("carrier")) { return true; } if (className.contains("NotificationShelf")) { return true; } - for (String token : TARGET_CLASS_SUBSTRINGS) { - if (className.contains(token)) { - return true; - } - } - return false; + return containsAny(className, TARGET_CLASS_SUBSTRINGS); } private boolean isStatusChipSource(View view) { @@ -2886,11 +2468,11 @@ final class StockLayoutCanvasController { if (view == null || ownedIconHostManager.isOwnedHost(view)) { return false; } - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); if (!"ongoing_activity_capsule".equals(idName)) { return false; } - String className = view.getClass().getName().toLowerCase(java.util.Locale.ROOT); + String className = view.getClass().getName().toLowerCase(Locale.ROOT); return className.contains("touchinterceptframelayout"); } @@ -2947,8 +2529,8 @@ final class StockLayoutCanvasController { } private boolean isStatusChipCandidate(View view) { - String className = view.getClass().getName().toLowerCase(java.util.Locale.ROOT); - String idName = resolveIdName(view).toLowerCase(java.util.Locale.ROOT); + String className = view.getClass().getName().toLowerCase(Locale.ROOT); + String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT); if (className.contains("privacy") || idName.contains("privacy")) { return false; } @@ -2970,7 +2552,7 @@ final class StockLayoutCanvasController { } markRootGeometryBeforeDraw(root); unlockedIconRenderController.clearAll(); - ownedIconHostManager.removeUnlockedHosts(root); + ownedIconHostManager.removeOwnedHosts(root); unlockedHostsByRoot.remove(root); pendingUnlockedAppearanceRefreshRoots.remove(root); dirtyUnlockedLayoutRoots.remove(root); @@ -3047,7 +2629,7 @@ final class StockLayoutCanvasController { if (view == null || ownedIconHostManager.isOwnedHost(view)) { continue; } - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); String className = view.getClass().getName(); if (movingAodStatusSource == null && isMovingAodStatusbarSource(view)) { movingAodStatusSource = view; @@ -3101,7 +2683,7 @@ final class StockLayoutCanvasController { if (result.isEmpty() && systemIcons instanceof ViewGroup group) { for (int i = 0; i < group.getChildCount(); i++) { View child = group.getChildAt(i); - String idName = resolveIdName(child); + String idName = ViewIdNames.idName(child); String className = child != null ? child.getClass().getName() : ""; if ("statusIcons".equals(idName) || "battery".equals(idName) @@ -3471,7 +3053,7 @@ final class StockLayoutCanvasController { if (view == null) { return false; } - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); return "common_notification_widget".equals(idName) || "status_bar_area".equals(idName) || "notification_icononly_area".equals(idName) @@ -3479,12 +3061,12 @@ final class StockLayoutCanvasController { } private boolean isKeyguardIconOnlyContainerView(View view) { - if (view == null || !"keyguard_icononly_container_view".equals(resolveIdName(view))) { + if (view == null || !"keyguard_icononly_container_view".equals(ViewIdNames.idName(view))) { return false; } Object parent = view.getParent(); while (parent instanceof View parentView) { - String idName = resolveIdName(parentView); + String idName = ViewIdNames.idName(parentView); if ("notification_icononly_container".equals(idName) || "notification_icononly_area".equals(idName) || "status_bar_area".equals(idName) @@ -3500,7 +3082,7 @@ final class StockLayoutCanvasController { if (view == null) { return false; } - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); return "common_statusbar_battery_container".equals(idName) || "common_battery_statusbar_container".equals(idName) || "common_statusbar_battery_container_internal".equals(idName); @@ -3508,11 +3090,7 @@ final class StockLayoutCanvasController { private boolean isMovingAodStatusbarSource(View view) { return view != null - && "common_statusbar_battery_container_internal".equals(resolveIdName(view)); - } - - private boolean hideView(View view) { - return hideView(view, false); + && "common_statusbar_battery_container_internal".equals(ViewIdNames.idName(view)); } private boolean hideView(View view, boolean keepLaidOut) { @@ -3549,12 +3127,10 @@ final class StockLayoutCanvasController { } private boolean isRecoverableStockStatusBarSurface(View view) { + String idName = ViewIdNames.idName(view); return trackedUnlockedStockSurfaces.contains(view) || trackedLockedStatusBarIconSurfaces.contains(view) - || "left_clock_container".equals(resolveIdName(view)) - || "notification_icon_area".equals(resolveIdName(view)) - || "notification_icon_area_inner".equals(resolveIdName(view)) - || "notificationIcons".equals(resolveIdName(view)); + || LAYOUT_OFF_UNLOCKED_ONLY_IDS.contains(idName); } private boolean restoreView(View view) { @@ -3634,13 +3210,13 @@ final class StockLayoutCanvasController { unlockedIconRenderController.clearRoot(root); removeLockscreenCardsNotificationHost(root); removeAodCardsNotificationHost(root); - ownedIconHostManager.removeUnlockedHosts(root); + ownedIconHostManager.removeOwnedHosts(root); return; } rootGeometries.remove(root); unlockedIconRenderController.clearAll(); removeAodCardsNotificationHost(root); - ownedIconHostManager.removeUnlockedHosts(root); + ownedIconHostManager.removeOwnedHosts(root); } private void restoreLayoutOffStockSurfaces(View root, SceneKey activeScene) { @@ -3652,8 +3228,11 @@ final class StockLayoutCanvasController { if (!isLayoutOffRecoverableStockSurface(view)) { return; } + String idName = ViewIdNames.idName(view); + int beforeVisibility = view.getVisibility(); + float beforeAlpha = view.getAlpha(); restoreView(view); - if (!unlockedScene && isUnlockedOnlyStatusbarSurface(view)) { + if (!unlockedScene && LAYOUT_OFF_UNLOCKED_ONLY_IDS.contains(idName)) { if (view.getAlpha() != 0f) { view.setAlpha(0f); } @@ -3665,43 +3244,56 @@ final class StockLayoutCanvasController { if (unlockedScene && view.getVisibility() != View.VISIBLE) { view.setVisibility(View.VISIBLE); } + if (shouldPreserveUnlockedStockAlpha(idName, beforeVisibility, beforeAlpha)) { + if (view.getAlpha() != beforeAlpha) { + view.setAlpha(beforeAlpha); + } + return; + } if (unlockedScene && view.getAlpha() <= 0f) { view.setAlpha(1f); } }); } - private boolean isUnlockedOnlyStatusbarSurface(View view) { - String idName = resolveIdName(view); - return "left_clock_container".equals(idName) - || "notification_icon_area".equals(idName) - || "notification_icon_area_inner".equals(idName) - || "notificationIcons".equals(idName); + private boolean shouldPreserveUnlockedStockAlpha( + String idName, + int beforeVisibility, + float beforeAlpha + ) { + if (!"notification_icon_area_inner".equals(idName)) { + return false; + } + return beforeVisibility == View.VISIBLE && beforeAlpha < 1f; } private boolean isLayoutOffRecoverableStockSurface(View view) { if (view == null || ownedIconHostManager.isOwnedHost(view)) { return false; } - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); String className = view.getClass().getName(); - return "left_clock_container".equals(idName) - || "notification_icon_area".equals(idName) - || "notification_icon_area_inner".equals(idName) - || "notificationIcons".equals(idName) - || "system_icon_area".equals(idName) - || "statusIcons".equals(idName) - || "battery".equals(idName) - || className.contains("NotificationIconContainer") - || className.contains("StatusIconContainer") - || className.contains("BatteryMeterView") - || className.contains("QSClockIndicatorView"); + return LAYOUT_OFF_UNLOCKED_ONLY_IDS.contains(idName) + || LAYOUT_OFF_EXTRA_RECOVERABLE_IDS.contains(idName) + || containsAny(className, LAYOUT_OFF_RECOVERABLE_CLASS_SUBSTRINGS); + } + + private static boolean containsAny(String value, Set tokens) { + if (value == null || tokens == null || tokens.isEmpty()) { + return false; + } + for (String token : tokens) { + if (value.contains(token)) { + return true; + } + } + return false; } private void removeSharedLayoutHosts(View root) { removeLockscreenCardsNotificationHost(root); removeAodCardsNotificationHost(root); - ownedIconHostManager.removeUnlockedHosts(root); + ownedIconHostManager.removeOwnedHosts(root); unlockedHostsByRoot.remove(root); pendingUnlockedAppearanceRefreshRoots.remove(root); dirtyUnlockedLayoutRoots.remove(root); @@ -3709,19 +3301,6 @@ final class StockLayoutCanvasController { clockTimeBucketByRoot.remove(root); } - private void requestViewAndParentLayout(View view) { - if (view == null || !isViewThread(view)) { - return; - } - view.requestLayout(); - view.invalidate(); - Object parent = view.getParent(); - if (parent instanceof View parentView) { - parentView.requestLayout(); - parentView.invalidate(); - } - } - private boolean isDescendantOfNotificationShadeWindow(View view) { View current = view; while (current != null) { @@ -3814,8 +3393,9 @@ final class StockLayoutCanvasController { generation = 31 * generation + aodNotificationSourceGeneration; generation = 31 * generation + latestAodIconImageViews.size(); generation = 31 * generation + latestAodNotifications.size(); - generation = 31 * generation + boundsSignature( - LockscreenCardsNotificationIconSourceStore.sourceContainerBoundsForRoot(root)); + Bounds sourceBounds = LockscreenCardsNotificationIconSourceStore + .sourceContainerBoundsForRoot(root); + generation = 31 * generation + (sourceBounds != null ? sourceBounds.signature() : 0); Integer previous = aodCardsNotificationRenderGenerationByRoot.get(root); boolean shouldRender = previous == null || previous != generation @@ -3830,22 +3410,6 @@ final class StockLayoutCanvasController { } } - private int boundsSignature(Bounds bounds) { - if (bounds == null) { - return 0; - } - int result = 17; - result = 31 * result + bounds.left; - result = 31 * result + bounds.top; - result = 31 * result + bounds.width; - result = 31 * result + bounds.height; - return result; - } - - private void applyLockscreenCardsNotificationRowFilter(ViewGroup stack) { - applyLockscreenCardsNotificationRowFilter(stack, false); - } - private void applyLockscreenCardsNotificationRowFilter(ViewGroup stack, boolean allowAfterKeyguardUnlock) { if (stack == null) { return; @@ -3938,8 +3502,8 @@ final class StockLayoutCanvasController { if (view.getWidth() <= 0 || view.getHeight() <= 0) { return false; } - String idName = resolveIdName(view).toLowerCase(java.util.Locale.ROOT); - String className = view.getClass().getName().toLowerCase(java.util.Locale.ROOT); + String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT); + String className = view.getClass().getName().toLowerCase(Locale.ROOT); return idName.contains("bouncer") || idName.contains("keyguard_security") || idName.contains("keyguard_pin") @@ -4018,7 +3582,7 @@ final class StockLayoutCanvasController { return null; } unlockedIconRenderController.clearAll(); - ownedIconHostManager.removeUnlockedHosts(root); + ownedIconHostManager.removeOwnedHosts(root); unlockedHostsByRoot.remove(root); pendingUnlockedAppearanceRefreshRoots.remove(root); dirtyUnlockedLayoutRoots.remove(root); @@ -4068,7 +3632,7 @@ final class StockLayoutCanvasController { } private boolean isAodRoot(View root) { - return root != null && root.getClass().getName().toLowerCase(java.util.Locale.ROOT).contains("aod"); + return root != null && root.getClass().getName().toLowerCase(Locale.ROOT).contains("aod"); } private boolean isAodLayoutRoot(View root) { @@ -4111,25 +3675,10 @@ final class StockLayoutCanvasController { return value instanceof View ? (View) value : null; } - private Object firstArg(java.util.List args) { + private Object firstArg(List args) { return args != null && !args.isEmpty() ? args.get(0) : null; } - private String resolveIdName(View view) { - if (view == null) { - return ""; - } - int id = view.getId(); - if (id == View.NO_ID) { - return ""; - } - try { - return view.getResources().getResourceEntryName(id); - } catch (Resources.NotFoundException ignored) { - return ""; - } - } - private boolean isKeyguardLocked(Context context) { if (context == null) { return false; @@ -4215,13 +3764,4 @@ final class StockLayoutCanvasController { private record AodLayoutBounds(int left, int top, int right, int bottom) { } - private record DarkIconDispatcherState( - Object darkIntensity, - Object iconTint, - Object tint, - Object darkAreas, - Object tintAreas - ) { - } - } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasFeature.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasFeature.java index c8fd1a8..10c3067 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasFeature.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasFeature.java @@ -10,8 +10,6 @@ import se.ajpanton.statusbartweak.runtime.features.RuntimeFeature; */ public final class StockLayoutCanvasFeature implements RuntimeFeature { - private static final String PACKAGE_SYSTEM_UI = "com.android.systemui"; - private StockLayoutCanvasController systemUiController; @Override @@ -21,18 +19,12 @@ public final class StockLayoutCanvasFeature implements RuntimeFeature { @Override public void onPackageReady(RuntimeContext context, XposedModuleInterface.PackageReadyParam param) { - if (param == null) { + if (!RuntimeFeature.isSystemUiPackage(param)) { return; } - installForPackage(context, param.getPackageName(), param.getClassLoader()); - } - - private void installForPackage(RuntimeContext context, String packageName, ClassLoader classLoader) { - if (PACKAGE_SYSTEM_UI.equals(packageName)) { - if (systemUiController == null) { - systemUiController = new StockLayoutCanvasController(context); - } - systemUiController.install(classLoader); + if (systemUiController == null) { + systemUiController = new StockLayoutCanvasController(context); } + systemUiController.install(param.getClassLoader()); } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layoutsolver/PackedStatusBarLayoutSolver.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layoutsolver/PackedStatusBarLayoutSolver.java index c9d6c4a..c52ef64 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layoutsolver/PackedStatusBarLayoutSolver.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layoutsolver/PackedStatusBarLayoutSolver.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.Map; public final class PackedStatusBarLayoutSolver { private PackedStatusBarLayoutSolver() { @@ -181,7 +182,7 @@ public final class PackedStatusBarLayoutSolver { } } - for (java.util.Map.Entry entry : relaxedWallByEndBox.entrySet()) { + for (Map.Entry entry : relaxedWallByEndBox.entrySet()) { ArrayList constraints = constraintsByEndBox.get(entry.getKey()); if (constraints == null) { continue; @@ -241,7 +242,7 @@ public final class PackedStatusBarLayoutSolver { LinkedHashMap> iconGroups = groupedIconItems(allItems); HashMap> remainingIconWidths = new HashMap<>(); HashMap remainingIconStarts = new HashMap<>(); - for (java.util.Map.Entry> entry : iconGroups.entrySet()) { + for (Map.Entry> entry : iconGroups.entrySet()) { remainingIconWidths.put(entry.getKey(), new ArrayList<>(entry.getValue().get(0).iconWidths())); remainingIconStarts.put(entry.getKey(), 0); } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layoutsolver/StatusBarLayoutSolver.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layoutsolver/StatusBarLayoutSolver.java index 65fc979..005d543 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layoutsolver/StatusBarLayoutSolver.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layoutsolver/StatusBarLayoutSolver.java @@ -717,6 +717,15 @@ public final class StatusBarLayoutSolver { int newTop = Math.min(top, other.top); return new Bounds(newLeft, newTop, right - newLeft, bottom - newTop); } + + public int signature() { + int result = 17; + result = 31 * result + left; + result = 31 * result + top; + result = 31 * result + width; + result = 31 * result + height; + return result; + } } public record PlacedBand( diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/mode/SystemUiStatusBarStateFeature.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/mode/SystemUiStatusBarStateFeature.java index ee0e8b7..46e90b2 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/mode/SystemUiStatusBarStateFeature.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/mode/SystemUiStatusBarStateFeature.java @@ -3,6 +3,7 @@ package se.ajpanton.statusbartweak.runtime.mode; import android.content.Context; import io.github.libxposed.api.XposedModuleInterface; +import se.ajpanton.statusbartweak.runtime.SystemContextProvider; import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; import se.ajpanton.statusbartweak.runtime.features.RuntimeFeature; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; @@ -16,7 +17,6 @@ import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; */ public final class SystemUiStatusBarStateFeature implements RuntimeFeature { - private static final String PACKAGE_SYSTEM_UI = "com.android.systemui"; private static final String CLASS_STATUS_BAR_STATE_CONTROLLER = "com.android.systemui.statusbar.StatusBarStateControllerImpl"; @@ -29,7 +29,7 @@ public final class SystemUiStatusBarStateFeature implements RuntimeFeature { @Override public void onPackageReady(RuntimeContext context, XposedModuleInterface.PackageReadyParam param) { - if (param == null || installed || !PACKAGE_SYSTEM_UI.equals(param.getPackageName())) { + if (installed || !RuntimeFeature.isSystemUiPackage(param)) { return; } Class controllerClass = ReflectionSupport.findClassIfExists( @@ -78,15 +78,6 @@ public final class SystemUiStatusBarStateFeature implements RuntimeFeature { if (context instanceof Context androidContext) { return androidContext; } - try { - Class activityThreadClass = Class.forName("android.app.ActivityThread"); - Object app = ReflectionSupport.invokeStaticMethod( - activityThreadClass, - "currentApplication", - new Class[0]); - return app instanceof Context androidContext ? androidContext : null; - } catch (Throwable ignored) { - return null; - } + return SystemContextProvider.currentApplication(); } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/AodLiveStatusbarSource.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/AodLiveStatusbarSource.java index 88d64fa..885ab6f 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/AodLiveStatusbarSource.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/AodLiveStatusbarSource.java @@ -1,11 +1,11 @@ package se.ajpanton.statusbartweak.runtime.render; -import android.content.res.Resources; import android.view.View; import android.view.ViewGroup; import java.util.ArrayDeque; +import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; final class AodLiveStatusbarSource { @@ -33,7 +33,7 @@ final class AodLiveStatusbarSource { if (childBounds == null) { continue; } - union = union == null ? childBounds : union(union, childBounds); + union = union == null ? childBounds : union.union(childBounds); } return union != null ? union : sourceBounds; } @@ -71,7 +71,7 @@ final class AodLiveStatusbarSource { } private static int sourcePriority(View view, boolean movingOnly) { - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); if ("common_statusbar_battery_container_internal".equals(idName) && view instanceof ViewGroup group && group.getChildCount() > 0 @@ -106,14 +106,6 @@ final class AodLiveStatusbarSource { return className.contains("ImageView") || className.contains("StatusBarIcon"); } - private static Bounds union(Bounds first, Bounds second) { - int left = Math.min(first.left, second.left); - int top = Math.min(first.top, second.top); - int right = Math.max(first.left + first.width, second.left + second.width); - int bottom = Math.max(first.top + first.height, second.top + second.height); - return new Bounds(left, top, right - left, bottom - top); - } - private static Bounds usableBounds(View root, View view) { Bounds bounds = ViewGeometry.boundsRelativeTo(root, view); if (root == null || bounds == null || bounds.width <= 0 || bounds.height <= 0) { @@ -130,22 +122,11 @@ final class AodLiveStatusbarSource { private static boolean hasAncestorId(View view, String idName) { Object parent = view != null ? view.getParent() : null; while (parent instanceof View parentView) { - if (idName.equals(resolveIdName(parentView))) { + if (idName.equals(ViewIdNames.idName(parentView))) { return true; } parent = parentView.getParent(); } return false; } - - private static String resolveIdName(View view) { - if (view == null || view.getId() == View.NO_ID) { - return ""; - } - try { - return view.getResources().getResourceEntryName(view.getId()); - } catch (Resources.NotFoundException ignored) { - return ""; - } - } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/AodStatusbarSourceStore.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/AodStatusbarSourceStore.java index 0536a27..1363f04 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/AodStatusbarSourceStore.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/AodStatusbarSourceStore.java @@ -21,17 +21,13 @@ public final class AodStatusbarSourceStore { private AodStatusbarSourceStore() { } - public static void putStatusContainer(View view) { - putStatusSource(view); - } - - public static void putStatusSource(View view) { + private static void putStatusSource(View view) { if (view != null) { STATUS_SOURCES.put(view, StatusSourceRole.UNKNOWN); } } - public static void putStatusSource(View view, StatusSourceRole role) { + private static void putStatusSource(View view, StatusSourceRole role) { if (view != null) { STATUS_SOURCES.put(view, role != null ? role : StatusSourceRole.UNKNOWN); } @@ -83,62 +79,28 @@ public final class AodStatusbarSourceStore { if (!isUsableRightAnchor(root, bounds)) { continue; } - union = union == null ? bounds : union(union, bounds); + union = union == null ? bounds : union.union(bounds); } return union; } - public static ArrayList statusBoundsListForRoot(View root, View excludedContainer) { - ArrayList result = new ArrayList<>(); - if (root == null) { - return result; - } - for (View source : STATUS_SOURCES.keySet()) { - if (!isUsableSource(root, source, excludedContainer)) { - continue; - } - if (!isStatusAnchorSource(STATUS_SOURCES.get(source))) { - continue; - } - Bounds bounds = boundsForRoot(root, source); - if (isUsableRightAnchor(root, bounds)) { - result.add(bounds); - } - } - return result; - } - private static boolean isStatusAnchorSource(StatusSourceRole role) { return role != StatusSourceRole.SYSTEM_ICONS; } - private static Bounds union(Bounds first, Bounds second) { - if (first == null) { - return second; - } - if (second == null) { - return first; - } - int left = Math.min(first.left, second.left); - int top = Math.min(first.top, second.top); - int right = Math.max(right(first), right(second)); - int bottom = Math.max(first.top + first.height, second.top + second.height); - return new Bounds(left, top, right - left, bottom - top); - } - private static boolean isUsableSource(View root, View source, View excludedContainer) { return source != null && source != excludedContainer && !isRelated(source, excludedContainer) && !isNotificationContainer(source) - && !isOwnedRuntimeView(source) + && !ViewGeometry.isOwnedRuntimeView(source) && source.isAttachedToWindow(); } private static Bounds boundsForRoot(View root, View view) { Bounds localBounds = ViewGeometry.boundsRelativeTo(root, view); if (localBounds != null) { - return clampToRoot(root, localBounds); + return ViewGeometry.clampToRoot(root, localBounds); } return screenBoundsRelativeTo(root, view); } @@ -158,21 +120,7 @@ public final class AodStatusbarSourceStore { view.getLocationOnScreen(viewLocation); int left = viewLocation[0] - rootLocation[0]; int top = viewLocation[1] - rootLocation[1]; - return clampToRoot(root, new Bounds(left, top, width, height)); - } - - private static Bounds clampToRoot(View root, Bounds bounds) { - if (root == null || bounds == null || root.getWidth() <= 0 || root.getHeight() <= 0) { - return null; - } - int left = Math.max(0, bounds.left); - int top = Math.max(0, bounds.top); - int right = Math.min(root.getWidth(), bounds.left + bounds.width); - int bottom = Math.min(root.getHeight(), bounds.top + bounds.height); - if (right <= left || bottom <= top) { - return null; - } - return new Bounds(left, top, right - left, bottom - top); + return ViewGeometry.clampToRoot(root, new Bounds(left, top, width, height)); } private static boolean isUsableRightAnchor(View root, Bounds bounds) { @@ -182,7 +130,7 @@ public final class AodStatusbarSourceStore { || bounds.height <= 0) { return false; } - int right = right(bounds); + int right = bounds.left + bounds.width; int bottom = bounds.top + bounds.height; return bounds.left >= 0 && right <= root.getWidth() @@ -190,15 +138,11 @@ public final class AodStatusbarSourceStore { && bottom <= root.getHeight(); } - private static int right(Bounds bounds) { - return bounds.left + bounds.width; - } - static ArrayList statusSourcesForRoot(View root, View excludedContainer) { ArrayList result = new ArrayList<>(); for (View source : STATUS_SOURCES.keySet()) { if (!isUsableSource(root, source, excludedContainer) - || !isDescendantOf(source, root)) { + || !ViewGeometry.isDescendantOf(source, root)) { continue; } if (!isStatusAnchorSource(STATUS_SOURCES.get(source))) { @@ -209,22 +153,10 @@ public final class AodStatusbarSourceStore { return result; } - private static boolean isDescendantOf(View view, View root) { - View current = view; - while (current != null) { - if (current == root) { - return true; - } - Object parent = current.getParent(); - current = parent instanceof View ? (View) parent : null; - } - return false; - } - private static boolean isRelated(View view, View other) { return view != null && other != null - && (isDescendantOf(view, other) || isDescendantOf(other, view)); + && (ViewGeometry.isDescendantOf(view, other) || ViewGeometry.isDescendantOf(other, view)); } private static boolean isNotificationContainer(View view) { @@ -233,17 +165,4 @@ public final class AodStatusbarSourceStore { || className.contains("NotificationIconContainer"); } - private static boolean isOwnedRuntimeView(View view) { - View current = view; - while (current != null) { - Object tag = current.getTag(); - if (tag instanceof String value && value.startsWith("statusbartweak.")) { - return true; - } - Object parent = current.getParent(); - current = parent instanceof View ? (View) parent : null; - } - return false; - } - } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockProxyRenderer.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockProxyRenderer.java index 2fa1bdd..8a7020f 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockProxyRenderer.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockProxyRenderer.java @@ -8,6 +8,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import java.util.ArrayList; +import java.util.Objects; import java.util.WeakHashMap; final class ClockProxyRenderer { @@ -53,7 +54,7 @@ final class ClockProxyRenderer { view.setText(row.text); hostChanged = true; } - if (!java.util.Objects.equals(view.getContentDescription(), placement.contentDescription)) { + if (!Objects.equals(view.getContentDescription(), placement.contentDescription)) { view.setContentDescription(placement.contentDescription); hostChanged = true; } @@ -145,7 +146,7 @@ final class ClockProxyRenderer { target.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, source.getTextSize()); changed = true; } - if (!java.util.Objects.equals(target.getTypeface(), source.getTypeface())) { + if (!Objects.equals(target.getTypeface(), source.getTypeface())) { target.setTypeface(source.getTypeface()); changed = true; } @@ -177,15 +178,13 @@ final class ClockProxyRenderer { source.getPaddingBottom()); changed = true; } - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - if (target.getLetterSpacing() != source.getLetterSpacing()) { - target.setLetterSpacing(source.getLetterSpacing()); - changed = true; - } - if (!java.util.Objects.equals(target.getFontFeatureSettings(), source.getFontFeatureSettings())) { - target.setFontFeatureSettings(source.getFontFeatureSettings()); - changed = true; - } + if (target.getLetterSpacing() != source.getLetterSpacing()) { + target.setLetterSpacing(source.getLetterSpacing()); + changed = true; + } + if (!Objects.equals(target.getFontFeatureSettings(), source.getFontFeatureSettings())) { + target.setFontFeatureSettings(source.getFontFeatureSettings()); + changed = true; } return changed; } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/DebugBoundsOverlayController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/DebugBoundsOverlayController.java index c7f6e07..f95d863 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/DebugBoundsOverlayController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/DebugBoundsOverlayController.java @@ -26,8 +26,6 @@ import se.ajpanton.statusbartweak.shell.settings.SbtSettings; public final class DebugBoundsOverlayController { private static final String TAG_OVERLAY_HOST = "statusbartweak.debug.bounds.host"; - private static final String TAG_CLOCK_HOST = "statusbartweak.unlocked.clock.host"; - private static final String OVERFLOW_DOT_KEY = "statusbartweak:overflow_dot"; private static final int COLOR_NOTIFICATION = Color.argb(96, 0, 128, 255); private static final int COLOR_STATUS = Color.argb(96, 255, 64, 64); private static final int COLOR_CLOCK = Color.argb(96, 64, 220, 96); @@ -35,10 +33,6 @@ public final class DebugBoundsOverlayController { private static final int COLOR_CUTOUT = Color.argb(112, 255, 180, 0); private static final int COLOR_AOD_DERIVED_STATUSBAR = Color.argb(80, 255, 220, 0); - public boolean hasEnabledVisualizer(boolean layoutEnabled, SbtSettings settings) { - return hasEnabledVisualizer(layoutEnabled, settings, true); - } - public boolean hasEnabledVisualizer( boolean layoutEnabled, SbtSettings settings, @@ -56,19 +50,6 @@ public final class DebugBoundsOverlayController { || (aodStockVisualizersEnabled && settings.debugVisualizeAodStockContainers))); } - public void apply(View root, boolean layoutEnabled, SbtSettings settings) { - apply(root, layoutEnabled, settings, true); - } - - public void apply( - View root, - boolean layoutEnabled, - SbtSettings settings, - boolean aodStockVisualizersEnabled - ) { - apply(root, layoutEnabled, settings, aodStockVisualizersEnabled, null); - } - public void apply( View root, boolean layoutEnabled, @@ -107,7 +88,12 @@ public final class DebugBoundsOverlayController { specs); } if (layoutEnabled && settings.debugVisualizeClockContainer) { - addHostChildSpecs(root, parent, TAG_CLOCK_HOST, COLOR_CLOCK, specs); + addHostChildSpecs( + root, + parent, + OwnedIconHostManager.TAG_UNLOCKED_CLOCK_HOST, + COLOR_CLOCK, + specs); } if (layoutEnabled && settings.debugVisualizeChipContainer) { addHostContainerSpecs( @@ -141,19 +127,6 @@ public final class DebugBoundsOverlayController { } } - public int sourceSignature(View root, boolean layoutEnabled, SbtSettings settings) { - return sourceSignature(root, layoutEnabled, settings, true); - } - - public int sourceSignature( - View root, - boolean layoutEnabled, - SbtSettings settings, - boolean aodStockVisualizersEnabled - ) { - return sourceSignature(root, layoutEnabled, settings, aodStockVisualizersEnabled, null); - } - public int sourceSignature( View root, boolean layoutEnabled, @@ -168,10 +141,10 @@ public final class DebugBoundsOverlayController { } int result = 17; ViewGroup notificationContainer = aodNotificationContainer(root, scene); - result = 31 * result + boundsSignature( + result = 31 * result + signature( aodNotificationBounds(root, scene)); - result = 31 * result + boundsSignature(liveAodStatusIconsBounds(root)); - result = 31 * result + boundsSignature( + result = 31 * result + signature(liveAodStatusIconsBounds(root)); + result = 31 * result + signature( derivedAodStatusbarBounds(root, notificationContainer, settings, scene)); return result; } @@ -289,16 +262,8 @@ public final class DebugBoundsOverlayController { && bottom <= root.getHeight(); } - private int boundsSignature(Bounds bounds) { - if (bounds == null) { - return 0; - } - int result = 17; - result = 31 * result + bounds.left; - result = 31 * result + bounds.top; - result = 31 * result + bounds.width; - result = 31 * result + bounds.height; - return result; + private int signature(Bounds bounds) { + return bounds != null ? bounds.signature() : 0; } private Bounds overlayBounds(StatusBarLayoutSolver.Bounds bounds) { @@ -396,7 +361,7 @@ public final class DebugBoundsOverlayController { private boolean isOverflowDot(View view) { Object tag = view != null ? view.getTag() : null; - return tag instanceof String string && string.startsWith(OVERFLOW_DOT_KEY); + return tag instanceof String string && string.startsWith(SnapshotKeys.OVERFLOW_DOT); } private Bounds overlayBoundsRelativeTo(View root, View child, boolean stableRegularIconSlots) { @@ -649,6 +614,15 @@ public final class DebugBoundsOverlayController { int newTop = Math.min(top, other.top); return new Bounds(newLeft, newTop, right - newLeft, bottom - newTop); } + + int signature() { + int result = 17; + result = 31 * result + left; + result = 31 * result + top; + result = 31 * result + width; + result = 31 * result + height; + return result; + } } private static final class OverlayView extends View { diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationIconSourceStore.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationIconSourceStore.java index c45d999..9c6274c 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationIconSourceStore.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationIconSourceStore.java @@ -8,9 +8,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.IdentityHashMap; +import java.util.Locale; import java.util.Set; import java.util.WeakHashMap; +import se.ajpanton.statusbartweak.runtime.NotificationKeyReflection; +import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; import se.ajpanton.statusbartweak.shell.settings.AppIconRules; @@ -318,7 +321,7 @@ public final class LockscreenCardsNotificationIconSourceStore { || bounds.height > 0) { return false; } - return "notification_dot_container".equals(resolveIdName(container)); + return "notification_dot_container".equals(ViewIdNames.idName(container)); } private static ViewGroup sourceContainerForRoot( @@ -339,7 +342,7 @@ public final class LockscreenCardsNotificationIconSourceStore { for (ViewGroup container : containers) { if (container != null && container.isAttachedToWindow() - && isDescendantOf(container, root) + && ViewGeometry.isDescendantOf(container, root) && (!iconOnlyContainerRequired || isAodIconOnlyContainer(container))) { return container; } @@ -351,24 +354,12 @@ public final class LockscreenCardsNotificationIconSourceStore { if (view == null) { return false; } - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); return "keyguard_icononly_container_view".equals(idName) || "notification_icononly_container".equals(idName) || "notification_icononly_area".equals(idName); } - private static boolean isDescendantOf(View view, View root) { - View current = view; - while (current != null) { - if (current == root) { - return true; - } - Object parent = current.getParent(); - current = parent instanceof View ? (View) parent : null; - } - return false; - } - private static boolean hasSources(ViewGroup container) { return SOURCES_BY_CONTAINER.containsKey(container) || AOD_DRAWABLES_BY_CONTAINER.containsKey(container) @@ -382,8 +373,7 @@ public final class LockscreenCardsNotificationIconSourceStore { } int shelfTop = Integer.MAX_VALUE; if (shelf != null) { - se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds shelfBounds = - ViewGeometry.boundsRelativeTo(root, shelf); + Bounds shelfBounds = ViewGeometry.boundsRelativeTo(root, shelf); if (shelfBounds != null) { shelfTop = shelfBounds.top; } @@ -393,8 +383,7 @@ public final class LockscreenCardsNotificationIconSourceStore { for (int i = 0; i < stack.size(); i++) { View view = stack.get(i); if (isNotificationRow(view) && isVisibleCardRow(view)) { - se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds rowBounds = - ViewGeometry.boundsRelativeTo(root, view); + Bounds rowBounds = ViewGeometry.boundsRelativeTo(root, view); if (rowBounds != null && rowBounds.top < shelfTop) { String key = keyForRow(view); if (key != null && !key.isEmpty()) { @@ -420,7 +409,7 @@ public final class LockscreenCardsNotificationIconSourceStore { } ArrayList visibleShelfIcons = visibleShelfIcons(container); for (View icon : visibleShelfIcons) { - String key = keyForIconView(icon); + String key = NotificationKeyReflection.keyForIconView(icon); for (int i = 0; i < entries.size(); i++) { SourceEntry entry = entries.get(i); if (entry == null) { @@ -478,8 +467,8 @@ public final class LockscreenCardsNotificationIconSourceStore { } private static boolean isOverflowDotCandidate(View view) { - String idName = resolveIdName(view).toLowerCase(java.util.Locale.ROOT); - String className = view.getClass().getName().toLowerCase(java.util.Locale.ROOT); + String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT); + String className = view.getClass().getName().toLowerCase(Locale.ROOT); return idName.contains("overflow") || idName.contains("dot") || className.contains("overflow") @@ -490,52 +479,13 @@ public final class LockscreenCardsNotificationIconSourceStore { return new SourceEntry(view, key); } - public static String keyForEntry(Object entry) { - if (entry == null) { - return null; - } - Object methodValue = ReflectionSupport.invokeMethod(entry, "getKey", new Class[0]); - if (methodValue instanceof String string && !string.isEmpty()) { - return string; - } - for (String fieldName : new String[]{"mKey", "key", "notificationKey", "mNotificationKey"}) { - Object value = ReflectionSupport.getFieldValue(entry, fieldName); - if (value instanceof String string && !string.isEmpty()) { - return string; - } - } - return null; - } - - public static String keyForIconView(View view) { - if (view == null) { - return null; - } - Object methodValue = ReflectionSupport.invokeMethod(view, "getNotificationKey", new Class[0]); - if (methodValue instanceof String string && !string.isEmpty()) { - return string; - } - for (String fieldName : new String[]{"mNotificationKey", "notificationKey", "mKey", "key"}) { - Object value = ReflectionSupport.getFieldValue(view, fieldName); - if (value instanceof String string && !string.isEmpty()) { - return string; - } - } - return null; - } - private static String keyForRow(View view) { - Object entry = ReflectionSupport.invokeMethod(view, "getEntry", new Class[0]); - String key = keyForEntry(entry); + Object entry = NotificationKeyReflection.primaryEntryForRow(view); + String key = NotificationKeyReflection.keyForEntry(entry); if (key != null && !key.isEmpty()) { return key; } - entry = ReflectionSupport.getFieldValue(view, "mEntry"); - key = keyForEntry(entry); - if (key != null && !key.isEmpty()) { - return key; - } - return keyForIconView(view); + return NotificationKeyReflection.keyForIconView(view); } private static boolean isNotificationRow(View view) { @@ -546,22 +496,11 @@ public final class LockscreenCardsNotificationIconSourceStore { return className.contains("ExpandableNotificationRow"); } - private static String resolveIdName(View view) { - if (view == null || view.getId() == View.NO_ID) { - return ""; - } - try { - return view.getResources().getResourceEntryName(view.getId()); - } catch (Throwable ignored) { - return ""; - } - } - private static boolean isVisibleCardRow(View view) { if (view == null || view.getVisibility() == View.GONE) { return false; } - if (!view.isShown() || effectiveAlpha(view) <= 0.05f) { + if (!view.isShown() || ViewGeometry.effectiveAlpha(view) <= 0.05f) { return false; } if (ReflectionSupport.getBooleanField(view, "mInShelf", false) @@ -580,7 +519,7 @@ public final class LockscreenCardsNotificationIconSourceStore { } private static int actualHeight(View view, int fallback) { - Object methodValue = ReflectionSupport.invokeMethod(view, "getActualHeight", new Class[0]); + Object methodValue = ReflectionSupport.invokeMethod(view, "getActualHeight"); if (methodValue instanceof Integer integer && integer > 0) { return integer; } @@ -591,16 +530,6 @@ public final class LockscreenCardsNotificationIconSourceStore { return fallback; } - private static float effectiveAlpha(View view) { - float alpha = 1f; - Object current = view; - while (current instanceof View currentView) { - alpha *= currentView.getAlpha(); - current = currentView.getParent(); - } - return alpha; - } - public static final class SourceEntry { final View view; final String key; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationStripRenderer.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationStripRenderer.java index ec0f006..cfd66e8 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationStripRenderer.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationStripRenderer.java @@ -12,8 +12,10 @@ import android.widget.TextView; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Locale; import java.util.WeakHashMap; +import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle; import se.ajpanton.statusbartweak.runtime.mode.SceneKey; @@ -115,16 +117,6 @@ public final class LockscreenCardsNotificationStripRenderer { clearPills(host); } - public void clearAll() { - renderer.clearAll(); - ArrayList hosts = new ArrayList<>(pillViewsByHost.keySet()); - for (ViewGroup host : hosts) { - clearPills(host); - } - pillViewsByHost.clear(); - aodInitialStatusbarHeightByRoot.clear(); - } - private ViewGroup findSourceRoot(View root) { if (!(root instanceof ViewGroup group)) { return null; @@ -170,22 +162,12 @@ public final class LockscreenCardsNotificationStripRenderer { if (host == null || sourceRoot == null) { return; } - float alpha = effectiveAncestorAlpha(sourceRoot); + float alpha = ViewGeometry.effectiveAncestorAlpha(sourceRoot); if (host.getAlpha() != alpha) { host.setAlpha(alpha); } } - private float effectiveAncestorAlpha(View view) { - float alpha = 1f; - Object current = view.getParent(); - while (current instanceof View currentView) { - alpha *= currentView.getAlpha(); - current = currentView.getParent(); - } - return Math.max(0f, Math.min(1f, alpha)); - } - private ArrayList collectSources(ViewGroup sourceRoot) { ArrayList result = new ArrayList<>(); ArrayDeque queue = new ArrayDeque<>(); @@ -448,9 +430,9 @@ public final class LockscreenCardsNotificationStripRenderer { while (!queue.isEmpty()) { View view = queue.removeFirst(); String className = view.getClass().getName(); - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); if (className.contains("NotificationShelfBackground") - || idName.toLowerCase(java.util.Locale.ROOT).contains("background")) { + || idName.toLowerCase(Locale.ROOT).contains("background")) { return view; } if (view instanceof ViewGroup childGroup) { @@ -580,7 +562,7 @@ public final class LockscreenCardsNotificationStripRenderer { } private boolean isIconOnlyContainer(View view) { - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); return "keyguard_icononly_container_view".equals(idName) || "notification_icononly_container".equals(idName) || "notification_icononly_area".equals(idName) @@ -603,25 +585,14 @@ public final class LockscreenCardsNotificationStripRenderer { } private boolean isOverflowDotCandidate(View view) { - String idName = resolveIdName(view).toLowerCase(java.util.Locale.ROOT); - String className = view.getClass().getName().toLowerCase(java.util.Locale.ROOT); + String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT); + String className = view.getClass().getName().toLowerCase(Locale.ROOT); return idName.contains("overflow") || idName.contains("dot") || className.contains("overflow") || className.contains("dot"); } - private String resolveIdName(View view) { - if (view == null || view.getId() == View.NO_ID) { - return ""; - } - try { - return view.getResources().getResourceEntryName(view.getId()); - } catch (Throwable ignored) { - return ""; - } - } - public record RenderResult(View sourceRoot, Bounds renderedBounds) { static RenderResult empty() { return new RenderResult(null, null); 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 45b67c0..b218eb5 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 @@ -30,6 +30,15 @@ public final class OwnedIconHostManager { "statusbartweak.lockscreen.cards.notification.host"; public static final String TAG_AOD_CARDS_NOTIFICATION_HOST = "statusbartweak.aod.cards.notification.host"; + private static final String[] OWNED_HOST_TAGS = { + TAG_UNLOCKED_STATUS_HOST, + TAG_UNLOCKED_NOTIFICATION_HOST, + TAG_UNLOCKED_CLOCK_HOST, + TAG_UNLOCKED_CARRIER_HOST, + TAG_UNLOCKED_CHIP_HOST, + TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST, + TAG_AOD_CARDS_NOTIFICATION_HOST + }; private static final String[] UNLOCKED_HOST_ORDER = { TAG_UNLOCKED_STATUS_HOST, TAG_UNLOCKED_NOTIFICATION_HOST, @@ -66,7 +75,7 @@ public final class OwnedIconHostManager { return ensureHost(root, TAG_AOD_CARDS_NOTIFICATION_HOST); } - public FrameLayout ensureHost(View root, String tag) { + private FrameLayout ensureHost(View root, String tag) { Objects.requireNonNull(tag, "tag"); if (!(root instanceof ViewGroup parent)) { return null; @@ -98,14 +107,10 @@ public final class OwnedIconHostManager { } } - public void removeUnlockedHosts(View root) { - removeHost(root, TAG_UNLOCKED_STATUS_HOST); - removeHost(root, TAG_UNLOCKED_NOTIFICATION_HOST); - removeHost(root, TAG_UNLOCKED_CLOCK_HOST); - removeHost(root, TAG_UNLOCKED_CARRIER_HOST); - removeHost(root, TAG_UNLOCKED_CHIP_HOST); - removeHost(root, TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST); - removeHost(root, TAG_AOD_CARDS_NOTIFICATION_HOST); + public void removeOwnedHosts(View root) { + for (String tag : OWNED_HOST_TAGS) { + removeHost(root, tag); + } } public void setUnlockedHostsVisible(View root, boolean visible) { @@ -132,13 +137,12 @@ public final class OwnedIconHostManager { return false; } Object tag = view.getTag(); - return TAG_UNLOCKED_STATUS_HOST.equals(tag) - || TAG_UNLOCKED_NOTIFICATION_HOST.equals(tag) - || TAG_UNLOCKED_CLOCK_HOST.equals(tag) - || TAG_UNLOCKED_CARRIER_HOST.equals(tag) - || TAG_UNLOCKED_CHIP_HOST.equals(tag) - || TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST.equals(tag) - || TAG_AOD_CARDS_NOTIFICATION_HOST.equals(tag); + for (String ownedTag : OWNED_HOST_TAGS) { + if (ownedTag.equals(tag)) { + return true; + } + } + return false; } private FrameLayout findDirectHost(ViewGroup parent, String tag) { diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotAppearanceSignature.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotAppearanceSignature.java index 1c02b8d..03293a5 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotAppearanceSignature.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotAppearanceSignature.java @@ -9,6 +9,8 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import java.util.Arrays; + import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; final class SnapshotAppearanceSignature { @@ -33,7 +35,7 @@ final class SnapshotAppearanceSignature { return 0; } int result = 17; - result = 31 * result + java.util.Arrays.hashCode(view.getDrawableState()); + result = 31 * result + Arrays.hashCode(view.getDrawableState()); if (view instanceof TextView textView) { result = 31 * result + textView.getCurrentTextColor(); result = 31 * result + textView.getTextColors().hashCode(); @@ -58,7 +60,7 @@ final class SnapshotAppearanceSignature { result = 31 * result + System.identityHashCode(drawable); result = 31 * result + drawable.getLevel(); result = 31 * result + drawable.getAlpha(); - result = 31 * result + java.util.Arrays.hashCode(drawable.getState()); + result = 31 * result + Arrays.hashCode(drawable.getState()); ColorFilter colorFilter = drawable.getColorFilter(); result = 31 * result + (colorFilter != null ? System.identityHashCode(colorFilter) : 0); return result; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotIconFilter.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotIconFilter.java index 4f7f482..3e3f38b 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotIconFilter.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotIconFilter.java @@ -1,6 +1,5 @@ package se.ajpanton.statusbartweak.runtime.render; -import android.content.res.Resources; import android.view.View; import java.util.ArrayList; @@ -9,6 +8,8 @@ import java.util.Map; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; +import se.ajpanton.statusbartweak.runtime.NotificationKeyReflection; +import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.mode.SceneKey; import se.ajpanton.statusbartweak.shell.settings.AppIconRules; import se.ajpanton.statusbartweak.shell.settings.SbtSettings; @@ -69,19 +70,7 @@ final class SnapshotIconFilter { } static String notificationPackage(String key) { - if (key == null) { - return null; - } - String normalized = key.trim(); - int suffix = normalized.indexOf('@'); - if (suffix >= 0) { - normalized = normalized.substring(0, suffix); - } - int slash = normalized.indexOf('/'); - if (slash >= 0) { - normalized = normalized.substring(0, slash); - } - return AppIconRules.isValidPackageName(normalized) ? normalized : null; + return NotificationKeyReflection.packageFromNotificationKey(key); } static boolean isNotificationBlocked( @@ -165,7 +154,7 @@ final class SnapshotIconFilter { return; } append(sb, view.getClass().getName()); - append(sb, resolveIdName(view)); + append(sb, ViewIdNames.idName(view)); CharSequence contentDescription = view.getContentDescription(); append(sb, contentDescription != null ? contentDescription.toString() : null); } @@ -275,14 +264,4 @@ final class SnapshotIconFilter { sb.append(value); } - private static String resolveIdName(View view) { - if (view == null || view.getId() == View.NO_ID) { - return ""; - } - try { - return view.getResources().getResourceEntryName(view.getId()); - } catch (Resources.NotFoundException ignored) { - return ""; - } - } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java index eca4ec4..fa9c2c7 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotModels.java @@ -6,6 +6,7 @@ import android.view.View; import android.widget.TextView; import java.util.ArrayList; +import java.util.Objects; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; @@ -353,7 +354,7 @@ final class LayoutInputSignature { } int stockSurfaceSignature() { - return java.util.Objects.hash( + return Objects.hash( rootWidth, rootHeight, rootPaddingLeft, @@ -370,7 +371,7 @@ final class LayoutInputSignature { } int nonClockLayoutSignature() { - return java.util.Objects.hash( + return Objects.hash( scene, rootWidth, rootHeight, @@ -421,12 +422,12 @@ final class LayoutInputSignature { && statusSources == that.statusSources && notificationSources == that.notificationSources && chipSources == that.chipSources - && java.util.Objects.equals(scene, that.scene); + && Objects.equals(scene, that.scene); } @Override public int hashCode() { - return java.util.Objects.hash( + return Objects.hash( scene, rootWidth, rootHeight, diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java index 09b0451..b49d7ae 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java @@ -2,7 +2,6 @@ package se.ajpanton.statusbartweak.runtime.render; import android.content.Context; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -18,13 +17,19 @@ import android.widget.ImageView; import android.widget.TextView; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; import java.util.WeakHashMap; +import se.ajpanton.statusbartweak.runtime.NotificationKeyReflection; +import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; @@ -180,7 +185,7 @@ final class SnapshotRenderer { structureChanged = true; proxyChanged = true; } - if (!java.util.Objects.equals(proxy.getTag(), key)) { + if (!Objects.equals(proxy.getTag(), key)) { proxy.setTag(key); proxyChanged = true; } @@ -372,14 +377,14 @@ final class SnapshotRenderer { return keyHint; } if (view == null) { - return source.mode().name().toLowerCase(java.util.Locale.ROOT) + "#" + order; + return source.mode().name().toLowerCase(Locale.ROOT) + "#" + order; } String slot = reflectString(view, "getSlot", "mSlot", "slot"); if (!slot.isEmpty()) { return slot; } - String key = reflectString(view, "getNotificationKey", "mNotificationKey", "notificationKey", "mKey", "key"); - if (!key.isEmpty()) { + String key = NotificationKeyReflection.keyForIconView(view); + if (key != null && !key.isEmpty()) { return key; } return view.getClass().getName() + "#" + order; @@ -389,15 +394,15 @@ final class SnapshotRenderer { if (view == null || key == null) { return null; } - String lowerKey = key.toLowerCase(java.util.Locale.ROOT); + String lowerKey = key.toLowerCase(Locale.ROOT); if (lowerKey.contains("wifi")) { return "wifi"; } if (lowerKey.contains("mobile")) { return "mobile"; } - String idName = resolveIdName(view).toLowerCase(java.util.Locale.ROOT); - String className = view.getClass().getName().toLowerCase(java.util.Locale.ROOT); + String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT); + String className = view.getClass().getName().toLowerCase(Locale.ROOT); if (idName.contains("wifi") || className.contains("wifi")) { return "wifi"; } @@ -448,16 +453,15 @@ final class SnapshotRenderer { liveSignalKeys.add(placement.key()); } } - ArrayList staleKeys = new ArrayList<>(); - for (String key : heldSignals.keySet()) { - if (!liveSignalKeys.contains(key)) { - staleKeys.add(key); - } - } - for (String key : staleKeys) { - HeldSignal stale = heldSignals.remove(key); - if (stale != null) { - stale.recycle(); + Iterator> iterator = heldSignals.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (!liveSignalKeys.contains(entry.getKey())) { + HeldSignal stale = entry.getValue(); + iterator.remove(); + if (stale != null) { + stale.recycle(); + } } } } @@ -466,19 +470,19 @@ final class SnapshotRenderer { LinkedHashMap proxies, HashSet desiredKeys ) { - ArrayList staleKeys = new ArrayList<>(); - for (Map.Entry entry : proxies.entrySet()) { + int removed = 0; + Iterator> iterator = proxies.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); if (!desiredKeys.contains(entry.getKey())) { ImageView proxy = entry.getValue(); detachFromParent(proxy); statesByProxy.remove(proxy); - staleKeys.add(entry.getKey()); + iterator.remove(); + removed++; } } - for (String key : staleKeys) { - proxies.remove(key); - } - return staleKeys.size(); + return removed; } private BitmapResult bitmapFor(ImageView proxy, Bounds bounds) { @@ -499,6 +503,7 @@ final class SnapshotRenderer { private SnapshotRenderState renderStateFor(SnapshotPlacement placement) { Bounds bounds = placement.bounds(); + Bounds sourceRenderBounds = placement.sourceRenderBounds(); return new SnapshotRenderState( placement.key(), bounds.width, @@ -507,7 +512,7 @@ final class SnapshotRenderer { placement.overflowDot(), placement.overflowDotColor(), placement.sourceOffsetX(), - boundsSignature(placement.sourceRenderBounds()), + sourceRenderBounds != null ? sourceRenderBounds.signature() : 0, placement.heldSignal() != null ? placement.heldSignal().key : "", placement.heldSignal() != null ? System.identityHashCode(placement.heldSignal().bitmap) : 0, placement.source() != null ? placement.source().mode() : null, @@ -520,19 +525,8 @@ final class SnapshotRenderer { } int result = 17; result = 31 * result + sourceTreeSignature(source.view()); - result = 31 * result + boundsSignature(source.boundsOverride()); - return result; - } - - private int boundsSignature(Bounds bounds) { - if (bounds == null) { - return 0; - } - int result = 17; - result = 31 * result + bounds.left; - result = 31 * result + bounds.top; - result = 31 * result + bounds.width; - result = 31 * result + bounds.height; + Bounds boundsOverride = source.boundsOverride(); + result = 31 * result + (boundsOverride != null ? boundsOverride.signature() : 0); return result; } @@ -565,7 +559,7 @@ final class SnapshotRenderer { result = 31 * result + view.getScrollY(); CharSequence contentDescription = view.getContentDescription(); result = 31 * result + (contentDescription != null ? contentDescription.toString().hashCode() : 0); - result = 31 * result + java.util.Arrays.hashCode(view.getDrawableState()); + result = 31 * result + Arrays.hashCode(view.getDrawableState()); result = 31 * result + SnapshotAppearanceSignature.view(view); if (view instanceof TextView textView) { CharSequence text = textView.getText(); @@ -597,7 +591,7 @@ final class SnapshotRenderer { result = 31 * result + view.getHeight(); result = 31 * result + view.getScrollX(); result = 31 * result + view.getScrollY(); - result = 31 * result + java.util.Arrays.hashCode(view.getDrawableState()); + result = 31 * result + Arrays.hashCode(view.getDrawableState()); result = 31 * result + SnapshotAppearanceSignature.view(view); if (view instanceof TextView textView) { CharSequence text = textView.getText(); @@ -611,7 +605,7 @@ final class SnapshotRenderer { if (drawable != null) { result = 31 * result + drawable.getLevel(); result = 31 * result + drawable.getAlpha(); - result = 31 * result + java.util.Arrays.hashCode(drawable.getState()); + result = 31 * result + Arrays.hashCode(drawable.getState()); } result = 31 * result + (imageView.getImageTintList() != null ? imageView.getImageTintList().hashCode() @@ -632,20 +626,6 @@ final class SnapshotRenderer { private record SourceSignatureCache(int stamp, int signature) { } - private int drawableSignature(Drawable drawable) { - if (drawable == null) { - return 0; - } - int result = 17; - result = 31 * result + SnapshotAppearanceSignature.drawable(drawable); - Rect bounds = drawable.getBounds(); - result = 31 * result + bounds.left; - result = 31 * result + bounds.top; - result = 31 * result + bounds.right; - result = 31 * result + bounds.bottom; - return result; - } - private void drawSnapshot(SnapshotSource source, Bitmap bitmap) { drawSnapshot(source, bitmap, 0); } @@ -1094,19 +1074,8 @@ final class SnapshotRenderer { return new Bounds(left, top, Math.max(0, width), Math.max(0, height)); } - private static String resolveIdName(View view) { - if (view.getId() == View.NO_ID) { - return ""; - } - try { - return view.getResources().getResourceEntryName(view.getId()); - } catch (Resources.NotFoundException ignored) { - return ""; - } - } - private static String reflectString(Object target, String methodName, String... fieldNames) { - Object methodValue = ReflectionSupport.invokeMethod(target, methodName, new Class[0]); + Object methodValue = ReflectionSupport.invokeMethod(target, methodName); if (methodValue instanceof String string && !string.isEmpty()) { return string; } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusIconSlotVisibilityStore.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusIconSlotVisibilityStore.java index 00a840f..ce37545 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusIconSlotVisibilityStore.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusIconSlotVisibilityStore.java @@ -1,6 +1,8 @@ package se.ajpanton.statusbartweak.runtime.render; import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; public final class StatusIconSlotVisibilityStore { private static final Object LOCK = new Object(); @@ -9,7 +11,7 @@ public final class StatusIconSlotVisibilityStore { private StatusIconSlotVisibilityStore() { } - public static boolean setActiveSlots(ArrayList slots) { + public static boolean setActiveSlots(List slots) { ArrayList normalized = normalize(slots); synchronized (LOCK) { if (activeSlots.equals(normalized)) { @@ -26,17 +28,17 @@ public final class StatusIconSlotVisibilityStore { } } - private static ArrayList normalize(ArrayList slots) { - ArrayList normalized = new ArrayList<>(); + private static ArrayList normalize(List slots) { if (slots == null) { - return normalized; + return new ArrayList<>(); } + LinkedHashSet normalized = new LinkedHashSet<>(); for (String slot : slots) { - if (slot == null || slot.isEmpty() || normalized.contains(slot)) { + if (slot == null || slot.isEmpty()) { continue; } normalized.add(slot); } - return normalized; + return new ArrayList<>(normalized); } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusbarHeightSource.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusbarHeightSource.java index dd284c2..d8968f2 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusbarHeightSource.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/StatusbarHeightSource.java @@ -1,11 +1,11 @@ package se.ajpanton.statusbartweak.runtime.render; -import android.content.res.Resources; import android.view.View; import android.view.ViewGroup; import java.util.ArrayDeque; +import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; import se.ajpanton.statusbartweak.runtime.mode.SceneKey; @@ -41,43 +41,39 @@ final class StatusbarHeightSource { return null; } - static int height(View root, SceneKey scene, RootAnchors anchors, Bounds fallbackBounds) { - Bounds bounds = bounds(root, scene, anchors); - if (isUsable(root, bounds)) { - return bounds.top + bounds.height; - } - if (isUsable(root, fallbackBounds)) { - return fallbackBounds.top + fallbackBounds.height; - } - return Math.max(1, root != null ? root.getHeight() : 1); - } - private static Bounds firstBoundsForIds(View root, String... idNames) { if (!(root instanceof ViewGroup rootGroup) || idNames == null || idNames.length == 0) { return null; } - for (String candidateId : idNames) { - ArrayDeque queue = new ArrayDeque<>(); - queue.add(rootGroup); - while (!queue.isEmpty()) { - View view = queue.removeFirst(); - if (candidateId.equals(resolveIdName(view))) { + Bounds bestMatch = null; + int bestIndex = idNames.length; + ArrayDeque queue = new ArrayDeque<>(); + queue.add(rootGroup); + while (!queue.isEmpty()) { + View view = queue.removeFirst(); + String idName = ViewIdNames.idName(view); + for (int i = 0; i < bestIndex; i++) { + if (idNames[i].equals(idName)) { Bounds bounds = ViewGeometry.boundsRelativeTo(root, view); if (isUsable(root, bounds)) { - return bounds; + if (i == 0) { + return bounds; + } + bestMatch = bounds; + bestIndex = i; } } - if (view instanceof ViewGroup group) { - for (int i = 0; i < group.getChildCount(); i++) { - View child = group.getChildAt(i); - if (child != null) { - queue.addLast(child); - } + } + if (view instanceof ViewGroup group) { + for (int i = 0; i < group.getChildCount(); i++) { + View child = group.getChildAt(i); + if (child != null) { + queue.addLast(child); } } } } - return null; + return bestMatch; } private static boolean isUsable(View root, Bounds bounds) { @@ -92,14 +88,4 @@ final class StatusbarHeightSource { && bottom <= root.getHeight(); } - private static String resolveIdName(View view) { - if (view == null || view.getId() == View.NO_ID) { - return ""; - } - try { - return view.getResources().getResourceEntryName(view.getId()); - } catch (Resources.NotFoundException ignored) { - return ""; - } - } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/TextVerticalOffsetScaler.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/TextVerticalOffsetScaler.java new file mode 100644 index 0000000..c490259 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/TextVerticalOffsetScaler.java @@ -0,0 +1,18 @@ +package se.ajpanton.statusbartweak.runtime.render; + +import android.widget.TextView; + +final class TextVerticalOffsetScaler { + private float baselineTextSizePx; + + int scale(int offsetPx, TextView source) { + if (offsetPx == 0 || source == null || source.getTextSize() <= 0f) { + return offsetPx; + } + float textSize = source.getTextSize(); + if (baselineTextSizePx <= 0f || textSize < baselineTextSizePx) { + baselineTextSizePx = textSize; + } + return Math.round(offsetPx * textSize / baselineTextSizePx); + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java index 384bf57..8b67fab 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java @@ -7,9 +7,9 @@ import android.widget.TextView; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import java.util.WeakHashMap; -import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; import se.ajpanton.statusbartweak.runtime.features.clock.ClockLayoutTextFactory; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle; @@ -24,14 +24,13 @@ import se.ajpanton.statusbartweak.shell.settings.SbtSettings; * into owned unlocked rows instead of copying each stock X position. */ public final class UnlockedIconSnapshotRenderController { - private final RuntimeContext runtimeContext; private final SnapshotRenderer statusRenderer; private final SnapshotRenderer notificationRenderer; private final SnapshotRenderer chipRenderer; private final ClockProxyRenderer clockRenderer = new ClockProxyRenderer(); private final ClockProxyRenderer carrierRenderer = new ClockProxyRenderer(); private final UnlockedStockSourceCollector sourceCollector; - private final UnlockedVerticalOffsetScaler offsetScaler = new UnlockedVerticalOffsetScaler(); + private final TextVerticalOffsetScaler textOffsetScaler = new TextVerticalOffsetScaler(); private final UnlockedChipPlacementController chipPlacementController; private final UnlockedLayoutPlanner layoutPlanner; private final WeakHashMap clockBaselineDeltaByRoot = new WeakHashMap<>(); @@ -45,12 +44,7 @@ public final class UnlockedIconSnapshotRenderController { private final WeakHashMap forcedChipRefreshByRoot = new WeakHashMap<>(); public UnlockedIconSnapshotRenderController() { - this(null); - } - - public UnlockedIconSnapshotRenderController(RuntimeContext runtimeContext) { - this.runtimeContext = runtimeContext; - sourceCollector = new UnlockedStockSourceCollector(runtimeContext); + sourceCollector = new UnlockedStockSourceCollector(); statusRenderer = new SnapshotRenderer(true, true, false); notificationRenderer = new SnapshotRenderer(false, false, false); chipRenderer = new SnapshotRenderer(false, false, true); @@ -60,8 +54,7 @@ public final class UnlockedIconSnapshotRenderController { notificationRenderer, chipRenderer, chipPlacementController, - offsetScaler, - runtimeContext); + textOffsetScaler); } public RenderResult renderUnlocked( @@ -110,7 +103,7 @@ public final class UnlockedIconSnapshotRenderController { LayoutInputSignature previousSignature = lastLayoutSignatureByRoot.get(root); boolean forceChipRefresh = Boolean.TRUE.equals(forcedChipRefreshByRoot.remove(root)); boolean sceneChanged = previousSignature != null - && !java.util.Objects.equals(signature.scene, previousSignature.scene); + && !Objects.equals(signature.scene, previousSignature.scene); boolean stockSourcesChanged = previousSignature == null || signature.stockSurfaceSignature() != previousSignature.stockSurfaceSignature(); boolean chipSourcesChanged = previousSignature == null @@ -134,7 +127,7 @@ public final class UnlockedIconSnapshotRenderController { && previousIcons != null) { LayoutPlan previousPlan = lastLayoutPlanByRoot.get(root); if (previousPlan != null - && java.util.Objects.equals( + && Objects.equals( lastClockSourceGeometryByRoot.get(root), currentClockSourceGeometry)) { ClockPlacement updatedClock = clockPlacementWithTextFromModel( @@ -165,12 +158,11 @@ public final class UnlockedIconSnapshotRenderController { previousPlan != null ? previousPlan.clockPlacement : null, textPlacement); Integer previousClockGeometry = lastClockGeometryByRoot.get(root); - String missReason = clockFastPathMissReason( + if (canReuseClockFastPath( previousPlan, previousClockGeometry, currentClockGeometry, - updatedClock); - if (missReason == null) { + updatedClock)) { LayoutPlan updatedPlan = previousPlan.withClockPlacement(updatedClock); lastLayoutPlanByRoot.put(root, updatedPlan); clockRenderer.render(clockHost, updatedClock); @@ -308,7 +300,7 @@ public final class UnlockedIconSnapshotRenderController { return false; } SbtSettings settings = RuntimeSettingsCache.get(root.getContext()); - if (settings == null || !settings.layoutChipEnabledUnlocked) { + if (!settings.layoutChipEnabledUnlocked) { return false; } chipRenderer.renderPlacements(chipHost, layoutPlan.chipPlacements); @@ -332,7 +324,7 @@ public final class UnlockedIconSnapshotRenderController { return false; } SbtSettings settings = RuntimeSettingsCache.get(root.getContext()); - if (settings == null || !clockEnabledForScene(settings, scene)) { + if (!clockEnabledForScene(settings, scene)) { return false; } CollectedIcons collectedIcons = lastCollectedIconsByRoot.get(root); @@ -600,7 +592,7 @@ public final class UnlockedIconSnapshotRenderController { if (settings == null) { return 0; } - return java.util.Objects.hash( + return Objects.hash( settings.clockShowSeconds, settings.clockShowDatePrefix, settings.clockCustomFormatEnabled, @@ -704,7 +696,7 @@ public final class UnlockedIconSnapshotRenderController { source, model, UnlockedLayoutPlanner.positionFor(settings.clockPosition), - baselineY - offsetScaler.clock(settings.clockVerticalOffsetPx, source), + baselineY - textOffsetScaler.scale(settings.clockVerticalOffsetPx, source), root.getWidth(), cutoutBounds, settings.clockMiddleAutoNearestSide, @@ -871,11 +863,9 @@ public final class UnlockedIconSnapshotRenderController { result = 31 * result + source.getPaddingTop(); result = 31 * result + source.getPaddingRight(); result = 31 * result + source.getPaddingBottom(); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - result = 31 * result + Float.floatToIntBits(source.getLetterSpacing()); - String fontFeatures = source.getFontFeatureSettings(); - result = 31 * result + (fontFeatures != null ? fontFeatures.hashCode() : 0); - } + result = 31 * result + Float.floatToIntBits(source.getLetterSpacing()); + String fontFeatures = source.getFontFeatureSettings(); + result = 31 * result + (fontFeatures != null ? fontFeatures.hashCode() : 0); return result; } @@ -918,25 +908,25 @@ public final class UnlockedIconSnapshotRenderController { return null; } - private String clockFastPathMissReason( + private boolean canReuseClockFastPath( LayoutPlan previousPlan, Integer previousGeometry, Integer currentGeometry, ClockPlacement updatedClock ) { if (previousPlan == null) { - return "noPlan"; + return false; } if (currentGeometry == null) { - return "noGeometry"; + return false; } if (!currentGeometry.equals(previousGeometry)) { - return "geometryChanged"; + return false; } if (previousPlan.clockPlacement != null && updatedClock == null) { - return "rowMismatch"; + return false; } - return null; + return true; } private static int boundsGeometrySignature(Bounds bounds) { diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutBand.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutBand.java index a2698e4..179e63b 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutBand.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutBand.java @@ -4,6 +4,7 @@ import android.view.ViewGroup; import java.util.ArrayList; import java.util.Collections; +import java.util.Locale; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.AnchorSide; @@ -611,19 +612,6 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand { return result; } - private int shrinkableIconCount(ArrayList icons) { - int count = 0; - if (icons == null) { - return 0; - } - for (SnapshotPlacement icon : icons) { - if (isShrinkableIcon(icon)) { - count++; - } - } - return count; - } - private boolean hasOverflowDot(ArrayList icons) { for (SnapshotPlacement icon : icons) { if (icon.overflowDot) { @@ -709,7 +697,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand { return new SnapshotPlacement( null, new Bounds(0, rowTop, safeWidth, safeHeight), - SnapshotKeys.OVERFLOW_DOT + ":" + kind.name().toLowerCase(java.util.Locale.ROOT), + SnapshotKeys.OVERFLOW_DOT + ":" + kind.name().toLowerCase(Locale.ROOT), false, true, dotColor(), @@ -762,7 +750,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand { return new SnapshotPlacement( null, new Bounds(0, rowTop(activePlacements()), 1, height), - SnapshotKeys.EMPTY_ICON_CONTAINER + ":" + kind.name().toLowerCase(java.util.Locale.ROOT), + SnapshotKeys.EMPTY_ICON_CONTAINER + ":" + kind.name().toLowerCase(Locale.ROOT), false, false, null, diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java index 3985cf1..6b1dba3 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java @@ -5,12 +5,13 @@ import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; +import java.util.IdentityHashMap; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import java.util.WeakHashMap; import se.ajpanton.statusbartweak.R; -import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.AnchorSide; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; @@ -28,7 +29,7 @@ final class UnlockedLayoutPlanner { private final SnapshotRenderer notificationRenderer; private final SnapshotRenderer chipRenderer; private final UnlockedChipPlacementController chipPlacementController; - private final UnlockedVerticalOffsetScaler offsetScaler; + private final TextVerticalOffsetScaler textOffsetScaler; private final NotificationSeenAppReporter notificationSeenAppReporter = new NotificationSeenAppReporter(); private final WeakHashMap aodStatusRightInsetByRoot = new WeakHashMap<>(); @@ -40,14 +41,13 @@ final class UnlockedLayoutPlanner { SnapshotRenderer notificationRenderer, SnapshotRenderer chipRenderer, UnlockedChipPlacementController chipPlacementController, - UnlockedVerticalOffsetScaler offsetScaler, - RuntimeContext runtimeContext + TextVerticalOffsetScaler textOffsetScaler ) { this.statusRenderer = statusRenderer; this.notificationRenderer = notificationRenderer; this.chipRenderer = chipRenderer; this.chipPlacementController = chipPlacementController; - this.offsetScaler = offsetScaler; + this.textOffsetScaler = textOffsetScaler; } LayoutPlan solve( @@ -307,7 +307,10 @@ final class UnlockedLayoutPlanner { if (bands == null || bands.size() < 2 || itemOrder == null || itemOrder.isEmpty()) { return; } - ArrayList originalOrder = new ArrayList<>(bands); + IdentityHashMap originalOrder = new IdentityHashMap<>(); + for (int i = 0; i < bands.size(); i++) { + originalOrder.put(bands.get(i), i); + } bands.sort((left, right) -> { int orderComparison = Integer.compare( orderIndex(itemOrder, left != null ? left.kind : null), @@ -315,7 +318,9 @@ final class UnlockedLayoutPlanner { if (orderComparison != 0) { return orderComparison; } - return Integer.compare(originalOrder.indexOf(left), originalOrder.indexOf(right)); + return Integer.compare( + originalOrder.getOrDefault(left, Integer.MAX_VALUE), + originalOrder.getOrDefault(right, Integer.MAX_VALUE)); }); } @@ -440,7 +445,7 @@ final class UnlockedLayoutPlanner { boxes); if ((band.kind == LayoutItemKind.NOTIFICATIONS || band.kind == LayoutItemKind.STATUS) && !isFixedDotBand(band)) { - item.iconGroup = packedKind(band.kind).toLowerCase(java.util.Locale.ROOT); + item.iconGroup = packedKind(band.kind).toLowerCase(Locale.ROOT); item.iconCount = band.forceOverflowDotOnly ? 1 : band.rawPlacements != null ? band.rawPlacements.size() : 0; @@ -544,8 +549,8 @@ final class UnlockedLayoutPlanner { } private void configurePackedIconGroups(ArrayList items) { - java.util.LinkedHashMap> groups = - new java.util.LinkedHashMap<>(); + LinkedHashMap> groups = + new LinkedHashMap<>(); for (PackedStatusBarLayoutSolver.LayoutItem item : items) { if (item.iconGroup != null) { groups.computeIfAbsent(item.iconGroup, ignored -> new ArrayList<>()).add(item); @@ -672,7 +677,7 @@ final class UnlockedLayoutPlanner { : ClockLayout.centeredTextBaseline( collectedIcons.carrierView, sourceHeight > 0 ? sourceHeight : anchorBounds.height); - int verticalOffset = offsetScaler.clock( + int verticalOffset = textOffsetScaler.scale( settings.layoutCarrierVerticalOffsetPx, collectedIcons.carrierView); return ClockLayout.simpleText( @@ -1183,13 +1188,6 @@ final class UnlockedLayoutPlanner { return result; } - private int representativeHeight(ArrayList placements) { - if (placements == null || placements.isEmpty()) { - return 0; - } - return Math.max(0, placements.get(0).bounds.height); - } - private void splitClockIfNeeded( View root, ArrayList bands, 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 0eebace..eca574c 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 @@ -1,6 +1,5 @@ package se.ajpanton.statusbartweak.runtime.render; -import android.content.res.Resources; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.View; @@ -10,10 +9,13 @@ import android.widget.TextView; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Objects; import java.util.WeakHashMap; -import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; +import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle; @@ -23,13 +25,6 @@ import se.ajpanton.statusbartweak.shell.settings.SbtSettings; final class UnlockedStockSourceCollector { private final WeakHashMap anchorsByRoot = new WeakHashMap<>(); - UnlockedStockSourceCollector() { - this(null); - } - - UnlockedStockSourceCollector(RuntimeContext runtimeContext) { - } - void clear() { anchorsByRoot.clear(); } @@ -72,7 +67,7 @@ final class UnlockedStockSourceCollector { if (scene == null || !scene.isAod()) { mergeActiveHiddenStatusSources(root, anchors.statusIcons, statusIcons); } - if (anchors.battery != null && !isDescendantOf(anchors.battery, anchors.statusRoot)) { + if (anchors.battery != null && !ViewGeometry.isDescendantOf(anchors.battery, anchors.statusRoot)) { collectStatusSnapshotViews(anchors.battery, statusIcons); } normalizeStatusIconSourceBounds(root, statusIcons); @@ -119,7 +114,7 @@ final class UnlockedStockSourceCollector { } private String reflectString(View view, String methodName, String... fieldNames) { - Object methodValue = ReflectionSupport.invokeMethod(view, methodName, new Class[0]); + Object methodValue = ReflectionSupport.invokeMethod(view, methodName); if (methodValue != null) { return safeText(String.valueOf(methodValue)); } @@ -196,7 +191,7 @@ final class UnlockedStockSourceCollector { loggingAwareSettingsSignature(root, settings), clockTimeSignature(settings), viewSignatureOrZero(anchors != null ? anchors.clockView : null), - shallowTreeSignature(anchors != null ? anchors.clockContainer : null), + shallowTreeSignatureExcluding(anchors != null ? anchors.clockContainer : null, null), shallowTreeSignatureExcluding( anchors != null ? anchors.statusRoot : null, anchors != null ? anchors.clockContainer : null), @@ -236,8 +231,8 @@ final class UnlockedStockSourceCollector { } Bounds statusBounds = AodStatusbarSourceStore.statusBoundsForRoot(root, notificationContainer); int result = 17; - result = 31 * result + boundsSignature(notificationBounds); - result = 31 * result + boundsSignature(statusBounds); + result = 31 * result + (notificationBounds != null ? notificationBounds.signature() : 0); + result = 31 * result + (statusBounds != null ? statusBounds.signature() : 0); return result; } @@ -289,7 +284,7 @@ final class UnlockedStockSourceCollector { queue.add(root); while (!queue.isEmpty()) { View view = queue.removeFirst(); - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); if (clockContainer == null && idName.equals("left_clock_container")) { clockContainer = view; } @@ -352,8 +347,8 @@ final class UnlockedStockSourceCollector { if (view == null) { return false; } - String idName = resolveIdName(view).toLowerCase(java.util.Locale.ROOT); - String className = view.getClass().getName().toLowerCase(java.util.Locale.ROOT); + String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT); + String className = view.getClass().getName().toLowerCase(Locale.ROOT); CharSequence text = view.getText(); return (idName.contains("carrier") || className.contains("carrier")) && text != null @@ -395,10 +390,10 @@ final class UnlockedStockSourceCollector { ArrayList out, boolean includeHidden ) { - if (isOwnedRuntimeView(source)) { + if (ViewGeometry.isOwnedRuntimeView(source)) { return; } - boolean candidate = isStatusSnapshotCandidate(source); + boolean candidate = isStatusSnapshotCandidate(source, false); if (includeHidden) { candidate = isStatusSnapshotCandidate(source, true); } @@ -435,7 +430,7 @@ final class UnlockedStockSourceCollector { } LinkedHashMap existingBySlot = new LinkedHashMap<>(); for (SnapshotSource source : out) { - if (source != null && isDescendantOf(source.view(), statusIcons)) { + if (source != null && ViewGeometry.isDescendantOf(source.view(), statusIcons)) { String slot = reflectString(source.view(), "getSlot", "mSlot", "slot"); if (!slot.isEmpty()) { existingBySlot.put(slot, source); @@ -486,7 +481,7 @@ final class UnlockedStockSourceCollector { ArrayList merged = new ArrayList<>(); boolean inserted = false; for (SnapshotSource source : out) { - if (source != null && isDescendantOf(source.view(), statusIcons)) { + if (source != null && ViewGeometry.isDescendantOf(source.view(), statusIcons)) { if (!inserted) { merged.addAll(mergedStatusIcons); inserted = true; @@ -653,7 +648,7 @@ final class UnlockedStockSourceCollector { ) { if (sources != null) { for (SnapshotSource source : sources) { - if (source == null || !isDescendantOf(source.view(), statusIcons)) { + if (source == null || !ViewGeometry.isDescendantOf(source.view(), statusIcons)) { continue; } Bounds bounds = ViewGeometry.boundsRelativeTo(root, source.view()); @@ -709,13 +704,13 @@ final class UnlockedStockSourceCollector { queue.add(root); while (!queue.isEmpty()) { View view = queue.removeFirst(); - if (isStatusChipSnapshotCandidate(root, view)) { + if (isStatusChipSnapshotCandidate(root, view, false)) { SnapshotSource source = statusChipSource(root, view); visibleOut.add(source); metricOut.add(source); return; } - if (metricOut.isEmpty() && isStatusChipMetricCandidate(root, view)) { + if (metricOut.isEmpty() && isStatusChipMetricCandidate(root, view, false)) { metricOut.add(statusChipSource(root, view)); } if (view instanceof ViewGroup group) { @@ -726,10 +721,6 @@ final class UnlockedStockSourceCollector { } } - private boolean isStatusChipSnapshotCandidate(View root, View view) { - return isStatusChipSnapshotCandidate(root, view, false); - } - private SnapshotSource statusChipSource(View root, View view) { Bounds bounds = ViewGeometry.boundsRelativeTo(root, view); if (bounds == null || bounds.width <= 0 || bounds.height <= 0) { @@ -755,7 +746,7 @@ final class UnlockedStockSourceCollector { if (view == null || view == root || !view.isAttachedToWindow() - || !isDescendantOf(view, root) + || !ViewGeometry.isDescendantOf(view, root) || ReflectionSupport.getBooleanField(view, "sbtStatusChipForcedHidden", false) || !isStatusChipCandidate(view) || !hasVisibleStatusChipContent(view)) { @@ -833,10 +824,6 @@ final class UnlockedStockSourceCollector { return false; } - private boolean isStatusChipMetricCandidate(View root, View view) { - return isStatusChipMetricCandidate(root, view, false); - } - private boolean isStatusChipMetricCandidate(View root, View view, boolean allowHidden) { if (view == null || view == root || root == null) { return false; @@ -867,8 +854,8 @@ final class UnlockedStockSourceCollector { } private boolean isStatusChipCandidate(View view) { - String className = view.getClass().getName().toLowerCase(java.util.Locale.ROOT); - String idName = resolveIdName(view).toLowerCase(java.util.Locale.ROOT); + String className = view.getClass().getName().toLowerCase(Locale.ROOT); + String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT); if (className.contains("privacy") || idName.contains("privacy")) { return false; } @@ -877,12 +864,8 @@ final class UnlockedStockSourceCollector { || "ongoing_activity_capsule".equals(idName); } - private boolean isStatusSnapshotCandidate(View view) { - return isStatusSnapshotCandidate(view, false); - } - private boolean isStatusSnapshotCandidate(View view, boolean includeHidden) { - if (isOwnedRuntimeView(view)) { + if (ViewGeometry.isOwnedRuntimeView(view)) { return false; } if (!includeHidden && view.getVisibility() != View.VISIBLE) { @@ -893,7 +876,7 @@ final class UnlockedStockSourceCollector { if (width <= 0 || height <= 0) { return false; } - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); if (idName.equals("battery") || containsAny(idName, "wifi", "mobile", "signal")) { return true; } @@ -912,7 +895,7 @@ final class UnlockedStockSourceCollector { if (view instanceof ImageView) { return true; } - String idName = resolveIdName(view); + String idName = ViewIdNames.idName(view); String className = view.getClass().getName(); return idName.equals("battery") || containsAny(idName, "wifi", "mobile", "signal") @@ -923,7 +906,7 @@ final class UnlockedStockSourceCollector { } private boolean isNotificationSnapshotCandidate(View view) { - if (isOwnedRuntimeView(view)) { + if (ViewGeometry.isOwnedRuntimeView(view)) { return false; } if (view instanceof TextView) { @@ -934,7 +917,7 @@ final class UnlockedStockSourceCollector { } int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth(); int height = view.getHeight() > 0 ? view.getHeight() : view.getMeasuredHeight(); - if (width <= 0 || height <= 0 || resolveIdName(view).equals("status_bar_dot")) { + if (width <= 0 || height <= 0 || ViewIdNames.idName(view).equals("status_bar_dot")) { return false; } String className = view.getClass().getName(); @@ -944,7 +927,7 @@ final class UnlockedStockSourceCollector { } private boolean containsAny(String value, String... needles) { - String lowerValue = value.toLowerCase(java.util.Locale.ROOT); + String lowerValue = value.toLowerCase(Locale.ROOT); for (String needle : needles) { if (lowerValue.contains(needle)) { return true; @@ -953,42 +936,10 @@ final class UnlockedStockSourceCollector { return false; } - private boolean isDescendantOf(View child, View ancestor) { - if (child == null || ancestor == null) { - return false; - } - View current = child; - while (current != null) { - if (current == ancestor) { - return true; - } - Object parent = current.getParent(); - current = parent instanceof View ? (View) parent : null; - } - return false; - } - - private boolean isOwnedRuntimeView(View view) { - View current = view; - while (current != null) { - Object tag = current.getTag(); - if (tag instanceof String value && value.startsWith("statusbartweak.")) { - return true; - } - Object parent = current.getParent(); - current = parent instanceof View ? (View) parent : null; - } - return false; - } - private static int viewSignatureOrZero(View view) { return view != null ? viewSignature(view) : 0; } - private static int shallowTreeSignature(View view) { - return shallowTreeSignatureExcluding(view, null); - } - private static int shallowTreeSignatureExcluding(View view, View excluded) { if (view == null) { return 0; @@ -1174,7 +1125,7 @@ final class UnlockedStockSourceCollector { result = 31 * result + view.getPaddingBottom(); CharSequence contentDescription = view.getContentDescription(); result = 31 * result + (contentDescription != null ? contentDescription.toString().hashCode() : 0); - result = 31 * result + java.util.Arrays.hashCode(view.getDrawableState()); + result = 31 * result + Arrays.hashCode(view.getDrawableState()); if (view instanceof TextView textView) { CharSequence text = textView.getText(); result = 31 * result + (text != null ? text.toString().hashCode() : 0); @@ -1192,26 +1143,14 @@ final class UnlockedStockSourceCollector { return result; } - private static int boundsSignature(Bounds bounds) { - if (bounds == null) { - return 0; - } - int result = 17; - result = 31 * result + bounds.left; - result = 31 * result + bounds.top; - result = 31 * result + bounds.width; - result = 31 * result + bounds.height; - return result; - } - - private static int drawableLayoutSignature(android.graphics.drawable.Drawable drawable) { + private static int drawableLayoutSignature(Drawable drawable) { if (drawable == null) { return 0; } int result = 17; result = 31 * result + System.identityHashCode(drawable); result = 31 * result + drawable.getLevel(); - result = 31 * result + java.util.Arrays.hashCode(drawable.getState()); + result = 31 * result + Arrays.hashCode(drawable.getState()); Rect bounds = drawable.getBounds(); result = 31 * result + bounds.left; result = 31 * result + bounds.top; @@ -1220,17 +1159,6 @@ final class UnlockedStockSourceCollector { return result; } - private String resolveIdName(View view) { - if (view.getId() == View.NO_ID) { - return ""; - } - try { - return view.getResources().getResourceEntryName(view.getId()); - } catch (Resources.NotFoundException ignored) { - return ""; - } - } - private int clockTimeSignature(SbtSettings settings) { if (settings == null || !settings.clockEnabledUnlocked) { return 0; @@ -1253,7 +1181,7 @@ final class UnlockedStockSourceCollector { if (settings == null) { return 0; } - return java.util.Objects.hash( + return Objects.hash( settings.clockEnabled, settings.clockEnabledUnlocked, settings.clockPosition, @@ -1291,44 +1219,44 @@ final class UnlockedStockSourceCollector { settings.layoutNotifSortOrder, settings.layoutNotifEnabledUnlocked, settings.layoutNotifUnlockedCount, - java.util.Arrays.hashCode(settings.layoutNotifPositions), - java.util.Arrays.hashCode(settings.layoutNotifMiddleSides), - java.util.Arrays.hashCode(settings.layoutNotifMiddleAutoNearestSides), - java.util.Arrays.hashCode(settings.layoutNotifVerticalOffsetsPx), + Arrays.hashCode(settings.layoutNotifPositions), + Arrays.hashCode(settings.layoutNotifMiddleSides), + Arrays.hashCode(settings.layoutNotifMiddleAutoNearestSides), + Arrays.hashCode(settings.layoutNotifVerticalOffsetsPx), settings.layoutNotifEnabledLockscreen, settings.layoutNotifLockCount, - java.util.Arrays.hashCode(settings.layoutNotifLockPositions), - java.util.Arrays.hashCode(settings.layoutNotifLockMiddleSides), - java.util.Arrays.hashCode(settings.layoutNotifLockMiddleAutoNearestSides), - java.util.Arrays.hashCode(settings.layoutNotifLockVerticalOffsetsPx), + Arrays.hashCode(settings.layoutNotifLockPositions), + Arrays.hashCode(settings.layoutNotifLockMiddleSides), + Arrays.hashCode(settings.layoutNotifLockMiddleAutoNearestSides), + Arrays.hashCode(settings.layoutNotifLockVerticalOffsetsPx), settings.layoutNotifEnabledAod, settings.layoutNotifAodCount, - java.util.Arrays.hashCode(settings.layoutNotifAodPositions), - java.util.Arrays.hashCode(settings.layoutNotifAodMiddleSides), - java.util.Arrays.hashCode(settings.layoutNotifAodMiddleAutoNearestSides), - java.util.Arrays.hashCode(settings.layoutNotifAodVerticalOffsetsPx), + Arrays.hashCode(settings.layoutNotifAodPositions), + Arrays.hashCode(settings.layoutNotifAodMiddleSides), + Arrays.hashCode(settings.layoutNotifAodMiddleAutoNearestSides), + Arrays.hashCode(settings.layoutNotifAodVerticalOffsetsPx), settings.layoutNotifMiddleSide, settings.layoutNotifMiddleAutoNearestSide, settings.layoutNotifVerticalOffsetPx, settings.layoutStatusPosition, settings.layoutStatusEnabledUnlocked, settings.layoutStatusUnlockedCount, - java.util.Arrays.hashCode(settings.layoutStatusPositions), - java.util.Arrays.hashCode(settings.layoutStatusMiddleSides), - java.util.Arrays.hashCode(settings.layoutStatusMiddleAutoNearestSides), - java.util.Arrays.hashCode(settings.layoutStatusVerticalOffsetsPx), + Arrays.hashCode(settings.layoutStatusPositions), + Arrays.hashCode(settings.layoutStatusMiddleSides), + Arrays.hashCode(settings.layoutStatusMiddleAutoNearestSides), + Arrays.hashCode(settings.layoutStatusVerticalOffsetsPx), settings.layoutStatusEnabledLockscreen, settings.layoutStatusLockCount, - java.util.Arrays.hashCode(settings.layoutStatusLockPositions), - java.util.Arrays.hashCode(settings.layoutStatusLockMiddleSides), - java.util.Arrays.hashCode(settings.layoutStatusLockMiddleAutoNearestSides), - java.util.Arrays.hashCode(settings.layoutStatusLockVerticalOffsetsPx), + Arrays.hashCode(settings.layoutStatusLockPositions), + Arrays.hashCode(settings.layoutStatusLockMiddleSides), + Arrays.hashCode(settings.layoutStatusLockMiddleAutoNearestSides), + Arrays.hashCode(settings.layoutStatusLockVerticalOffsetsPx), settings.layoutStatusEnabledAod, settings.layoutStatusAodCount, - java.util.Arrays.hashCode(settings.layoutStatusAodPositions), - java.util.Arrays.hashCode(settings.layoutStatusAodMiddleSides), - java.util.Arrays.hashCode(settings.layoutStatusAodMiddleAutoNearestSides), - java.util.Arrays.hashCode(settings.layoutStatusAodVerticalOffsetsPx), + Arrays.hashCode(settings.layoutStatusAodPositions), + Arrays.hashCode(settings.layoutStatusAodMiddleSides), + Arrays.hashCode(settings.layoutStatusAodMiddleAutoNearestSides), + Arrays.hashCode(settings.layoutStatusAodVerticalOffsetsPx), settings.layoutStatusMiddleSide, settings.layoutStatusMiddleAutoNearestSide, settings.layoutStatusVerticalOffsetPx, diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedVerticalOffsetScaler.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedVerticalOffsetScaler.java deleted file mode 100644 index cbfff03..0000000 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedVerticalOffsetScaler.java +++ /dev/null @@ -1,48 +0,0 @@ -package se.ajpanton.statusbartweak.runtime.render; - -import android.widget.TextView; - -final class UnlockedVerticalOffsetScaler { - private int baselineNotificationIconSizePx; - private int baselineStatusIconHeightPx; - private float baselineClockTextSizePx; - - int notification(int offsetPx, int currentIconHeightPx) { - baselineNotificationIconSizePx = updateBaseline( - baselineNotificationIconSizePx, - currentIconHeightPx); - return scaledOffset(offsetPx, currentIconHeightPx, baselineNotificationIconSizePx); - } - - int status(int offsetPx, int currentIconHeightPx) { - baselineStatusIconHeightPx = updateBaseline( - baselineStatusIconHeightPx, - currentIconHeightPx); - return scaledOffset(offsetPx, currentIconHeightPx, baselineStatusIconHeightPx); - } - - int clock(int offsetPx, TextView source) { - if (offsetPx == 0 || source == null || source.getTextSize() <= 0f) { - return offsetPx; - } - float textSize = source.getTextSize(); - if (baselineClockTextSizePx <= 0f || textSize < baselineClockTextSizePx) { - baselineClockTextSizePx = textSize; - } - return Math.round(offsetPx * textSize / baselineClockTextSizePx); - } - - private int updateBaseline(int baselinePx, int currentPx) { - if (currentPx <= 0) { - return baselinePx; - } - return baselinePx <= 0 || currentPx < baselinePx ? currentPx : baselinePx; - } - - private int scaledOffset(int offsetPx, int currentPx, int baselinePx) { - if (offsetPx == 0 || currentPx <= 0 || baselinePx <= 0) { - return offsetPx; - } - return Math.round((float) offsetPx * currentPx / baselinePx); - } -} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ViewGeometry.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ViewGeometry.java index c2ce41f..72ca74f 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ViewGeometry.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ViewGeometry.java @@ -10,6 +10,8 @@ import java.util.List; import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; final class ViewGeometry { + private static final String OWNED_RUNTIME_TAG_PREFIX = "statusbartweak."; + private ViewGeometry() { } @@ -34,6 +36,72 @@ final class ViewGeometry { return new Bounds(left, top, Math.max(0, width), Math.max(0, height)); } + static boolean isDescendantOf(View child, View ancestor) { + if (child == null || ancestor == null) { + return false; + } + View current = child; + while (current != null) { + if (current == ancestor) { + return true; + } + Object parent = current.getParent(); + current = parent instanceof View ? (View) parent : null; + } + return false; + } + + static boolean hasAncestorTagPrefix(View view, String prefix) { + if (view == null || prefix == null) { + return false; + } + View current = view; + while (current != null) { + Object tag = current.getTag(); + if (tag instanceof String value && value.startsWith(prefix)) { + return true; + } + Object parent = current.getParent(); + current = parent instanceof View ? (View) parent : null; + } + return false; + } + + static boolean isOwnedRuntimeView(View view) { + return hasAncestorTagPrefix(view, OWNED_RUNTIME_TAG_PREFIX); + } + + static Bounds clampToRoot(View root, Bounds bounds) { + if (root == null || bounds == null || root.getWidth() <= 0 || root.getHeight() <= 0) { + return null; + } + int left = Math.max(0, bounds.left); + int top = Math.max(0, bounds.top); + int right = Math.min(root.getWidth(), bounds.left + bounds.width); + int bottom = Math.min(root.getHeight(), bounds.top + bounds.height); + if (right <= left || bottom <= top) { + return null; + } + return new Bounds(left, top, right - left, bottom - top); + } + + static float effectiveAlpha(View view) { + return effectiveAlphaFrom(view); + } + + static float effectiveAncestorAlpha(View view) { + return view != null ? effectiveAlphaFrom(view.getParent()) : 1f; + } + + private static float effectiveAlphaFrom(Object current) { + float alpha = 1f; + while (current instanceof View currentView) { + alpha *= currentView.getAlpha(); + current = currentView.getParent(); + } + return Math.max(0f, Math.min(1f, alpha)); + } + static Rect middleCutout(View root) { if (root == null || root.getWidth() <= 0) { return null; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/settings/BroadcastReceiverSupport.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/settings/BroadcastReceiverSupport.java new file mode 100644 index 0000000..1644802 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/settings/BroadcastReceiverSupport.java @@ -0,0 +1,58 @@ +package se.ajpanton.statusbartweak.runtime.settings; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +public final class BroadcastReceiverSupport { + private BroadcastReceiverSupport() { + } + + public static IntentFilter filter(String firstAction, String... additionalActions) { + IntentFilter filter = new IntentFilter(firstAction); + if (additionalActions != null) { + for (String action : additionalActions) { + if (action != null) { + filter.addAction(action); + } + } + } + return filter; + } + + public static Intent registerExportedOnApplicationContext( + Context context, + BroadcastReceiver receiver, + IntentFilter filter + ) { + return applicationContext(context) + .registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED); + } + + public static Intent registerInvalidatingReceiver( + Context context, + Runnable invalidator, + IntentFilter filter + ) { + BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (invalidator != null) { + invalidator.run(); + } + } + }; + return registerExportedOnApplicationContext(context, receiver, filter); + } + + public static Intent readExportedSticky(Context context, IntentFilter filter) { + return applicationContext(context) + .registerReceiver(null, filter, Context.RECEIVER_EXPORTED); + } + + private static Context applicationContext(Context context) { + Context appContext = context.getApplicationContext(); + return appContext != null ? appContext : context; + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/settings/RuntimeSettingsCache.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/settings/RuntimeSettingsCache.java index 805161b..1596ede 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/settings/RuntimeSettingsCache.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/settings/RuntimeSettingsCache.java @@ -1,10 +1,7 @@ package se.ajpanton.statusbartweak.runtime.settings; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.os.Build; import se.ajpanton.statusbartweak.shell.settings.SbtSettings; @@ -38,7 +35,7 @@ public final class RuntimeSettingsCache { if (cachedSettings == null) { cachedSettings = SbtSettings.from(context); } - return cachedSettings != null ? cachedSettings : SbtSettings.defaults(); + return cachedSettings; } } @@ -56,23 +53,12 @@ public final class RuntimeSettingsCache { if (receiverRegistered) { return; } - Context appContext = context.getApplicationContext(); - if (appContext == null) { - appContext = context; - } - BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - invalidate(); - } - }; - IntentFilter filter = new IntentFilter(SbtSettings.ACTION_SETTINGS_CHANGED); - filter.addAction(Intent.ACTION_USER_UNLOCKED); - if (Build.VERSION.SDK_INT >= 33) { - appContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED); - } else { - appContext.registerReceiver(receiver, filter); - } + BroadcastReceiverSupport.registerInvalidatingReceiver( + context, + RuntimeSettingsCache::invalidate, + BroadcastReceiverSupport.filter( + SbtSettings.ACTION_SETTINGS_CHANGED, + Intent.ACTION_USER_UNLOCKED)); receiverRegistered = true; } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/debug/DebugNotifications.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/debug/DebugNotifications.java index 2c6044a..d294da4 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/debug/DebugNotifications.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/debug/DebugNotifications.java @@ -1,6 +1,7 @@ package se.ajpanton.statusbartweak.shell.debug; import android.Manifest; +import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -111,9 +112,7 @@ public final class DebugNotifications { cancelDebugNotificationsAbove(appContext, nm, desired); cleanupAutoGroupSummaries(appContext, nm); - for (int i = 1; i <= desired; i++) { - nm.notify(DEBUG_ID_BASE + i, buildDebugNotification(appContext, i)); - } + postDebugNotifications(appContext, nm, desired); if (desired > 0) { scheduleAutoGroupRecovery(appContext); } else { @@ -194,7 +193,7 @@ public final class DebugNotifications { int desired ) { for (StatusBarNotification sbn : nm.getActiveNotifications()) { - if (!context.getPackageName().equals(sbn.getPackageName())) { + if (!isOwnNotification(context, sbn)) { continue; } int debugNumber = sbn.getId() - DEBUG_ID_BASE; @@ -206,15 +205,7 @@ public final class DebugNotifications { private static void cleanupAutoGroupSummaries(Context context, NotificationManager nm) { for (StatusBarNotification sbn : nm.getActiveNotifications()) { - if (!context.getPackageName().equals(sbn.getPackageName()) || isDebugId(sbn.getId())) { - continue; - } - Notification notification = sbn.getNotification(); - if (notification == null || isDebugNotification(notification)) { - continue; - } - String group = notification.getGroup(); - if (group == null || !group.startsWith(GROUP_PREFIX)) { + if (isDebugAutoGroupSummary(context, sbn)) { cancel(nm, sbn); } } @@ -254,14 +245,22 @@ public final class DebugNotifications { try { prefs.edit().putBoolean(KEY_RECOVERY_USED, true).apply(); cancelDebugNotificationsAndSummaries(appContext, nm); - for (int i = 1; i <= desired; i++) { - nm.notify(DEBUG_ID_BASE + i, buildDebugNotification(appContext, i)); - } + postDebugNotifications(appContext, nm, desired); } finally { recoveryInProgress = false; } } + private static void postDebugNotifications( + Context context, + NotificationManager nm, + int desired + ) { + for (int i = 1; i <= desired; i++) { + nm.notify(DEBUG_ID_BASE + i, buildDebugNotification(context, i)); + } + } + private static void cancelAutoGroupRecovery() { if (recoveryRunnable != null) { RECOVERY_HANDLER.removeCallbacks(recoveryRunnable); @@ -271,15 +270,7 @@ public final class DebugNotifications { private static boolean hasAutoGroupSummary(Context context, NotificationManager nm) { for (StatusBarNotification sbn : nm.getActiveNotifications()) { - if (!context.getPackageName().equals(sbn.getPackageName()) || isDebugId(sbn.getId())) { - continue; - } - Notification notification = sbn.getNotification(); - if (notification == null || isDebugNotification(notification)) { - continue; - } - String group = notification.getGroup(); - if (group == null || !group.startsWith(GROUP_PREFIX)) { + if (isDebugAutoGroupSummary(context, sbn)) { return true; } } @@ -288,15 +279,10 @@ public final class DebugNotifications { private static void cancelDebugNotificationsAndSummaries(Context context, NotificationManager nm) { for (StatusBarNotification sbn : nm.getActiveNotifications()) { - if (!context.getPackageName().equals(sbn.getPackageName())) { + if (!isOwnNotification(context, sbn)) { continue; } - Notification notification = sbn.getNotification(); - boolean debugSummary = notification != null - && !isDebugNotification(notification) - && (notification.getGroup() == null - || !notification.getGroup().startsWith(GROUP_PREFIX)); - if (isDebugId(sbn.getId()) || debugSummary) { + if (isDebugId(sbn.getId()) || isDebugAutoGroupSummary(context, sbn)) { cancel(nm, sbn); } } @@ -323,6 +309,22 @@ public final class DebugNotifications { return debugNumber >= 1 && debugNumber <= MAX_COUNT; } + private static boolean isDebugAutoGroupSummary(Context context, StatusBarNotification sbn) { + if (!isOwnNotification(context, sbn) || isDebugId(sbn.getId())) { + return false; + } + Notification notification = sbn.getNotification(); + if (notification == null || isDebugNotification(notification)) { + return false; + } + String group = notification.getGroup(); + return group == null || !group.startsWith(GROUP_PREFIX); + } + + private static boolean isOwnNotification(Context context, StatusBarNotification sbn) { + return context.getPackageName().equals(sbn.getPackageName()); + } + private static void cancel(NotificationManager nm, StatusBarNotification sbn) { if (sbn.getTag() != null) { nm.cancel(sbn.getTag(), sbn.getId()); @@ -416,6 +418,7 @@ public final class DebugNotifications { return true; } + @SuppressLint("WakelockTimeout") private static void acquireCycleWakeLock(Context context) { if (cycleWakeLock == null) { PowerManager pm = context.getSystemService(PowerManager.class); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/debug/NotificationDisplayStyleSettings.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/debug/NotificationDisplayStyleSettings.java index 4936426..6098939 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/debug/NotificationDisplayStyleSettings.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/debug/NotificationDisplayStyleSettings.java @@ -5,6 +5,8 @@ import android.content.Context; import android.provider.Settings; import android.text.TextUtils; +import java.io.IOException; + public final class NotificationDisplayStyleSettings { private static final String KEY_LOCKSCREEN_MINIMIZING_NOTIFICATION = "lockscreen_minimizing_notification"; @@ -53,7 +55,7 @@ public final class NotificationDisplayStyleSettings { } try { return Settings.System.getInt(resolver, key); - } catch (Throwable ignored) { + } catch (Settings.SettingNotFoundException ignored) { return null; } } @@ -67,7 +69,10 @@ public final class NotificationDisplayStyleSettings { .redirectErrorStream(true) .start(); return process.waitFor() == 0; - } catch (Throwable ignored) { + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + return false; + } catch (IOException | SecurityException ignored) { return false; } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/AppIconRules.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/AppIconRules.java index cbb036e..2a7473a 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/AppIconRules.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/AppIconRules.java @@ -32,6 +32,7 @@ public final class AppIconRules { public static final int MODE_AOD = 1; public static final int MODE_LOCK = 1 << 1; public static final int MODE_UNLOCK = 1 << 2; + private static final int ALL_MODES = MODE_AOD | MODE_LOCK | MODE_UNLOCK; private static final String MODE_NAME_AOD = "AOD"; private static final String MODE_NAME_LOCK = "LOCK"; private static final String MODE_NAME_UNLOCK = "UNLOCK"; @@ -62,51 +63,27 @@ public final class AppIconRules { } public static Map loadBlockedModes(SharedPreferences prefs) { - if (prefs == null) { - return new HashMap<>(); - } - Set raw = prefs.getStringSet(PREF_KEY_BLOCKED_MODES, Collections.emptySet()); - return parseBlockedModes(raw); + return parseBlockedModes(readStringSet(prefs, PREF_KEY_BLOCKED_MODES)); } public static void saveBlockedModes(SharedPreferences prefs, Map map) { - if (prefs == null) { - return; - } - Set encoded = encodeBlockedModes(map); - prefs.edit().putStringSet(PREF_KEY_BLOCKED_MODES, encoded).apply(); + writeStringSet(prefs, PREF_KEY_BLOCKED_MODES, encodeBlockedModes(map)); } public static Map loadSeenSession(SharedPreferences prefs) { - if (prefs == null) { - return new HashMap<>(); - } - Set raw = prefs.getStringSet(PREF_KEY_SEEN_SESSION, Collections.emptySet()); - return parseSeenSession(raw); + return parseSeenSession(readStringSet(prefs, PREF_KEY_SEEN_SESSION)); } public static Map> loadExceptionRules(SharedPreferences prefs) { - if (prefs == null) { - return new HashMap<>(); - } - Set raw = prefs.getStringSet(PREF_KEY_EXCEPTION_RULES, Collections.emptySet()); - return parseExceptionRules(raw); + return parseExceptionRules(readStringSet(prefs, PREF_KEY_EXCEPTION_RULES)); } public static void saveExceptionRules(SharedPreferences prefs, Map> map) { - if (prefs == null) { - return; - } - Set encoded = encodeExceptionRules(map); - prefs.edit().putStringSet(PREF_KEY_EXCEPTION_RULES, encoded).apply(); + writeStringSet(prefs, PREF_KEY_EXCEPTION_RULES, encodeExceptionRules(map)); } public static void saveSeenSession(SharedPreferences prefs, Map map) { - if (prefs == null) { - return; - } - Set encoded = encodeSeenSession(map); - prefs.edit().putStringSet(PREF_KEY_SEEN_SESSION, encoded).apply(); + writeStringSet(prefs, PREF_KEY_SEEN_SESSION, encodeSeenSession(map)); } public static void clearSeenSession(SharedPreferences prefs) { @@ -116,6 +93,20 @@ public final class AppIconRules { prefs.edit().remove(PREF_KEY_SEEN_SESSION).apply(); } + private static Set readStringSet(SharedPreferences prefs, String key) { + if (prefs == null) { + return Collections.emptySet(); + } + return prefs.getStringSet(key, Collections.emptySet()); + } + + private static void writeStringSet(SharedPreferences prefs, String key, Set value) { + if (prefs == null) { + return; + } + prefs.edit().putStringSet(key, value).apply(); + } + public static boolean noteSeen(Map seen, String pkg, long seenMs, @@ -226,7 +217,7 @@ public final class AppIconRules { if (rawSet == null || rawSet.isEmpty()) { return out; } - for (String entry : new HashSet<>(rawSet)) { + for (String entry : snapshot(rawSet)) { if (entry == null || entry.isEmpty()) { continue; } @@ -284,7 +275,7 @@ public final class AppIconRules { if (rawSet == null || rawSet.isEmpty()) { return new HashMap<>(); } - for (String entry : new HashSet<>(rawSet)) { + for (String entry : snapshot(rawSet)) { if (entry == null || entry.isEmpty()) { continue; } @@ -312,7 +303,7 @@ public final class AppIconRules { try { byte[] decoded = Base64.decode(parts[3], Base64.NO_WRAP); regex = new String(decoded, StandardCharsets.UTF_8).trim(); - } catch (Throwable ignored) { + } catch (IllegalArgumentException ignored) { continue; } if (regex.isEmpty()) { @@ -366,8 +357,11 @@ public final class AppIconRules { } private static int sanitizeModeMask(int mask) { - int allowed = MODE_AOD | MODE_LOCK | MODE_UNLOCK; - return mask & allowed; + return mask & ALL_MODES; + } + + private static HashSet snapshot(Set rawSet) { + return new HashSet<>(rawSet); } private static String normalizeModeName(String mode) { @@ -395,7 +389,7 @@ public final class AppIconRules { if (rawSet == null || rawSet.isEmpty()) { return out; } - for (String entry : new HashSet<>(rawSet)) { + for (String entry : snapshot(rawSet)) { if (entry == null || entry.isEmpty()) { continue; } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/BatteryBarStyle.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/BatteryBarStyle.java index c8421ed..2f4d0f3 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/BatteryBarStyle.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/BatteryBarStyle.java @@ -3,10 +3,10 @@ package se.ajpanton.statusbartweak.shell.settings; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.graphics.Color; import android.net.Uri; import android.os.BatteryManager; -import android.os.Build; import java.util.ArrayList; import java.util.Collection; @@ -104,6 +104,13 @@ public final class BatteryBarStyle { return result; } + public static ArrayList loadThresholds(SharedPreferences prefs, String key) { + if (prefs == null || key == null || key.isEmpty()) { + return new ArrayList<>(); + } + return decodeThresholds(prefs.getStringSet(key, Collections.emptySet())); + } + public static ArrayList encodeThresholds(List thresholds) { ArrayList result = new ArrayList<>(); if (thresholds == null) { @@ -196,11 +203,7 @@ public final class BatteryBarStyle { IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent sticky = null; try { - if (Build.VERSION.SDK_INT >= 33) { - sticky = context.registerReceiver(null, filter, Context.RECEIVER_EXPORTED); - } else { - sticky = context.registerReceiver(null, filter); - } + sticky = context.registerReceiver(null, filter, Context.RECEIVER_EXPORTED); } catch (Throwable ignored) { } if (sticky != null) { diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/ClockCameraTypeSupport.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/ClockCameraTypeSupport.java index 8788b72..be67b63 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/ClockCameraTypeSupport.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/ClockCameraTypeSupport.java @@ -147,6 +147,13 @@ public final class ClockCameraTypeSupport { return out.isEmpty() ? Collections.emptyMap() : out; } + public static Map loadOverrides(SharedPreferences prefs, String key) { + if (prefs == null || TextUtils.isEmpty(key)) { + return Collections.emptyMap(); + } + return parseOverrides(prefs.getStringSet(key, Collections.emptySet())); + } + public static Set encodeOverrides(Map overrides) { if (overrides == null || overrides.isEmpty()) { return Collections.emptySet(); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java index e99ee90..848cfe0 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettings.java @@ -12,7 +12,7 @@ import java.util.Map; import java.util.Set; public final class SbtSettings { - private interface IndexedKey { + public interface IndexedKey { String key(int index); } @@ -21,6 +21,8 @@ public final class SbtSettings { public static final String ACTION_SETTINGS_CHANGED = "se.ajpanton.statusbartweak.action.SETTINGS_CHANGED"; public static final String PACKAGE_SYSTEMUI = "com.android.systemui"; + private static final String SETTINGS_PROVIDER_BASE_URI = + "content://" + SbtSettingsProvider.AUTHORITY; public static final String KEY_DEBUG_CURRENT_CAMERA_ID = "debug_current_camera_id"; public static final String KEY_DEBUG_CURRENT_CAMERA_AUTO_TYPE = "debug_current_camera_auto_type"; public static final String KEY_DEBUG_CURRENT_CAMERA_EFFECTIVE_TYPE = "debug_current_camera_effective_type"; @@ -117,6 +119,24 @@ public final class SbtSettings { public static final String KEY_LAYOUT_NOTIF_ENABLED_LOCKSCREEN = "layout_notif_enabled_lockscreen"; public static final String KEY_LAYOUT_NOTIF_ENABLED_UNLOCKED = "layout_notif_enabled_unlocked"; public static final String KEY_LAYOUT_NOTIF_UNLOCKED_COUNT = "layout_notif_unlocked_count"; + public static final String KEY_LAYOUT_NOTIF_LOCK_COUNT = "layout_lock_notifications_count"; + public static final String KEY_LAYOUT_NOTIF_AOD_COUNT = "layout_aod_notifications_count"; + public static final String KEY_LAYOUT_NOTIF_LOCK_POSITION = "layout_lock_notifications_position"; + public static final String KEY_LAYOUT_NOTIF_LOCK_MIDDLE_SIDE = "layout_lock_notifications_middle_side"; + public static final String KEY_LAYOUT_NOTIF_LOCK_MIDDLE_AUTO_NEAREST_SIDE = + "layout_lock_notifications_middle_auto_nearest_side"; + public static final String KEY_LAYOUT_NOTIF_LOCK_VERTICAL_OFFSET_PX = + "layout_lock_notifications_vertical_offset_px"; + public static final String KEY_LAYOUT_NOTIF_LOCK_ICON_HEIGHT_STEPS = + "layout_lock_notifications_icon_height_steps"; + public static final String KEY_LAYOUT_NOTIF_AOD_POSITION = "layout_aod_notifications_position"; + public static final String KEY_LAYOUT_NOTIF_AOD_MIDDLE_SIDE = "layout_aod_notifications_middle_side"; + public static final String KEY_LAYOUT_NOTIF_AOD_MIDDLE_AUTO_NEAREST_SIDE = + "layout_aod_notifications_middle_auto_nearest_side"; + public static final String KEY_LAYOUT_NOTIF_AOD_VERTICAL_OFFSET_PX = + "layout_aod_notifications_vertical_offset_px"; + public static final String KEY_LAYOUT_NOTIF_AOD_ICON_HEIGHT_STEPS = + "layout_aod_notifications_icon_height_steps"; public static final String KEY_LAYOUT_NOTIF_MIDDLE_SIDE = "layout_notif_middle_side"; public static final String KEY_LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE = "layout_notif_middle_auto_nearest_side"; public static final String KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX = "layout_notif_vertical_offset_px"; @@ -126,6 +146,24 @@ public final class SbtSettings { public static final String KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN = "layout_status_enabled_lockscreen"; public static final String KEY_LAYOUT_STATUS_ENABLED_UNLOCKED = "layout_status_enabled_unlocked"; public static final String KEY_LAYOUT_STATUS_UNLOCKED_COUNT = "layout_status_unlocked_count"; + public static final String KEY_LAYOUT_STATUS_LOCK_COUNT = "layout_lock_status_count"; + public static final String KEY_LAYOUT_STATUS_AOD_COUNT = "layout_aod_status_count"; + public static final String KEY_LAYOUT_STATUS_LOCK_POSITION = "layout_lock_status_position"; + public static final String KEY_LAYOUT_STATUS_LOCK_MIDDLE_SIDE = "layout_lock_status_middle_side"; + public static final String KEY_LAYOUT_STATUS_LOCK_MIDDLE_AUTO_NEAREST_SIDE = + "layout_lock_status_middle_auto_nearest_side"; + public static final String KEY_LAYOUT_STATUS_LOCK_VERTICAL_OFFSET_PX = + "layout_lock_status_vertical_offset_px"; + public static final String KEY_LAYOUT_STATUS_LOCK_ICON_HEIGHT_STEPS = + "layout_lock_status_icon_height_steps"; + public static final String KEY_LAYOUT_STATUS_AOD_POSITION = "layout_aod_status_position"; + public static final String KEY_LAYOUT_STATUS_AOD_MIDDLE_SIDE = "layout_aod_status_middle_side"; + public static final String KEY_LAYOUT_STATUS_AOD_MIDDLE_AUTO_NEAREST_SIDE = + "layout_aod_status_middle_auto_nearest_side"; + public static final String KEY_LAYOUT_STATUS_AOD_VERTICAL_OFFSET_PX = + "layout_aod_status_vertical_offset_px"; + public static final String KEY_LAYOUT_STATUS_AOD_ICON_HEIGHT_STEPS = + "layout_aod_status_icon_height_steps"; public static final String KEY_LAYOUT_STATUS_SHOW_DOT_IF_TRUNCATED = "layout_status_show_dot_if_truncated"; public static final String KEY_LAYOUT_STATUS_MIDDLE_SIDE = "layout_status_middle_side"; public static final String KEY_LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE = "layout_status_middle_auto_nearest_side"; @@ -169,87 +207,91 @@ public final class SbtSettings { } public static String layoutLockNotifPositionKey(int index) { - return suffixedKey("layout_lock_notifications_position", index); + return suffixedKey(KEY_LAYOUT_NOTIF_LOCK_POSITION, index); } public static String layoutLockNotifMiddleSideKey(int index) { - return suffixedKey("layout_lock_notifications_middle_side", index); + return suffixedKey(KEY_LAYOUT_NOTIF_LOCK_MIDDLE_SIDE, index); } public static String layoutLockNotifMiddleAutoNearestSideKey(int index) { - return suffixedKey("layout_lock_notifications_middle_auto_nearest_side", index); + return suffixedKey(KEY_LAYOUT_NOTIF_LOCK_MIDDLE_AUTO_NEAREST_SIDE, index); } public static String layoutLockNotifVerticalOffsetKey(int index) { - return suffixedKey("layout_lock_notifications_vertical_offset_px", index); + return suffixedKey(KEY_LAYOUT_NOTIF_LOCK_VERTICAL_OFFSET_PX, index); } public static String layoutLockNotifIconHeightKey() { - return "layout_lock_notifications_icon_height_steps"; + return KEY_LAYOUT_NOTIF_LOCK_ICON_HEIGHT_STEPS; } public static String layoutAodNotifPositionKey(int index) { - return suffixedKey("layout_aod_notifications_position", index); + return suffixedKey(KEY_LAYOUT_NOTIF_AOD_POSITION, index); } public static String layoutAodNotifMiddleSideKey(int index) { - return suffixedKey("layout_aod_notifications_middle_side", index); + return suffixedKey(KEY_LAYOUT_NOTIF_AOD_MIDDLE_SIDE, index); } public static String layoutAodNotifMiddleAutoNearestSideKey(int index) { - return suffixedKey("layout_aod_notifications_middle_auto_nearest_side", index); + return suffixedKey(KEY_LAYOUT_NOTIF_AOD_MIDDLE_AUTO_NEAREST_SIDE, index); } public static String layoutAodNotifVerticalOffsetKey(int index) { - return suffixedKey("layout_aod_notifications_vertical_offset_px", index); + return suffixedKey(KEY_LAYOUT_NOTIF_AOD_VERTICAL_OFFSET_PX, index); } public static String layoutAodNotifIconHeightKey() { - return "layout_aod_notifications_icon_height_steps"; + return KEY_LAYOUT_NOTIF_AOD_ICON_HEIGHT_STEPS; } public static String layoutLockStatusPositionKey(int index) { - return suffixedKey("layout_lock_status_position", index); + return suffixedKey(KEY_LAYOUT_STATUS_LOCK_POSITION, index); } public static String layoutLockStatusMiddleSideKey(int index) { - return suffixedKey("layout_lock_status_middle_side", index); + return suffixedKey(KEY_LAYOUT_STATUS_LOCK_MIDDLE_SIDE, index); } public static String layoutLockStatusMiddleAutoNearestSideKey(int index) { - return suffixedKey("layout_lock_status_middle_auto_nearest_side", index); + return suffixedKey(KEY_LAYOUT_STATUS_LOCK_MIDDLE_AUTO_NEAREST_SIDE, index); } public static String layoutLockStatusVerticalOffsetKey(int index) { - return suffixedKey("layout_lock_status_vertical_offset_px", index); + return suffixedKey(KEY_LAYOUT_STATUS_LOCK_VERTICAL_OFFSET_PX, index); } public static String layoutLockStatusIconHeightKey() { - return "layout_lock_status_icon_height_steps"; + return KEY_LAYOUT_STATUS_LOCK_ICON_HEIGHT_STEPS; } public static String layoutAodStatusPositionKey(int index) { - return suffixedKey("layout_aod_status_position", index); + return suffixedKey(KEY_LAYOUT_STATUS_AOD_POSITION, index); } public static String layoutAodStatusMiddleSideKey(int index) { - return suffixedKey("layout_aod_status_middle_side", index); + return suffixedKey(KEY_LAYOUT_STATUS_AOD_MIDDLE_SIDE, index); } public static String layoutAodStatusMiddleAutoNearestSideKey(int index) { - return suffixedKey("layout_aod_status_middle_auto_nearest_side", index); + return suffixedKey(KEY_LAYOUT_STATUS_AOD_MIDDLE_AUTO_NEAREST_SIDE, index); } public static String layoutAodStatusVerticalOffsetKey(int index) { - return suffixedKey("layout_aod_status_vertical_offset_px", index); + return suffixedKey(KEY_LAYOUT_STATUS_AOD_VERTICAL_OFFSET_PX, index); } public static String layoutAodStatusIconHeightKey() { - return "layout_aod_status_icon_height_steps"; + return KEY_LAYOUT_STATUS_AOD_ICON_HEIGHT_STEPS; + } + + public static String indexedKey(String base, int index) { + return base == null || index <= 0 ? base : base + "_" + (index + 1); } private static String suffixedKey(String base, int index) { - return index <= 0 ? base : base + "_" + (index + 1); + return indexedKey(base, index); } public final boolean enableAodCutoutLimit; public final boolean enableLockCutoutLimit; @@ -669,9 +711,7 @@ public final class SbtSettings { this.layoutNotifIconHeightSteps = clampIconHeightSteps(layoutNotifIconHeightSteps); this.layoutNotifLockIconHeightSteps = clampIconHeightSteps(layoutNotifLockIconHeightSteps); this.layoutNotifAodIconHeightSteps = clampIconHeightSteps(layoutNotifAodIconHeightSteps); - this.layoutNotifUnlockedCount = Math.max( - SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN, - Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, layoutNotifUnlockedCount)); + this.layoutNotifUnlockedCount = clampIconContainerCount(layoutNotifUnlockedCount); this.layoutNotifPositions = sanitizeStringArray( layoutNotifPositions, this.layoutNotifPosition, @@ -746,9 +786,7 @@ public final class SbtSettings { this.layoutStatusIconHeightSteps = clampIconHeightSteps(layoutStatusIconHeightSteps); this.layoutStatusLockIconHeightSteps = clampIconHeightSteps(layoutStatusLockIconHeightSteps); this.layoutStatusAodIconHeightSteps = clampIconHeightSteps(layoutStatusAodIconHeightSteps); - this.layoutStatusUnlockedCount = Math.max( - SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN, - Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, layoutStatusUnlockedCount)); + this.layoutStatusUnlockedCount = clampIconContainerCount(layoutStatusUnlockedCount); this.layoutStatusPositions = sanitizeStringArray( layoutStatusPositions, this.layoutStatusPosition, @@ -1002,9 +1040,8 @@ public final class SbtSettings { } private static SbtSettings fromProvider(Context context) { - Uri uri = Uri.parse("content://" + SbtSettingsProvider.AUTHORITY); Bundle out = context.getContentResolver() - .call(uri, SbtSettingsProvider.METHOD_GET_ALL, null, null); + .call(settingsProviderUri(), SbtSettingsProvider.METHOD_GET_ALL, null, null); if (out == null) { return null; } @@ -1017,9 +1054,8 @@ public final class SbtSettings { } try { if (!MODULE_PACKAGE.equals(context.getPackageName()) && hasSettingsProvider(context)) { - Uri uri = Uri.parse("content://" + SbtSettingsProvider.AUTHORITY); Bundle out = context.getContentResolver() - .call(uri, SbtSettingsProvider.METHOD_GET_ALL, null, null); + .call(settingsProviderUri(), SbtSettingsProvider.METHOD_GET_ALL, null, null); if (out != null) { return out.getBoolean(KEY_DEBUG_WRITE_LOGS, SbtDefaults.DEBUG_WRITE_LOGS_DEFAULT); } @@ -1039,14 +1075,16 @@ public final class SbtSettings { int offsetMinutes = AppIconRules.currentTimezoneOffsetMinutes(); try { if (!MODULE_PACKAGE.equals(context.getPackageName()) && hasSettingsProvider(context)) { - Uri uri = Uri.parse("content://" + SbtSettingsProvider.AUTHORITY); Bundle extras = new Bundle(); extras.putString(AppIconRules.EXTRA_PACKAGE, packageName); extras.putLong(AppIconRules.EXTRA_FIRST_SEEN_MS, seenMs); extras.putLong(AppIconRules.EXTRA_LAST_SEEN_MS, seenMs); extras.putInt(AppIconRules.EXTRA_LAST_SEEN_TIMEZONE_OFFSET_MINUTES, offsetMinutes); context.getContentResolver() - .call(uri, SbtSettingsProvider.METHOD_NOTE_SEEN_ICON_APP, null, extras); + .call(settingsProviderUri(), + SbtSettingsProvider.METHOD_NOTE_SEEN_ICON_APP, + null, + extras); return; } SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); @@ -1138,9 +1176,7 @@ public final class SbtSettings { SbtDefaults.BATTERY_BAR_DEFAULT_DISCHARGE_COLOR_DEFAULT), prefs.getString(KEY_BATTERY_BAR_DEFAULT_CHARGE_COLOR, SbtDefaults.BATTERY_BAR_DEFAULT_CHARGE_COLOR_DEFAULT), - BatteryBarStyle.decodeThresholds(prefs.getStringSet( - KEY_BATTERY_BAR_THRESHOLDS, - Collections.emptySet())), + BatteryBarStyle.loadThresholds(prefs, KEY_BATTERY_BAR_THRESHOLDS), prefs.getBoolean(KEY_CLOCK_ENABLED, SbtDefaults.CLOCK_ENABLED_DEFAULT), prefs.getBoolean(KEY_CLOCK_ENABLED_LOCKSCREEN, SbtDefaults.CLOCK_ENABLED_LOCKSCREEN_DEFAULT), prefs.getBoolean(KEY_CLOCK_ENABLED_UNLOCKED, SbtDefaults.CLOCK_ENABLED_UNLOCKED_DEFAULT), @@ -1233,7 +1269,7 @@ public final class SbtSettings { SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT), readIconContainerCount( prefs, - "layout_lock_notifications_count", + KEY_LAYOUT_NOTIF_LOCK_COUNT, KEY_LAYOUT_NOTIF_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT), @@ -1255,7 +1291,7 @@ public final class SbtSettings { SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT), readIconContainerCount( prefs, - "layout_aod_notifications_count", + KEY_LAYOUT_NOTIF_AOD_COUNT, KEY_LAYOUT_NOTIF_ENABLED_AOD, SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT), @@ -1322,7 +1358,7 @@ public final class SbtSettings { SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT), readIconContainerCount( prefs, - "layout_lock_status_count", + KEY_LAYOUT_STATUS_LOCK_COUNT, KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT), @@ -1344,7 +1380,7 @@ public final class SbtSettings { SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT), readIconContainerCount( prefs, - "layout_aod_status_count", + KEY_LAYOUT_STATUS_AOD_COUNT, KEY_LAYOUT_STATUS_ENABLED_AOD, SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT), @@ -1364,17 +1400,18 @@ public final class SbtSettings { prefs, SbtSettings::layoutAodStatusVerticalOffsetKey, SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT), - ClockCameraTypeSupport.parseOverrides(copyStringSet( - prefs.getStringSet(KEY_CLOCK_CAMERA_TYPE_OVERRIDES, Collections.emptySet()))), + ClockCameraTypeSupport.loadOverrides(prefs, KEY_CLOCK_CAMERA_TYPE_OVERRIDES), systemIconBlockedModes, prefs.getBoolean(KEY_STATUS_CHIPS_HIDE_ALL, false), prefs.getBoolean(KEY_STATUS_CHIPS_HIDE_MEDIA_UNLOCKED, false), prefs.getBoolean(KEY_STATUS_CHIPS_HIDE_NAV_UNLOCKED, false), prefs.getBoolean(KEY_STATUS_CHIPS_HIDE_CALL, false), - AppIconRules.parseBlockedModes(copyStringSet( - prefs.getStringSet(KEY_APP_ICON_BLOCKED_MODES, Collections.emptySet()))), - AppIconRules.parseExceptionRules(copyStringSet( - prefs.getStringSet(KEY_APP_ICON_EXCEPTION_RULES, Collections.emptySet()))), + AppIconRules.parseBlockedModes(readStringSetFromPrefs( + prefs, + KEY_APP_ICON_BLOCKED_MODES)), + AppIconRules.parseExceptionRules(readStringSetFromPrefs( + prefs, + KEY_APP_ICON_EXCEPTION_RULES)), prefs.getString(KEY_LOCKED_NOTIFICATION_MODE, "dot"), prefs.getBoolean( KEY_DEBUG_VISUALIZE_NOTIFICATION_CONTAINER, @@ -1561,7 +1598,7 @@ public final class SbtSettings { SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT), readIconContainerCount( bundle, - "layout_lock_notifications_count", + KEY_LAYOUT_NOTIF_LOCK_COUNT, KEY_LAYOUT_NOTIF_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT), @@ -1583,7 +1620,7 @@ public final class SbtSettings { SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT), readIconContainerCount( bundle, - "layout_aod_notifications_count", + KEY_LAYOUT_NOTIF_AOD_COUNT, KEY_LAYOUT_NOTIF_ENABLED_AOD, SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT), @@ -1650,7 +1687,7 @@ public final class SbtSettings { SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT), readIconContainerCount( bundle, - "layout_lock_status_count", + KEY_LAYOUT_STATUS_LOCK_COUNT, KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT), @@ -1672,7 +1709,7 @@ public final class SbtSettings { SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT), readIconContainerCount( bundle, - "layout_aod_status_count", + KEY_LAYOUT_STATUS_AOD_COUNT, KEY_LAYOUT_STATUS_ENABLED_AOD, SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT), @@ -1698,8 +1735,12 @@ public final class SbtSettings { bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_MEDIA_UNLOCKED, false), bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_NAV_UNLOCKED, false), bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_CALL, false), - AppIconRules.parseBlockedModes(readBlockedSetFromBundle(bundle)), - AppIconRules.parseExceptionRules(readRuleSetFromBundle(bundle)), + AppIconRules.parseBlockedModes(readStringSetFromBundle( + bundle, + KEY_APP_ICON_BLOCKED_MODES)), + AppIconRules.parseExceptionRules(readStringSetFromBundle( + bundle, + KEY_APP_ICON_EXCEPTION_RULES)), bundle.getString(KEY_LOCKED_NOTIFICATION_MODE, "dot"), bundle.getBoolean( KEY_DEBUG_VISUALIZE_NOTIFICATION_CONTAINER, @@ -1730,13 +1771,13 @@ public final class SbtSettings { return value; } - private static int clampIconContainerCount(int value) { + public static int clampIconContainerCount(int value) { return Math.max( SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN, Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, value)); } - private static int clampIconHeightSteps(int value) { + public static int clampIconHeightSteps(int value) { return Math.max( SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN, Math.min(SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX, value)); @@ -1749,11 +1790,11 @@ public final class SbtSettings { int defaultCount, boolean legacyEnabledDefault ) { - int fallback = prefs.getBoolean(legacyEnabledKey, legacyEnabledDefault) ? defaultCount : 0; + int fallback = iconContainerCountFallback( + prefs.getBoolean(legacyEnabledKey, legacyEnabledDefault), + defaultCount); int value = prefs.getInt(countKey, fallback); - return Math.max( - SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN, - Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, value)); + return clampIconContainerCount(value); } private static int readIconContainerCount( @@ -1763,11 +1804,15 @@ public final class SbtSettings { int defaultCount, boolean legacyEnabledDefault ) { - int fallback = bundle.getBoolean(legacyEnabledKey, legacyEnabledDefault) ? defaultCount : 0; + int fallback = iconContainerCountFallback( + bundle.getBoolean(legacyEnabledKey, legacyEnabledDefault), + defaultCount); int value = bundle.getInt(countKey, fallback); - return Math.max( - SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN, - Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, value)); + return clampIconContainerCount(value); + } + + private static int iconContainerCountFallback(boolean legacyEnabled, int defaultCount) { + return legacyEnabled ? defaultCount : 0; } private static String[] readStringArrayFromPrefs( @@ -1864,24 +1909,17 @@ public final class SbtSettings { return result; } - private static Set copyStringSet(Set source) { + private static Set readStringSetFromPrefs(SharedPreferences prefs, String key) { + if (prefs == null) { + return Collections.emptySet(); + } + Set source = prefs.getStringSet(key, Collections.emptySet()); if (source == null || source.isEmpty()) { return Collections.emptySet(); } return new HashSet<>(source); } - private static Set readBlockedSetFromBundle(Bundle bundle) { - if (bundle == null) { - return Collections.emptySet(); - } - ArrayList list = bundle.getStringArrayList(KEY_APP_ICON_BLOCKED_MODES); - if (list == null || list.isEmpty()) { - return Collections.emptySet(); - } - return new HashSet<>(list); - } - private static Set readStringSetFromBundle(Bundle bundle, String key) { if (bundle == null) { return Collections.emptySet(); @@ -1893,31 +1931,14 @@ public final class SbtSettings { return new HashSet<>(list); } - private static Set readRuleSetFromBundle(Bundle bundle) { - if (bundle == null) { - return Collections.emptySet(); - } - ArrayList list = bundle.getStringArrayList(KEY_APP_ICON_EXCEPTION_RULES); - if (list == null || list.isEmpty()) { - return Collections.emptySet(); - } - return new HashSet<>(list); - } - private static Map readSystemIconBlockedModesFromPrefs(SharedPreferences prefs) { if (prefs == null) { return Collections.emptyMap(); } - Map parsed = SystemIconRules.parseBlockedModes(copyStringSet( - prefs.getStringSet(KEY_SYSTEM_ICON_BLOCKED_MODES, Collections.emptySet()))); + Map parsed = SystemIconRules.parseBlockedModes( + readStringSetFromPrefs(prefs, KEY_SYSTEM_ICON_BLOCKED_MODES)); HashMap merged = new HashMap<>(parsed); - Set legacyHidden = copyStringSet( - prefs.getStringSet(KEY_SYSTEM_ICON_HIDE_SLOTS, Collections.emptySet())); - for (String slot : legacyHidden) { - SystemIconRules.setModeBlocked(merged, slot, - SystemIconRules.MODE_AOD | SystemIconRules.MODE_LOCK | SystemIconRules.MODE_UNLOCK, - true); - } + mergeLegacySystemIconHidden(merged, readStringSetFromPrefs(prefs, KEY_SYSTEM_ICON_HIDE_SLOTS)); return merged.isEmpty() ? Collections.emptyMap() : merged; } @@ -1928,23 +1949,36 @@ public final class SbtSettings { Map parsed = SystemIconRules.parseBlockedModes( readStringSetFromBundle(bundle, KEY_SYSTEM_ICON_BLOCKED_MODES)); HashMap merged = new HashMap<>(parsed); - Set legacyHidden = readStringSetFromBundle(bundle, KEY_SYSTEM_ICON_HIDE_SLOTS); + mergeLegacySystemIconHidden(merged, readStringSetFromBundle(bundle, KEY_SYSTEM_ICON_HIDE_SLOTS)); + return merged.isEmpty() ? Collections.emptyMap() : merged; + } + + private static void mergeLegacySystemIconHidden(HashMap merged, Set legacyHidden) { + if (merged == null || legacyHidden == null || legacyHidden.isEmpty()) { + return; + } for (String slot : legacyHidden) { SystemIconRules.setModeBlocked(merged, slot, SystemIconRules.MODE_AOD | SystemIconRules.MODE_LOCK | SystemIconRules.MODE_UNLOCK, true); } - return merged.isEmpty() ? Collections.emptyMap() : merged; } public static void ensureReadable(Context context) { if (context == null) { return; } - Uri uri = Uri.parse("content://" + SbtSettingsProvider.AUTHORITY + "/settings"); - context.getContentResolver().notifyChange(uri, null); + context.getContentResolver().notifyChange(settingsNotifyUri(), null); android.content.Intent intent = new android.content.Intent(ACTION_SETTINGS_CHANGED); intent.setPackage(PACKAGE_SYSTEMUI); context.sendBroadcast(intent); } + + private static Uri settingsProviderUri() { + return Uri.parse(SETTINGS_PROVIDER_BASE_URI); + } + + private static Uri settingsNotifyUri() { + return Uri.parse(SETTINGS_PROVIDER_BASE_URI + "/settings"); + } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java index 6f08000..79ffab0 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SbtSettingsProvider.java @@ -9,13 +9,11 @@ import android.net.Uri; import android.os.Bundle; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.Map; public class SbtSettingsProvider extends ContentProvider { - private interface IndexedKey { - String key(int index); - } - public static final String AUTHORITY = "se.ajpanton.statusbartweak.settings"; public static final String METHOD_GET_UNLOCKED = "get_unlocked"; public static final String METHOD_GET_ALL = "get_all"; @@ -111,11 +109,7 @@ public class SbtSettingsProvider extends ContentProvider { out.putString(SbtSettings.KEY_BATTERY_BAR_DEFAULT_CHARGE_COLOR, prefs.getString(SbtSettings.KEY_BATTERY_BAR_DEFAULT_CHARGE_COLOR, SbtDefaults.BATTERY_BAR_DEFAULT_CHARGE_COLOR_DEFAULT)); - out.putStringArrayList( - SbtSettings.KEY_BATTERY_BAR_THRESHOLDS, - new ArrayList<>(prefs.getStringSet( - SbtSettings.KEY_BATTERY_BAR_THRESHOLDS, - java.util.Collections.emptySet()))); + putStringSetArrayList(out, prefs, SbtSettings.KEY_BATTERY_BAR_THRESHOLDS); out.putBoolean(SbtSettings.KEY_CLOCK_ENABLED, prefs.getBoolean(SbtSettings.KEY_CLOCK_ENABLED, SbtDefaults.CLOCK_ENABLED_DEFAULT)); out.putBoolean(SbtSettings.KEY_CLOCK_ENABLED_LOCKSCREEN, @@ -276,8 +270,8 @@ public class SbtSettingsProvider extends ContentProvider { SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtSettings::layoutNotifVerticalOffsetKey, SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT); - out.putInt("layout_lock_notifications_count", - prefs.getInt("layout_lock_notifications_count", + out.putInt(SbtSettings.KEY_LAYOUT_NOTIF_LOCK_COUNT, + prefs.getInt(SbtSettings.KEY_LAYOUT_NOTIF_LOCK_COUNT, prefs.getBoolean( SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT) @@ -297,8 +291,8 @@ public class SbtSettingsProvider extends ContentProvider { SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtSettings::layoutLockNotifVerticalOffsetKey, SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT); - out.putInt("layout_aod_notifications_count", - prefs.getInt("layout_aod_notifications_count", + out.putInt(SbtSettings.KEY_LAYOUT_NOTIF_AOD_COUNT, + prefs.getInt(SbtSettings.KEY_LAYOUT_NOTIF_AOD_COUNT, prefs.getBoolean( SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_AOD, SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT) @@ -363,8 +357,8 @@ public class SbtSettingsProvider extends ContentProvider { SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtSettings::layoutStatusVerticalOffsetKey, SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT); - out.putInt("layout_lock_status_count", - prefs.getInt("layout_lock_status_count", + out.putInt(SbtSettings.KEY_LAYOUT_STATUS_LOCK_COUNT, + prefs.getInt(SbtSettings.KEY_LAYOUT_STATUS_LOCK_COUNT, prefs.getBoolean( SbtSettings.KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT) @@ -384,8 +378,8 @@ public class SbtSettingsProvider extends ContentProvider { SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtSettings::layoutLockStatusVerticalOffsetKey, SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT); - out.putInt("layout_aod_status_count", - prefs.getInt("layout_aod_status_count", + out.putInt(SbtSettings.KEY_LAYOUT_STATUS_AOD_COUNT, + prefs.getInt(SbtSettings.KEY_LAYOUT_STATUS_AOD_COUNT, prefs.getBoolean( SbtSettings.KEY_LAYOUT_STATUS_ENABLED_AOD, SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT) @@ -405,21 +399,9 @@ public class SbtSettingsProvider extends ContentProvider { SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtSettings::layoutAodStatusVerticalOffsetKey, SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT); - out.putStringArrayList( - SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES, - new ArrayList<>(prefs.getStringSet( - SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES, - java.util.Collections.emptySet()))); - out.putStringArrayList( - SbtSettings.KEY_SYSTEM_ICON_BLOCKED_MODES, - new ArrayList<>(prefs.getStringSet( - SbtSettings.KEY_SYSTEM_ICON_BLOCKED_MODES, - java.util.Collections.emptySet()))); - out.putStringArrayList( - SbtSettings.KEY_SYSTEM_ICON_HIDE_SLOTS, - new ArrayList<>(prefs.getStringSet( - SbtSettings.KEY_SYSTEM_ICON_HIDE_SLOTS, - java.util.Collections.emptySet()))); + putStringSetArrayList(out, prefs, SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES); + putStringSetArrayList(out, prefs, SbtSettings.KEY_SYSTEM_ICON_BLOCKED_MODES); + putStringSetArrayList(out, prefs, SbtSettings.KEY_SYSTEM_ICON_HIDE_SLOTS); out.putBoolean(SbtSettings.KEY_STATUS_CHIPS_HIDE_ALL, prefs.getBoolean(SbtSettings.KEY_STATUS_CHIPS_HIDE_ALL, false)); out.putBoolean(SbtSettings.KEY_STATUS_CHIPS_HIDE_MEDIA_UNLOCKED, @@ -428,12 +410,14 @@ public class SbtSettingsProvider extends ContentProvider { prefs.getBoolean(SbtSettings.KEY_STATUS_CHIPS_HIDE_NAV_UNLOCKED, false)); out.putBoolean(SbtSettings.KEY_STATUS_CHIPS_HIDE_CALL, prefs.getBoolean(SbtSettings.KEY_STATUS_CHIPS_HIDE_CALL, false)); - out.putStringArrayList( + putStringCollectionArrayList( + out, SbtSettings.KEY_APP_ICON_BLOCKED_MODES, - new ArrayList<>(AppIconRules.encodeBlockedModes(AppIconRules.loadBlockedModes(prefs)))); - out.putStringArrayList( + AppIconRules.encodeBlockedModes(AppIconRules.loadBlockedModes(prefs))); + putStringCollectionArrayList( + out, SbtSettings.KEY_APP_ICON_EXCEPTION_RULES, - new ArrayList<>(AppIconRules.encodeExceptionRules(AppIconRules.loadExceptionRules(prefs)))); + AppIconRules.encodeExceptionRules(AppIconRules.loadExceptionRules(prefs))); return out; } if (METHOD_NOTE_SEEN_ICON_APP.equals(method)) { @@ -477,13 +461,13 @@ public class SbtSettingsProvider extends ContentProvider { private static void putIconCopySettings( Bundle out, SharedPreferences prefs, - IndexedKey positionKey, + SbtSettings.IndexedKey positionKey, String positionDefault, - IndexedKey middleSideKey, + SbtSettings.IndexedKey middleSideKey, String middleSideDefault, - IndexedKey autoNearestKey, + SbtSettings.IndexedKey autoNearestKey, boolean autoNearestDefault, - IndexedKey verticalOffsetKey, + SbtSettings.IndexedKey verticalOffsetKey, int verticalOffsetDefault ) { String position = positionDefault; @@ -510,6 +494,19 @@ public class SbtSettingsProvider extends ContentProvider { } } + private static void putStringSetArrayList(Bundle out, SharedPreferences prefs, String key) { + putStringCollectionArrayList(out, key, prefs.getStringSet(key, Collections.emptySet())); + } + + private static void putStringCollectionArrayList( + Bundle out, + String key, + Collection values + ) { + out.putStringArrayList(key, new ArrayList<>( + values != null ? values : Collections.emptySet())); + } + @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SystemIconRules.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SystemIconRules.java index 25e97d5..9ece38d 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SystemIconRules.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/settings/SystemIconRules.java @@ -13,6 +13,7 @@ public final class SystemIconRules { public static final int MODE_AOD = 1; public static final int MODE_LOCK = 1 << 1; public static final int MODE_UNLOCK = 1 << 2; + private static final int ALL_MODES = MODE_AOD | MODE_LOCK | MODE_UNLOCK; private static final String MODE_NAME_AOD = "AOD"; private static final String MODE_NAME_LOCK = "LOCK"; private static final String MODE_NAME_UNLOCK = "UNLOCK"; @@ -42,10 +43,10 @@ public final class SystemIconRules { int mask; try { mask = Integer.parseInt(parts[1].trim()); - } catch (Throwable ignored) { + } catch (NumberFormatException ignored) { continue; } - mask &= (MODE_AOD | MODE_LOCK | MODE_UNLOCK); + mask &= ALL_MODES; if (mask == 0) { continue; } @@ -69,7 +70,7 @@ public final class SystemIconRules { if (maskObj == null) { continue; } - int mask = maskObj & (MODE_AOD | MODE_LOCK | MODE_UNLOCK); + int mask = maskObj & ALL_MODES; if (mask == 0) { continue; } @@ -106,7 +107,7 @@ public final class SystemIconRules { if (normalizedSlot == null) { return; } - int validModeMask = modeMask & (MODE_AOD | MODE_LOCK | MODE_UNLOCK); + int validModeMask = modeMask & ALL_MODES; if (validModeMask == 0) { return; } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/BatteryBarFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/BatteryBarFragment.java index 2f7455e..cde7d93 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/BatteryBarFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/BatteryBarFragment.java @@ -270,7 +270,7 @@ public final class BatteryBarFragment extends Fragment { try { java.lang.reflect.Method method = target.getClass().getMethod(methodName, int.class); method.invoke(target, value); - } catch (Throwable ignored) { + } catch (Exception ignored) { } } @@ -304,14 +304,14 @@ public final class BatteryBarFragment extends Fragment { final boolean[] updating = new boolean[]{false}; if (minus != null) { minus.setOnClickListener(v -> { - int current = parseIntInput(input, prefs.getInt(key, fallback)); - persistInt(prefs, key, clampInt(current - 1, min, max), input, updating); + int current = ViewTreeSupport.parseInt(input, prefs.getInt(key, fallback)); + persistInt(prefs, key, ViewTreeSupport.clamp(current - 1, min, max), input, updating); }); } if (plus != null) { plus.setOnClickListener(v -> { - int current = parseIntInput(input, prefs.getInt(key, fallback)); - persistInt(prefs, key, clampInt(current + 1, min, max), input, updating); + int current = ViewTreeSupport.parseInt(input, prefs.getInt(key, fallback)); + persistInt(prefs, key, ViewTreeSupport.clamp(current + 1, min, max), input, updating); }); } if (input != null) { @@ -333,16 +333,16 @@ public final class BatteryBarFragment extends Fragment { if (text.isEmpty()) { return; } - int current = parseIntInput(input, prefs.getInt(key, fallback)); - persistInt(prefs, key, clampInt(current, min, max), input, updating); + int current = ViewTreeSupport.parseInt(input, prefs.getInt(key, fallback)); + persistInt(prefs, key, ViewTreeSupport.clamp(current, min, max), input, updating); } }); input.setOnFocusChangeListener((v, hasFocus) -> { if (hasFocus) { return; } - int current = parseIntInput(input, prefs.getInt(key, fallback)); - persistInt(prefs, key, clampInt(current, min, max), input, updating); + int current = ViewTreeSupport.parseInt(input, prefs.getInt(key, fallback)); + persistInt(prefs, key, ViewTreeSupport.clamp(current, min, max), input, updating); }); } } @@ -360,9 +360,9 @@ public final class BatteryBarFragment extends Fragment { int min = Math.round(values.get(0)); int max = Math.round(values.get(1)); if (lower) { - min = clampInt(min + delta, SbtDefaults.BATTERY_BAR_MIN_LEVEL_MIN, max - 1); + min = ViewTreeSupport.clamp(min + delta, SbtDefaults.BATTERY_BAR_MIN_LEVEL_MIN, max - 1); } else { - max = clampInt(max + delta, min + 1, SbtDefaults.BATTERY_BAR_MAX_LEVEL_MAX); + max = ViewTreeSupport.clamp(max + delta, min + 1, SbtDefaults.BATTERY_BAR_MAX_LEVEL_MAX); } persistMapping(prefs, min, max, slider, minLabel, maxLabel); } @@ -373,8 +373,8 @@ public final class BatteryBarFragment extends Fragment { RangeSlider slider, TextView minLabel, TextView maxLabel) { - min = clampInt(min, SbtDefaults.BATTERY_BAR_MIN_LEVEL_MIN, SbtDefaults.BATTERY_BAR_MIN_LEVEL_MAX); - max = clampInt(max, SbtDefaults.BATTERY_BAR_MAX_LEVEL_MIN, SbtDefaults.BATTERY_BAR_MAX_LEVEL_MAX); + min = ViewTreeSupport.clamp(min, SbtDefaults.BATTERY_BAR_MIN_LEVEL_MIN, SbtDefaults.BATTERY_BAR_MIN_LEVEL_MAX); + max = ViewTreeSupport.clamp(max, SbtDefaults.BATTERY_BAR_MAX_LEVEL_MIN, SbtDefaults.BATTERY_BAR_MAX_LEVEL_MAX); if (max <= min) { max = Math.min(SbtDefaults.BATTERY_BAR_MAX_LEVEL_MAX, min + 1); if (max <= min) { @@ -442,9 +442,7 @@ public final class BatteryBarFragment extends Fragment { } private ArrayList loadThresholds(SharedPreferences prefs) { - return BatteryBarStyle.decodeThresholds(prefs.getStringSet( - SbtSettings.KEY_BATTERY_BAR_THRESHOLDS, - java.util.Collections.emptySet())); + return BatteryBarStyle.loadThresholds(prefs, SbtSettings.KEY_BATTERY_BAR_THRESHOLDS); } private ArrayList sortedThresholdsForUi(SharedPreferences prefs) { @@ -466,12 +464,12 @@ public final class BatteryBarFragment extends Fragment { LinearLayout row = new LinearLayout(context); row.setOrientation(LinearLayout.VERTICAL); row.setBackgroundResource(R.drawable.sbt_threshold_card); - int padding = dpToPx(context, 12); + int padding = ViewTreeSupport.dp(context, 12); row.setPadding(padding, padding, padding, padding); LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - rowParams.topMargin = dpToPx(context, 12); + rowParams.topMargin = ViewTreeSupport.dp(context, 12); row.setLayoutParams(rowParams); TextView title = new TextView(context); @@ -550,7 +548,7 @@ public final class BatteryBarFragment extends Fragment { LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - params.topMargin = dpToPx(context, 8); + params.topMargin = ViewTreeSupport.dp(context, 8); button.setLayoutParams(params); return button; } @@ -656,9 +654,9 @@ public final class BatteryBarFragment extends Fragment { LinearLayout layout = new LinearLayout(context); layout.setOrientation(LinearLayout.VERTICAL); layout.setPadding( - dpToPx(context, 24), - dpToPx(context, 8), - dpToPx(context, 24), + ViewTreeSupport.dp(context, 24), + ViewTreeSupport.dp(context, 8), + ViewTreeSupport.dp(context, 24), 0); EditText input = new EditText(context); input.setInputType(InputType.TYPE_CLASS_TEXT); @@ -677,7 +675,7 @@ public final class BatteryBarFragment extends Fragment { LinearLayout.LayoutParams noteParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - noteParams.topMargin = dpToPx(context, 8); + noteParams.topMargin = ViewTreeSupport.dp(context, 8); note.setLayoutParams(noteParams); note.setGravity(Gravity.START); layout.addView(note); @@ -690,7 +688,7 @@ public final class BatteryBarFragment extends Fragment { LinearLayout.LayoutParams dischargeParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - dischargeParams.topMargin = dpToPx(context, 8); + dischargeParams.topMargin = ViewTreeSupport.dp(context, 8); sameAsDischargeButton.setLayoutParams(dischargeParams); layout.addView(sameAsDischargeButton); @@ -702,7 +700,7 @@ public final class BatteryBarFragment extends Fragment { LinearLayout.LayoutParams defaultParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - defaultParams.topMargin = dpToPx(context, 8); + defaultParams.topMargin = ViewTreeSupport.dp(context, 8); sameAsDefaultButton.setLayoutParams(defaultParams); layout.addView(sameAsDefaultButton); } @@ -752,7 +750,7 @@ public final class BatteryBarFragment extends Fragment { .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(android.R.string.ok, (dialogInterface, which) -> { int percent = BatteryBarStyle.clampPercent( - parseIntInput(input, existing != null ? existing.percent : 15)); + ViewTreeSupport.parseInt(input, existing != null ? existing.percent : 15)); if (existing == null && containsThreshold(loadThresholds(prefs), percent, -1)) { showMessageDialog(R.string.battery_bar_threshold_duplicate); return; @@ -770,45 +768,12 @@ public final class BatteryBarFragment extends Fragment { .show(); } - private int parseIntInput(EditText input, int fallback) { - if (input == null) { - return fallback; - } - String text = input.getText() != null ? input.getText().toString().trim() : ""; - if (text.isEmpty() || "-".equals(text)) { - return fallback; - } - try { - return Integer.parseInt(text); - } catch (NumberFormatException ignored) { - return fallback; - } - } - private void setIntInput(EditText input, int value) { if (input != null) { input.setText(String.valueOf(value)); } } - private int clampInt(int value, int min, int max) { - if (value < min) { - return min; - } - if (value > max) { - return max; - } - return value; - } - - private int dpToPx(Context context, int dp) { - if (context == null) { - return dp; - } - float density = context.getResources().getDisplayMetrics().density; - return (int) (dp * density + 0.5f); - } - private void applyPositionSelection(RadioButton top, RadioButton bottom, String position) { diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/BlockAppNotificationIconsFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/BlockAppNotificationIconsFragment.java index a5db2ba..95a9f10 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/BlockAppNotificationIconsFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/BlockAppNotificationIconsFragment.java @@ -137,7 +137,7 @@ public class BlockAppNotificationIconsFragment extends Fragment { View content = root.findViewById(R.id.hidden_apps_content); TextView disabledHint = root.findViewById(R.id.hidden_apps_disabled_hint); if (content != null) { - setViewTreeEnabled(content, enabled); + ViewTreeSupport.setEnabledRecursive(content, enabled); content.setAlpha(enabled ? 1f : 0.45f); } if (disabledHint != null) { @@ -146,17 +146,4 @@ public class BlockAppNotificationIconsFragment extends Fragment { } } - private void setViewTreeEnabled(View view, boolean enabled) { - if (view == null) { - return; - } - view.setEnabled(enabled); - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - for (int i = 0; i < group.getChildCount(); i++) { - setViewTreeEnabled(group.getChildAt(i), enabled); - } - } - } - } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/ClockFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/ClockFragment.java index 3e83fed..07c2d9d 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/ClockFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/ClockFragment.java @@ -146,8 +146,8 @@ public final class ClockFragment extends Fragment { LinearLayout container = new LinearLayout(context); container.setOrientation(LinearLayout.VERTICAL); - int padding = dpToPx(context, 20); - container.setPadding(padding, dpToPx(context, 8), padding, 0); + int padding = ViewTreeSupport.dp(context, 20); + container.setPadding(padding, ViewTreeSupport.dp(context, 8), padding, 0); EditText searchInput = new EditText(context); searchInput.setHint(R.string.clock_font_picker_hint); @@ -161,8 +161,8 @@ public final class ClockFragment extends Fragment { listView.setDividerHeight(0); LinearLayout.LayoutParams listParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, - dpToPx(context, 360)); - listParams.topMargin = dpToPx(context, 12); + ViewTreeSupport.dp(context, 360)); + listParams.topMargin = ViewTreeSupport.dp(context, 12); container.addView(listView, listParams); FontFamilyAdapter adapter = new FontFamilyAdapter(context, filteredFamilies); @@ -253,7 +253,7 @@ public final class ClockFragment extends Fragment { } String[] lines = raw.toString().split("\\n", -1); SpannableStringBuilder builder = new SpannableStringBuilder(); - int bulletGap = dpToPx(view.getContext(), 8); + int bulletGap = ViewTreeSupport.dp(view.getContext(), 8); for (int i = 0; i < lines.length; i++) { String line = lines[i]; boolean bulleted = line.startsWith("\u2022"); @@ -274,14 +274,6 @@ public final class ClockFragment extends Fragment { view.setText(builder); } - private int dpToPx(Context context, int dp) { - if (context == null) { - return dp; - } - float density = context.getResources().getDisplayMetrics().density; - return Math.round(dp * density); - } - private static final class FontFamilyAdapter extends BaseAdapter { private static final String SAMPLE_PATTERN = "EEEE HH:mm:ss"; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/HiddenAppsUiSupport.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/HiddenAppsUiSupport.java index 70decbe..744e504 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/HiddenAppsUiSupport.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/HiddenAppsUiSupport.java @@ -43,6 +43,7 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; +@SuppressWarnings("deprecation") final class HiddenAppsUiSupport { interface RowChangedListener { void onChanged(RowItem row); @@ -147,12 +148,14 @@ final class HiddenAppsUiSupport { String label = packageName; Drawable icon = fallbackIcon; try { - ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0); + ApplicationInfo appInfo = packageManager.getApplicationInfo( + packageName, + PackageManager.ApplicationInfoFlags.of(0)); CharSequence cs = packageManager.getApplicationLabel(appInfo); if (cs != null && cs.length() > 0) { label = cs.toString(); } - icon = packageManager.getApplicationIcon(appInfo); + icon = packageManager.getApplicationIcon(packageName); } catch (PackageManager.NameNotFoundException ignored) { // Keep fallback label/icon. } @@ -632,8 +635,11 @@ final class HiddenAppsUiSupport { holder.label.setText(row.label); int exceptionCount = row.exceptionCount(); if (exceptionCount > 0) { - holder.pkg.setText(context.getString( - R.string.hidden_apps_package_with_exceptions, row.packageName, exceptionCount)); + holder.pkg.setText(context.getResources().getQuantityString( + R.plurals.hidden_apps_package_with_exceptions, + exceptionCount, + row.packageName, + exceptionCount)); } else { holder.pkg.setText(row.packageName); } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/IconsDebugFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/IconsDebugFragment.java index 17307d5..e2962d1 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/IconsDebugFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/IconsDebugFragment.java @@ -22,6 +22,8 @@ import com.google.android.material.button.MaterialButton; import com.google.android.material.switchmaterial.SwitchMaterial; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import se.ajpanton.statusbartweak.R; @@ -82,37 +84,55 @@ public class IconsDebugFragment extends Fragment { SharedPreferences prefs = requireContext() .getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE); - bindWriteLogsSwitch(prefs, writeLogsSwitch); - bindBooleanSwitch( + ViewTreeSupport.bindBooleanPreference( + requireContext(), + prefs, + writeLogsSwitch, + SbtSettings.KEY_DEBUG_WRITE_LOGS, + SbtDefaults.DEBUG_WRITE_LOGS_DEFAULT, + true); + ViewTreeSupport.bindBooleanPreference( + requireContext(), prefs, visualizeNotificationContainerSwitch, SbtSettings.KEY_DEBUG_VISUALIZE_NOTIFICATION_CONTAINER, - SbtDefaults.DEBUG_VISUALIZE_NOTIFICATION_CONTAINER_DEFAULT); - bindBooleanSwitch( + SbtDefaults.DEBUG_VISUALIZE_NOTIFICATION_CONTAINER_DEFAULT, + true); + ViewTreeSupport.bindBooleanPreference( + requireContext(), prefs, visualizeStatusContainerSwitch, SbtSettings.KEY_DEBUG_VISUALIZE_STATUS_CONTAINER, - SbtDefaults.DEBUG_VISUALIZE_STATUS_CONTAINER_DEFAULT); - bindBooleanSwitch( + SbtDefaults.DEBUG_VISUALIZE_STATUS_CONTAINER_DEFAULT, + true); + ViewTreeSupport.bindBooleanPreference( + requireContext(), prefs, visualizeClockContainerSwitch, SbtSettings.KEY_DEBUG_VISUALIZE_CLOCK_CONTAINER, - SbtDefaults.DEBUG_VISUALIZE_CLOCK_CONTAINER_DEFAULT); - bindBooleanSwitch( + SbtDefaults.DEBUG_VISUALIZE_CLOCK_CONTAINER_DEFAULT, + true); + ViewTreeSupport.bindBooleanPreference( + requireContext(), prefs, visualizeChipContainerSwitch, SbtSettings.KEY_DEBUG_VISUALIZE_CHIP_CONTAINER, - SbtDefaults.DEBUG_VISUALIZE_CHIP_CONTAINER_DEFAULT); - bindBooleanSwitch( + SbtDefaults.DEBUG_VISUALIZE_CHIP_CONTAINER_DEFAULT, + true); + ViewTreeSupport.bindBooleanPreference( + requireContext(), prefs, visualizeCameraCutoutSwitch, SbtSettings.KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT, - SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT); - bindBooleanSwitch( + SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT, + true); + ViewTreeSupport.bindBooleanPreference( + requireContext(), prefs, visualizeAodStockContainersSwitch, SbtSettings.KEY_DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS, - SbtDefaults.DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS_DEFAULT); + SbtDefaults.DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS_DEFAULT, + true); bindRestartSystemUiButton(restartSystemUiButton); updateVisualizerSwitchAvailability( prefs, @@ -163,33 +183,6 @@ public class IconsDebugFragment extends Fragment { return root; } - private void bindWriteLogsSwitch(SharedPreferences prefs, SwitchMaterial writeLogsSwitch) { - if (prefs == null || writeLogsSwitch == null) { - return; - } - writeLogsSwitch.setChecked(prefs.getBoolean( - SbtSettings.KEY_DEBUG_WRITE_LOGS, - SbtDefaults.DEBUG_WRITE_LOGS_DEFAULT)); - writeLogsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - prefs.edit().putBoolean(SbtSettings.KEY_DEBUG_WRITE_LOGS, isChecked).commit(); - SbtSettings.ensureReadable(requireContext()); - }); - } - - private void bindBooleanSwitch(SharedPreferences prefs, - SwitchMaterial switchView, - String key, - boolean defaultValue) { - if (prefs == null || switchView == null || key == null) { - return; - } - switchView.setChecked(prefs.getBoolean(key, defaultValue)); - switchView.setOnCheckedChangeListener((buttonView, isChecked) -> { - prefs.edit().putBoolean(key, isChecked).commit(); - SbtSettings.ensureReadable(requireContext()); - }); - } - private void bindRestartSystemUiButton(MaterialButton restartButton) { if (restartButton == null) { return; @@ -236,7 +229,7 @@ public class IconsDebugFragment extends Fragment { .redirectErrorStream(true) .start(); success = process.waitFor() == 0; - } catch (Throwable ignored) { + } catch (Exception ignored) { } if (!success && isAdded()) { requireActivity().runOnUiThread(() -> Toast.makeText( @@ -284,6 +277,7 @@ public class IconsDebugFragment extends Fragment { } } + @SuppressWarnings("deprecation") private void ensureNotificationPermission() { Context context = requireContext(); if (ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) @@ -297,6 +291,7 @@ public class IconsDebugFragment extends Fragment { Toast.LENGTH_SHORT).show(); } + @SuppressWarnings("deprecation") @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @@ -329,10 +324,9 @@ public class IconsDebugFragment extends Fragment { ? prefs.getString(SbtSettings.KEY_DEBUG_CURRENT_CAMERA_EFFECTIVE_TYPE, "") : ""; - Map overrides = ClockCameraTypeSupport.parseOverrides( - prefs != null - ? prefs.getStringSet(SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES, java.util.Collections.emptySet()) - : java.util.Collections.emptySet()); + Map overrides = ClockCameraTypeSupport.loadOverrides( + prefs, + SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES); if (TextUtils.isEmpty(cameraId)) { identifiedView.setText(getString(R.string.debug_camera_identified_as, @@ -376,7 +370,7 @@ public class IconsDebugFragment extends Fragment { } otherList.removeAllViews(); ArrayList keys = new ArrayList<>(overrides.keySet()); - java.util.Collections.sort(keys); + Collections.sort(keys); int shown = 0; for (String key : keys) { if (!TextUtils.isEmpty(cameraId) && TextUtils.equals(key, cameraId)) { @@ -400,7 +394,7 @@ public class IconsDebugFragment extends Fragment { ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); if (addTopMargin) { - rowParams.topMargin = dpToPx(context, 8); + rowParams.topMargin = ViewTreeSupport.dp(context, 8); } row.setLayoutParams(rowParams); @@ -439,9 +433,9 @@ public class IconsDebugFragment extends Fragment { if (prefs == null || TextUtils.isEmpty(cameraId)) { return; } - Map overrides = new java.util.HashMap<>( - ClockCameraTypeSupport.parseOverrides( - prefs.getStringSet(SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES, java.util.Collections.emptySet()))); + Map overrides = new HashMap<>(ClockCameraTypeSupport.loadOverrides( + prefs, + SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES)); overrides.put(cameraId, ClockCameraTypeSupport.normalizeType(type)); ClockCameraTypeSupport.saveOverrides(prefs, SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES, overrides); SbtSettings.ensureReadable(requireContext()); @@ -451,9 +445,9 @@ public class IconsDebugFragment extends Fragment { if (prefs == null || TextUtils.isEmpty(cameraId)) { return; } - Map overrides = new java.util.HashMap<>( - ClockCameraTypeSupport.parseOverrides( - prefs.getStringSet(SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES, java.util.Collections.emptySet()))); + Map overrides = new HashMap<>(ClockCameraTypeSupport.loadOverrides( + prefs, + SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES)); overrides.remove(cameraId); ClockCameraTypeSupport.saveOverrides(prefs, SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES, overrides); SbtSettings.ensureReadable(requireContext()); @@ -462,11 +456,4 @@ public class IconsDebugFragment extends Fragment { return ClockCameraTypeSupport.formatType(type); } - private int dpToPx(Context context, int dp) { - if (context == null) { - return dp; - } - float density = context.getResources().getDisplayMetrics().density; - return (int) (dp * density + 0.5f); - } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutFragment.java index 86c1e7e..6b7d763 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutFragment.java @@ -11,7 +11,6 @@ import android.text.Editable; import android.text.InputFilter; import android.text.TextWatcher; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; @@ -24,16 +23,9 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.button.MaterialButton; import com.google.android.material.card.MaterialCardView; -import com.google.android.material.switchmaterial.SwitchMaterial; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; public final class LayoutFragment extends Fragment { private static final int PADDING_MIN = -999; @@ -50,15 +42,6 @@ public final class LayoutFragment extends Fragment { hideUnlockedSubpageObsoleteControls(root); reorderItemCards(root, prefs); - SwitchMaterial enabledSwitch = root.findViewById(R.id.clock_enabled_switch); - enabledSwitch.setChecked(prefs.getBoolean( - SbtSettings.KEY_CLOCK_ENABLED, - SbtDefaults.CLOCK_ENABLED_DEFAULT)); - enabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - prefs.edit().putBoolean(SbtSettings.KEY_CLOCK_ENABLED, isChecked).apply(); - SbtSettings.ensureReadable(requireContext()); - }); - setupPositionSection(root, prefs, R.id.clock_position_group, @@ -100,32 +83,6 @@ public final class LayoutFragment extends Fragment { SbtSettings.KEY_LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE, SbtSettings.KEY_LAYOUT_STATUS_MIDDLE_SIDE); - bindStepper(prefs, - SbtSettings.KEY_LAYOUT_PADDING_CUTOUT_PX, - root.findViewById(R.id.layout_cutout_gap_input), - root.findViewById(R.id.layout_cutout_gap_minus), - root.findViewById(R.id.layout_cutout_gap_plus), - SbtDefaults.LAYOUT_PADDING_CUTOUT_PX_DEFAULT); - bindStepper(prefs, - SbtSettings.KEY_LAYOUT_PADDING_LEFT_PX, - root.findViewById(R.id.layout_left_padding_input), - root.findViewById(R.id.layout_left_padding_minus), - root.findViewById(R.id.layout_left_padding_plus), - SbtDefaults.LAYOUT_PADDING_LEFT_PX_DEFAULT); - bindStepper(prefs, - SbtSettings.KEY_LAYOUT_PADDING_RIGHT_PX, - root.findViewById(R.id.layout_right_padding_input), - root.findViewById(R.id.layout_right_padding_minus), - root.findViewById(R.id.layout_right_padding_plus), - SbtDefaults.LAYOUT_PADDING_RIGHT_PX_DEFAULT); - bindStepper(prefs, - SbtSettings.KEY_LAYOUT_ITEM_PADDING_PX, - root.findViewById(R.id.layout_item_padding_input), - root.findViewById(R.id.layout_item_padding_minus), - root.findViewById(R.id.layout_item_padding_plus), - SbtDefaults.LAYOUT_ITEM_PADDING_PX_DEFAULT, - SbtDefaults.LAYOUT_ITEM_PADDING_PX_MIN, - SbtDefaults.LAYOUT_ITEM_PADDING_PX_MAX); bindStepper(prefs, SbtSettings.KEY_CLOCK_VERTICAL_OFFSET_PX, root.findViewById(R.id.clock_vertical_offset_input), @@ -163,19 +120,6 @@ public final class LayoutFragment extends Fragment { root.findViewById(R.id.status_vertical_offset_plus_fast), root.findViewById(R.id.status_vertical_offset_reset), SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT); - bindCheckBoxPref(prefs, root.findViewById(R.id.layout_left_padding_add_stock), - SbtSettings.KEY_LAYOUT_PADDING_LEFT_ADD_STOCK, - SbtDefaults.LAYOUT_PADDING_LEFT_ADD_STOCK_DEFAULT); - bindCheckBoxPref(prefs, root.findViewById(R.id.layout_right_padding_add_stock), - SbtSettings.KEY_LAYOUT_PADDING_RIGHT_ADD_STOCK, - SbtDefaults.LAYOUT_PADDING_RIGHT_ADD_STOCK_DEFAULT); - bindCheckBoxPref(prefs, root.findViewById(R.id.layout_ignore_under_display_cameras), - SbtSettings.KEY_LAYOUT_IGNORE_UNDER_DISPLAY_CAMERAS, - SbtDefaults.LAYOUT_IGNORE_UNDER_DISPLAY_CAMERAS_DEFAULT); - - bindCheckBoxPref(prefs, root.findViewById(R.id.clock_position_mode_lockscreen), - SbtSettings.KEY_CLOCK_ENABLED_LOCKSCREEN, - SbtDefaults.CLOCK_ENABLED_LOCKSCREEN_DEFAULT); bindCheckBoxPref(prefs, root.findViewById(R.id.clock_position_mode_unlocked), SbtSettings.KEY_CLOCK_ENABLED_UNLOCKED, SbtDefaults.CLOCK_ENABLED_UNLOCKED_DEFAULT); @@ -187,33 +131,15 @@ public final class LayoutFragment extends Fragment { LayoutSectionCopySupport.Section.CLOCK, false, this::refreshSelf); - bindCheckBoxPref(prefs, root.findViewById(R.id.notif_position_mode_aod), - SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_AOD, - SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT); - bindCheckBoxPref(prefs, root.findViewById(R.id.notif_position_mode_lockscreen), - SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_LOCKSCREEN, - SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT); bindCheckBoxPref(prefs, root.findViewById(R.id.notif_position_mode_unlocked), SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_UNLOCKED, SbtDefaults.LAYOUT_NOTIF_ENABLED_UNLOCKED_DEFAULT); - bindCheckBoxPref(prefs, root.findViewById(R.id.chip_position_mode_lockscreen), - SbtSettings.KEY_LAYOUT_CHIP_ENABLED_LOCKSCREEN, - SbtDefaults.LAYOUT_CHIP_ENABLED_LOCKSCREEN_DEFAULT); bindCheckBoxPref(prefs, root.findViewById(R.id.chip_position_mode_unlocked), SbtSettings.KEY_LAYOUT_CHIP_ENABLED_UNLOCKED, SbtDefaults.LAYOUT_CHIP_ENABLED_UNLOCKED_DEFAULT); - bindCheckBoxPref(prefs, root.findViewById(R.id.status_position_mode_aod), - SbtSettings.KEY_LAYOUT_STATUS_ENABLED_AOD, - SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT); - bindCheckBoxPref(prefs, root.findViewById(R.id.status_position_mode_lockscreen), - SbtSettings.KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN, - SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT); bindCheckBoxPref(prefs, root.findViewById(R.id.status_position_mode_unlocked), SbtSettings.KEY_LAYOUT_STATUS_ENABLED_UNLOCKED, SbtDefaults.LAYOUT_STATUS_ENABLED_UNLOCKED_DEFAULT); - bindSortOrderGroup(prefs, root.findViewById(R.id.layout_sort_icons_group)); - - setupOrderingList(root, prefs); setupUnlockedIconContainerCount( root, prefs, @@ -257,10 +183,6 @@ public final class LayoutFragment extends Fragment { return root; } - private interface IndexedKey { - String key(int index); - } - private void setupUnlockedIconContainerCount( View root, SharedPreferences prefs, @@ -270,10 +192,10 @@ public final class LayoutFragment extends Fragment { int countDefault, String legacyEnabledKey, boolean legacyEnabledDefault, - IndexedKey positionKey, - IndexedKey autoNearestKey, - IndexedKey middleSideKey, - IndexedKey verticalOffsetKey, + SbtSettings.IndexedKey positionKey, + SbtSettings.IndexedKey autoNearestKey, + SbtSettings.IndexedKey middleSideKey, + SbtSettings.IndexedKey verticalOffsetKey, String iconHeightKey, String positionDefault, boolean autoNearestDefault, @@ -304,7 +226,7 @@ public final class LayoutFragment extends Fragment { countContainer.removeAllViews(); removeGeneratedCopyCards(cardsParent, copyTag); int count = iconContainerCount(prefs, countKey, countDefault, legacyEnabledKey, legacyEnabledDefault); - updateIconCardTitle(sourceCard, sectionTitleId, 0, count); + LayoutXmlCardSupport.setIndexedCardTitle(requireContext(), sourceCard, sectionTitleId, 0, count); boolean cardsMode = LockedNotificationModeUi.MODE_CARDS.equals( LockedNotificationModeUi.currentMode(requireContext())); LayoutSectionCopySupport.addDropdownRow( @@ -379,31 +301,26 @@ public final class LayoutFragment extends Fragment { String legacyEnabledKey, Runnable onChanged ) { - LinearLayout outer = new LinearLayout(requireContext()); - outer.setOrientation(LinearLayout.VERTICAL); - TextView label = new TextView(requireContext()); - label.setText(R.string.layout_icon_container_count); - label.setPadding(0, dp(8), 0, 0); + Context context = requireContext(); + LinearLayout outer = ViewTreeSupport.verticalLayout(context); + TextView label = ViewTreeSupport.bodyText(context, R.string.layout_icon_container_count, 0); outer.addView(label); - LinearLayout row = new LinearLayout(requireContext()); + LinearLayout row = new LinearLayout(context); row.setOrientation(LinearLayout.HORIZONTAL); row.setGravity(android.view.Gravity.CENTER_VERTICAL); - MaterialButton minus = smallButton("-"); - MaterialButton plus = smallButton("+"); - EditText input = new EditText(requireContext()); - input.setGravity(android.view.Gravity.CENTER); - input.setSingleLine(true); - input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER); - input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1)}); + MaterialButton minus = ViewTreeSupport.outlinedStepperButton(context, "-"); + MaterialButton plus = ViewTreeSupport.outlinedStepperButton(context, "+"); + EditText input = ViewTreeSupport.numericInput(context, false, 1); input.setText(String.valueOf(current)); - row.addView(minus, compactButtonParams()); - row.addView(input, compactInputParams()); - row.addView(plus, compactButtonParams()); + row.addView(minus, ViewTreeSupport.stepperButtonParams(context)); + row.addView(input, ViewTreeSupport.stepperInputParams(context)); + row.addView(plus, ViewTreeSupport.stepperButtonParams(context)); outer.addView(row); View.OnClickListener listener = view -> { - int next = clampIconContainerCount(parseInt(input, current) + (view == minus ? -1 : 1)); + int next = SbtSettings.clampIconContainerCount( + ViewTreeSupport.parseInt(input, current) + (view == minus ? -1 : 1)); persistIconContainerCount(prefs, countKey, legacyEnabledKey, next); onChanged.run(); }; @@ -411,7 +328,7 @@ public final class LayoutFragment extends Fragment { plus.setOnClickListener(listener); input.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { - int next = clampIconContainerCount(parseInt(input, current)); + int next = SbtSettings.clampIconContainerCount(ViewTreeSupport.parseInt(input, current)); persistIconContainerCount(prefs, countKey, legacyEnabledKey, next); onChanged.run(); } @@ -425,71 +342,59 @@ public final class LayoutFragment extends Fragment { int defaultValue, int labelRes ) { - LinearLayout outer = new LinearLayout(requireContext()); - outer.setOrientation(LinearLayout.VERTICAL); - TextView label = new TextView(requireContext()); - label.setText(labelRes); - label.setPadding(0, dp(8), 0, 0); + Context context = requireContext(); + LinearLayout outer = ViewTreeSupport.verticalLayout(context); + TextView label = ViewTreeSupport.bodyText(context, labelRes, 0); outer.addView(label); - LinearLayout row = new LinearLayout(requireContext()); + LinearLayout row = new LinearLayout(context); row.setOrientation(LinearLayout.HORIZONTAL); row.setGravity(android.view.Gravity.CENTER_VERTICAL); - MaterialButton minus = smallButton("-"); - MaterialButton plus = smallButton("+"); - MaterialButton reset = smallButton("D"); - EditText input = new EditText(requireContext()); - input.setGravity(android.view.Gravity.CENTER); - input.setSingleLine(true); - input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER); - input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(3)}); - int current = clampValue( - prefs.getInt(key, defaultValue), - SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN, - SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX); + MaterialButton minus = ViewTreeSupport.outlinedStepperButton(context, "-"); + MaterialButton plus = ViewTreeSupport.outlinedStepperButton(context, "+"); + MaterialButton reset = ViewTreeSupport.outlinedStepperButton(context, "D"); + EditText input = ViewTreeSupport.numericInput(context, false, 3); + int current = SbtSettings.clampIconHeightSteps(prefs.getInt(key, defaultValue)); input.setText(String.valueOf(current)); - row.addView(minus, compactButtonParams()); - row.addView(input, compactInputParams()); - row.addView(plus, compactButtonParams()); - LinearLayout.LayoutParams resetParams = compactButtonParams(); - resetParams.leftMargin = dp(8); + row.addView(minus, ViewTreeSupport.stepperButtonParams(context)); + row.addView(input, ViewTreeSupport.stepperInputParams(context)); + row.addView(plus, ViewTreeSupport.stepperButtonParams(context)); + LinearLayout.LayoutParams resetParams = ViewTreeSupport.stepperButtonParams(context); + resetParams.leftMargin = ViewTreeSupport.dp(context, 8); row.addView(reset, resetParams); outer.addView(row); View.OnClickListener listener = view -> { - int next = clampValue( - parseInt(input, current) + (view == minus ? -1 : 1), - SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN, - SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX); - prefs.edit().putInt(key, next).apply(); - SbtSettings.ensureReadable(requireContext()); - input.setText(String.valueOf(next)); + int next = SbtSettings.clampIconHeightSteps( + ViewTreeSupport.parseInt(input, current) + (view == minus ? -1 : 1)); + persistIconHeight(prefs, key, input, next); }; minus.setOnClickListener(listener); plus.setOnClickListener(listener); reset.setOnClickListener(v -> { - int next = clampValue( - defaultValue, - SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN, - SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX); - prefs.edit().putInt(key, next).apply(); - SbtSettings.ensureReadable(requireContext()); - input.setText(String.valueOf(next)); + int next = SbtSettings.clampIconHeightSteps(defaultValue); + persistIconHeight(prefs, key, input, next); }); input.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { - int next = clampValue( - parseInt(input, current), - SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN, - SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX); - prefs.edit().putInt(key, next).apply(); - SbtSettings.ensureReadable(requireContext()); - input.setText(String.valueOf(next)); + int next = SbtSettings.clampIconHeightSteps(ViewTreeSupport.parseInt(input, current)); + persistIconHeight(prefs, key, input, next); } }); return outer; } + private void persistIconHeight( + SharedPreferences prefs, + String key, + EditText input, + int value + ) { + prefs.edit().putInt(key, value).apply(); + SbtSettings.ensureReadable(requireContext()); + input.setText(String.valueOf(value)); + } + private void addChipHeightControl(View root, SharedPreferences prefs) { View offsetInput = root.findViewById(R.id.chip_vertical_offset_input); if (offsetInput == null) { @@ -518,10 +423,10 @@ public final class LayoutFragment extends Fragment { int sourceCheckboxId, int titleId, int index, - IndexedKey positionKey, - IndexedKey autoNearestKey, - IndexedKey middleSideKey, - IndexedKey verticalOffsetKey, + SbtSettings.IndexedKey positionKey, + SbtSettings.IndexedKey autoNearestKey, + SbtSettings.IndexedKey middleSideKey, + SbtSettings.IndexedKey verticalOffsetKey, String positionDefault, boolean autoNearestDefault, String middleSideDefault, @@ -531,13 +436,10 @@ public final class LayoutFragment extends Fragment { int cardId = sourceCheckboxId == R.id.notif_position_mode_unlocked ? R.id.layout_notif_card : R.id.layout_status_card; - View card = inflateLayoutCard(cardId); + View card = LayoutXmlCardSupport.inflateCard(requireContext(), null, cardId); card.setTag(copyTag); - TextView title = findCardTitle(card); - if (title != null) { - title.setText(getString(titleId) + " " + (index + 1)); - } - removeModeContainerForCheckbox(card, sourceCheckboxId); + LayoutXmlCardSupport.setIndexedCardTitle(requireContext(), card, titleId, index, index + 1); + LayoutXmlCardSupport.removeModeContainerForCheckbox(card, sourceCheckboxId); SharedPreferences prefs = requireContext() .getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE); if (sourceCheckboxId == R.id.notif_position_mode_unlocked) { @@ -637,40 +539,6 @@ public final class LayoutFragment extends Fragment { return null; } - private void updateIconCardTitle(@Nullable MaterialCardView card, int titleId, int index, int count) { - TextView title = findCardTitle(card); - if (title == null) { - return; - } - if (count > 1) { - title.setText(getString(titleId) + " " + (index + 1)); - } else { - title.setText(titleId); - } - } - - private MaterialButton smallButton(String text) { - MaterialButton button = new MaterialButton(requireContext(), null, com.google.android.material.R.attr.materialButtonOutlinedStyle); - button.setText(text); - button.setMinWidth(dp(40)); - button.setMinHeight(dp(40)); - button.setPadding(0, button.getPaddingTop(), 0, button.getPaddingBottom()); - return button; - } - - private LinearLayout.LayoutParams compactButtonParams() { - return new LinearLayout.LayoutParams(dp(40), ViewGroup.LayoutParams.WRAP_CONTENT); - } - - private LinearLayout.LayoutParams compactInputParams() { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - dp(48), - ViewGroup.LayoutParams.WRAP_CONTENT); - params.leftMargin = dp(8); - params.rightMargin = dp(8); - return params; - } - @Nullable private MaterialCardView findAncestorCard(View view) { View current = view; @@ -695,54 +563,6 @@ public final class LayoutFragment extends Fragment { } } - private View inflateLayoutCard(int cardId) { - View fullLayout = LayoutInflater.from(requireContext()).inflate(R.layout.fragment_layout, null, false); - View card = fullLayout.findViewById(cardId); - if (card == null) { - return new View(requireContext()); - } - if (card.getParent() instanceof ViewGroup parent) { - parent.removeView(card); - } - return card; - } - - @Nullable - private TextView findCardTitle(View card) { - if (!(card instanceof ViewGroup cardGroup) || cardGroup.getChildCount() <= 0) { - return null; - } - View inner = cardGroup.getChildAt(0); - if (!(inner instanceof ViewGroup innerGroup) || innerGroup.getChildCount() <= 0) { - return null; - } - View title = innerGroup.getChildAt(0); - return title instanceof TextView textView ? textView : null; - } - - @Nullable - private LinearLayout removeModeContainerForCheckbox(View root, int checkboxId) { - CheckBox checkbox = root.findViewById(checkboxId); - View modeContainer = directSectionChild(checkbox); - if (modeContainer == null || !(modeContainer.getParent() instanceof LinearLayout sectionContent)) { - return null; - } - sectionContent.removeView(modeContainer); - return sectionContent; - } - - @Nullable - private View directSectionChild(@Nullable View child) { - View current = child; - while (current != null && current.getParent() instanceof View parent) { - if (parent.getParent() instanceof MaterialCardView) { - return current; - } - current = parent; - } - return null; - } - private int iconContainerCount( SharedPreferences prefs, String countKey, @@ -751,13 +571,7 @@ public final class LayoutFragment extends Fragment { boolean legacyEnabledDefault ) { int fallback = prefs.getBoolean(legacyEnabledKey, legacyEnabledDefault) ? countDefault : 0; - return clampIconContainerCount(prefs.getInt(countKey, fallback)); - } - - private int clampIconContainerCount(int value) { - return Math.max( - SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN, - Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, value)); + return SbtSettings.clampIconContainerCount(prefs.getInt(countKey, fallback)); } private void persistIconContainerCount( @@ -767,20 +581,13 @@ public final class LayoutFragment extends Fragment { int count ) { prefs.edit() - .putInt(countKey, clampIconContainerCount(count)) + .putInt(countKey, SbtSettings.clampIconContainerCount(count)) .putBoolean(legacyEnabledKey, count > 0) .apply(); SbtSettings.ensureReadable(requireContext()); } - private int dp(int value) { - return Math.round(value * getResources().getDisplayMetrics().density); - } - private void hideUnlockedSubpageObsoleteControls(View root) { - hide(root, R.id.layout_master_card); - hide(root, R.id.layout_padding_card); - hide(root, R.id.layout_ordering_card); hide(root, R.id.clock_position_mode_lockscreen); hide(root, R.id.notif_position_mode_aod); hide(root, R.id.notif_position_mode_lockscreen); @@ -872,7 +679,7 @@ public final class LayoutFragment extends Fragment { middleOptions.setVisibility(View.VISIBLE); } boolean enabled = isMiddleSelection(checkedId); - setViewTreeEnabled(middleOptions, enabled); + ViewTreeSupport.setEnabledRecursive(middleOptions, enabled); middleOptions.setAlpha(enabled ? 1f : 0.45f); updateMiddlePrompt(prompt, autoNearest.isChecked()); } @@ -893,22 +700,6 @@ public final class LayoutFragment extends Fragment { : R.string.layout_middle_side_prompt_attach_only); } - private void bindSortOrderGroup(SharedPreferences prefs, @Nullable RadioGroup group) { - if (prefs == null || group == null) { - return; - } - String current = prefs.getString( - SbtSettings.KEY_LAYOUT_NOTIF_SORT_ORDER, - SbtDefaults.LAYOUT_NOTIF_SORT_ORDER_DEFAULT); - group.check(sortOrderIdForValue(current)); - group.setOnCheckedChangeListener((radioGroup, checkedId) -> { - prefs.edit().putString( - SbtSettings.KEY_LAYOUT_NOTIF_SORT_ORDER, - sortOrderValueForId(checkedId)).apply(); - SbtSettings.ensureReadable(requireContext()); - }); - } - private void bindStepper(SharedPreferences prefs, @Nullable String key, @Nullable EditText input, @@ -977,7 +768,7 @@ public final class LayoutFragment extends Fragment { if (input == null) { return; } - int startValue = clampValue(key != null ? prefs.getInt(key, initialValue) : initialValue, minValue, maxValue); + int startValue = ViewTreeSupport.clamp(key != null ? prefs.getInt(key, initialValue) : initialValue, minValue, maxValue); input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(4)}); setNumericText(input, startValue); final boolean[] updating = new boolean[]{false}; @@ -989,7 +780,7 @@ public final class LayoutFragment extends Fragment { if (resetButton != null) { resetButton.setOnClickListener(v -> { updating[0] = true; - int next = clampValue(initialValue, minValue, maxValue); + int next = ViewTreeSupport.clamp(initialValue, minValue, maxValue); setNumericText(input, next); updating[0] = false; persistIntPref(prefs, key, next); @@ -1012,12 +803,13 @@ public final class LayoutFragment extends Fragment { } String text = s != null ? s.toString().trim() : ""; if (text.isEmpty() || "-".equals(text)) { - persistIntPref(prefs, key, clampValue(0, minValue, maxValue)); + persistIntPref(prefs, key, ViewTreeSupport.clamp(0, minValue, maxValue)); return; } try { - int parsed = clampValue(Integer.parseInt(text), minValue, maxValue); - if (parsed != Integer.parseInt(text)) { + int rawValue = Integer.parseInt(text); + int parsed = ViewTreeSupport.clamp(rawValue, minValue, maxValue); + if (parsed != rawValue) { updating[0] = true; setNumericText(input, parsed); updating[0] = false; @@ -1030,7 +822,7 @@ public final class LayoutFragment extends Fragment { input.setOnEditorActionListener((v, actionId, event) -> { if (actionId == EditorInfo.IME_ACTION_DONE) { - int next = clampValue(parseInt(input, 0), minValue, maxValue); + int next = ViewTreeSupport.clamp(ViewTreeSupport.parseInt(input, 0), minValue, maxValue); updating[0] = true; setNumericText(input, next); updating[0] = false; @@ -1043,7 +835,7 @@ public final class LayoutFragment extends Fragment { if (hasFocus) { return; } - int next = clampValue(parseInt(input, 0), minValue, maxValue); + int next = ViewTreeSupport.clamp(ViewTreeSupport.parseInt(input, 0), minValue, maxValue); updating[0] = true; setNumericText(input, next); updating[0] = false; @@ -1063,7 +855,7 @@ public final class LayoutFragment extends Fragment { return; } button.setOnClickListener(v -> { - int next = clampValue(parseInt(input, 0) + delta, minValue, maxValue); + int next = ViewTreeSupport.clamp(ViewTreeSupport.parseInt(input, 0) + delta, minValue, maxValue); updating[0] = true; setNumericText(input, next); updating[0] = false; @@ -1075,14 +867,13 @@ public final class LayoutFragment extends Fragment { @Nullable CheckBox checkBox, String key, boolean defaultValue) { - if (checkBox == null) { - return; - } - checkBox.setChecked(prefs.getBoolean(key, defaultValue)); - checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> { - prefs.edit().putBoolean(key, isChecked).apply(); - SbtSettings.ensureReadable(requireContext()); - }); + ViewTreeSupport.bindBooleanPreference( + requireContext(), + prefs, + checkBox, + key, + defaultValue, + false); } private void persistIntPref(SharedPreferences prefs, @Nullable String key, int value) { @@ -1093,19 +884,6 @@ public final class LayoutFragment extends Fragment { SbtSettings.ensureReadable(requireContext()); } - private void setViewTreeEnabled(View view, boolean enabled) { - if (view == null) { - return; - } - view.setEnabled(enabled); - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - for (int i = 0; i < group.getChildCount(); i++) { - setViewTreeEnabled(group.getChildAt(i), enabled); - } - } - } - private int positionIdForValue(int groupId, @Nullable String value) { String normalized = value != null ? value : "left"; if (groupId == R.id.clock_position_group) { @@ -1188,35 +966,6 @@ public final class LayoutFragment extends Fragment { return id != View.NO_ID ? id : resolveLeftSideId(group); } - private int parseInt(EditText input, int fallback) { - if (input == null) { - return fallback; - } - String text = input.getText() != null ? input.getText().toString().trim() : ""; - if (text.isEmpty() || "-".equals(text)) { - return fallback; - } - try { - return Integer.parseInt(text); - } catch (NumberFormatException ignored) { - return fallback; - } - } - - private int clampPadding(int value) { - return clampValue(value, PADDING_MIN, PADDING_MAX); - } - - private int clampValue(int value, int minValue, int maxValue) { - if (value < minValue) { - return minValue; - } - if (value > maxValue) { - return maxValue; - } - return value; - } - private void setNumericText(EditText input, int value) { if (input == null) { return; @@ -1228,208 +977,4 @@ public final class LayoutFragment extends Fragment { } } - private int sortOrderIdForValue(@Nullable String value) { - if ("stock".equals(value)) { - return R.id.layout_sort_icons_stock; - } - if ("reversed".equals(value)) { - return R.id.layout_sort_icons_reversed; - } - return R.id.layout_sort_icons_automatic; - } - - private String sortOrderValueForId(int checkedId) { - if (checkedId == R.id.layout_sort_icons_stock) { - return "stock"; - } - if (checkedId == R.id.layout_sort_icons_reversed) { - return "reversed"; - } - return "automatic"; - } - - private void setupOrderingList(View root, SharedPreferences prefs) { - RecyclerView list = root.findViewById(R.id.layout_ordering_list); - if (list == null) { - return; - } - ArrayList items = parseOrderingItems( - prefs.getString(SbtSettings.KEY_LAYOUT_ITEM_ORDER, SbtDefaults.LAYOUT_ITEM_ORDER_DEFAULT)); - - list.setLayoutManager(new LinearLayoutManager(requireContext())); - OrderingAdapter adapter = new OrderingAdapter(items, order -> { - prefs.edit().putString(SbtSettings.KEY_LAYOUT_ITEM_ORDER, order).apply(); - SbtSettings.ensureReadable(requireContext()); - }); - list.setAdapter(adapter); - - final ItemTouchHelper[] itemTouchHelperHolder = new ItemTouchHelper[1]; - ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback( - ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) { - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, - @NonNull RecyclerView.ViewHolder viewHolder, - @NonNull RecyclerView.ViewHolder target) { - int from = viewHolder.getAdapterPosition(); - int to = target.getAdapterPosition(); - if (from == RecyclerView.NO_POSITION || to == RecyclerView.NO_POSITION) { - return false; - } - adapter.moveItem(from, to); - return true; - } - - @Override - public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { - } - - @Override - public boolean isLongPressDragEnabled() { - return false; - } - }); - itemTouchHelper.attachToRecyclerView(list); - itemTouchHelperHolder[0] = itemTouchHelper; - adapter.setDragStarter(holder -> { - ItemTouchHelper helper = itemTouchHelperHolder[0]; - if (helper != null) { - helper.startDrag(holder); - } - }); - } - - private ArrayList parseOrderingItems(@Nullable String encoded) { - if (encoded == null || encoded.isEmpty() || "clock,notifications,status".equals(encoded)) { - encoded = SbtDefaults.LAYOUT_ITEM_ORDER_DEFAULT; - } - ArrayList items = new ArrayList<>(); - for (String token : encoded.split(",")) { - addOrderingItemIfValid(items, token != null ? token.trim() : ""); - } - addCarrierOrderingItemIfMissing(items); - addOrderingItemIfValid(items, "clock"); - addOrderingItemIfValid(items, "chip"); - addOrderingItemIfValid(items, "status"); - addOrderingItemIfValid(items, "notifications"); - return items; - } - - private void addCarrierOrderingItemIfMissing(ArrayList items) { - for (OrderingItem item : items) { - if ("carrier".equals(item.key)) { - return; - } - } - items.add(0, new OrderingItem("carrier", getString(R.string.layout_ordering_carrier))); - } - - private void addOrderingItemIfValid(ArrayList items, String key) { - if (key == null || key.isEmpty()) { - return; - } - for (OrderingItem item : items) { - if (item.key.equals(key)) { - return; - } - } - int labelRes; - if ("carrier".equals(key)) { - labelRes = R.string.layout_ordering_carrier; - } else if ("clock".equals(key)) { - labelRes = R.string.layout_ordering_clock; - } else if ("chip".equals(key)) { - labelRes = R.string.layout_ordering_chip; - } else if ("notifications".equals(key)) { - labelRes = R.string.layout_ordering_notif_icons; - } else if ("status".equals(key)) { - labelRes = R.string.layout_ordering_status_icons; - } else { - return; - } - items.add(new OrderingItem(key, getString(labelRes))); - } - - private record OrderingItem(String key, String label) { - } - - private static final class OrderingAdapter extends RecyclerView.Adapter { - interface DragStarter { - void startDrag(OrderingViewHolder holder); - } - - interface OrderChangedListener { - void onOrderChanged(String encodedOrder); - } - - private final List items; - private final OrderChangedListener orderChangedListener; - private DragStarter dragStarter; - - OrderingAdapter(List items, OrderChangedListener orderChangedListener) { - this.items = items; - this.orderChangedListener = orderChangedListener; - } - - void setDragStarter(DragStarter dragStarter) { - this.dragStarter = dragStarter; - } - - void moveItem(int from, int to) { - if (from == to || from < 0 || to < 0 || from >= items.size() || to >= items.size()) { - return; - } - Collections.swap(items, from, to); - notifyItemMoved(from, to); - if (orderChangedListener != null) { - orderChangedListener.onOrderChanged(encodeOrder()); - } - } - - private String encodeOrder() { - StringBuilder builder = new StringBuilder(); - for (OrderingItem item : items) { - if (builder.length() > 0) { - builder.append(','); - } - builder.append(item.key); - } - return builder.toString(); - } - - @NonNull - @Override - public OrderingViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_layout_ordering, parent, false); - return new OrderingViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull OrderingViewHolder holder, int position) { - holder.label.setText(items.get(position).label); - holder.handle.setOnTouchListener((v, event) -> { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN && dragStarter != null) { - dragStarter.startDrag(holder); - return true; - } - return false; - }); - } - - @Override - public int getItemCount() { - return items.size(); - } - } - - private static final class OrderingViewHolder extends RecyclerView.ViewHolder { - final TextView label; - final TextView handle; - - OrderingViewHolder(@NonNull View itemView) { - super(itemView); - label = itemView.findViewById(R.id.layout_ordering_item_label); - handle = itemView.findViewById(R.id.layout_ordering_item_handle); - } - } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutHomeFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutHomeFragment.java index 352b7a5..20d6d91 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutHomeFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutHomeFragment.java @@ -3,11 +3,9 @@ package se.ajpanton.statusbartweak.shell.ui; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; -import android.text.InputFilter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.CheckBox; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RadioGroup; @@ -19,7 +17,6 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.google.android.material.button.MaterialButton; -import com.google.android.material.card.MaterialCardView; import com.google.android.material.switchmaterial.SwitchMaterial; import se.ajpanton.statusbartweak.R; @@ -41,9 +38,12 @@ public final class LayoutHomeFragment extends Fragment { Context context = requireContext(); SharedPreferences prefs = context.getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE); ScrollView scroll = new ScrollView(context); - LinearLayout root = new LinearLayout(context); - root.setOrientation(LinearLayout.VERTICAL); - root.setPadding(dp(20), dp(20), dp(20), dp(20)); + LinearLayout root = vertical(context); + root.setPadding( + ViewTreeSupport.dp(context, 20), + ViewTreeSupport.dp(context, 20), + ViewTreeSupport.dp(context, 20), + ViewTreeSupport.dp(context, 20)); scroll.addView(root); TextView title = title(context, R.string.layout_home_title); @@ -69,7 +69,7 @@ public final class LayoutHomeFragment extends Fragment { root.addView(miscCard(context, prefs)); - Runnable refreshAvailability = () -> refreshAvailability(prefs, master.isChecked()); + Runnable refreshAvailability = () -> refreshAvailability(context, prefs, master.isChecked()); master.setOnCheckedChangeListener((buttonView, isChecked) -> { prefs.edit().putBoolean(SbtSettings.KEY_CLOCK_ENABLED, isChecked).apply(); SbtSettings.ensureReadable(context); @@ -79,15 +79,15 @@ public final class LayoutHomeFragment extends Fragment { return scroll; } - private void refreshAvailability(SharedPreferences prefs, boolean layoutEnabled) { - setViewTreeEnabled(layoutOnContainer, layoutEnabled); + private void refreshAvailability(Context context, SharedPreferences prefs, boolean layoutEnabled) { + ViewTreeSupport.setEnabledRecursive(layoutOnContainer, layoutEnabled); layoutOnContainer.setAlpha(layoutEnabled ? 1f : 0.45f); - setViewTreeEnabled(miscLayoutOnContainer, layoutEnabled); + ViewTreeSupport.setEnabledRecursive(miscLayoutOnContainer, layoutEnabled); miscLayoutOnContainer.setAlpha(layoutEnabled ? 1f : 0.45f); - setViewTreeEnabled(miscUnlockedOffOnlyContainer, !layoutEnabled); + ViewTreeSupport.setEnabledRecursive(miscUnlockedOffOnlyContainer, !layoutEnabled); miscUnlockedOffOnlyContainer.setAlpha(layoutEnabled ? 0.45f : 1f); LockedNotificationModeUi.bind( - requireContext(), + context, prefs, lockedModeGroup, lockedModeHint); @@ -111,12 +111,13 @@ public final class LayoutHomeFragment extends Fragment { SbtDefaults.LAYOUT_PADDING_LEFT_PX_DEFAULT, -999, 999, - checkBox( + ViewTreeSupport.boundCheckBox( context, prefs, R.string.layout_padding_add_stock, SbtSettings.KEY_LAYOUT_PADDING_LEFT_ADD_STOCK, - SbtDefaults.LAYOUT_PADDING_LEFT_ADD_STOCK_DEFAULT))); + SbtDefaults.LAYOUT_PADDING_LEFT_ADD_STOCK_DEFAULT, + false))); content.addView(stepper( context, prefs, @@ -125,12 +126,13 @@ public final class LayoutHomeFragment extends Fragment { SbtDefaults.LAYOUT_PADDING_RIGHT_PX_DEFAULT, -999, 999, - checkBox( + ViewTreeSupport.boundCheckBox( context, prefs, R.string.layout_padding_add_stock, SbtSettings.KEY_LAYOUT_PADDING_RIGHT_ADD_STOCK, - SbtDefaults.LAYOUT_PADDING_RIGHT_ADD_STOCK_DEFAULT))); + SbtDefaults.LAYOUT_PADDING_RIGHT_ADD_STOCK_DEFAULT, + false))); content.addView(stepper( context, prefs, @@ -145,12 +147,13 @@ public final class LayoutHomeFragment extends Fragment { private View miscCard(Context context, SharedPreferences prefs) { LinearLayout content = vertical(context); miscLayoutOnContainer = vertical(context); - miscLayoutOnContainer.addView(checkBox( + miscLayoutOnContainer.addView(ViewTreeSupport.boundCheckBox( context, prefs, R.string.layout_ignore_under_display_cameras, SbtSettings.KEY_LAYOUT_IGNORE_UNDER_DISPLAY_CAMERAS, - SbtDefaults.LAYOUT_IGNORE_UNDER_DISPLAY_CAMERAS_DEFAULT)); + SbtDefaults.LAYOUT_IGNORE_UNDER_DISPLAY_CAMERAS_DEFAULT, + false)); content.addView(miscLayoutOnContainer); miscUnlockedOffOnlyContainer = vertical(context); miscUnlockedOffOnlyContainer.addView(stepper( @@ -193,29 +196,25 @@ public final class LayoutHomeFragment extends Fragment { LinearLayout row = new LinearLayout(context); row.setOrientation(LinearLayout.HORIZONTAL); row.setGravity(android.view.Gravity.CENTER_VERTICAL); - row.setPadding(0, dp(6), 0, dp(8)); - MaterialButton minus = button(context, "-"); - MaterialButton plus = button(context, "+"); - EditText input = new EditText(context); - input.setGravity(android.view.Gravity.CENTER); - input.setSingleLine(true); - input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED); - input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(5)}); - input.setText(String.valueOf(clamp(prefs.getInt(key, defaultValue), min, max))); - row.addView(minus, buttonParams()); - row.addView(input, inputParams()); - row.addView(plus, buttonParams()); + row.setPadding(0, ViewTreeSupport.dp(context, 6), 0, ViewTreeSupport.dp(context, 8)); + MaterialButton minus = ViewTreeSupport.outlinedStepperButton(context, "-"); + MaterialButton plus = ViewTreeSupport.outlinedStepperButton(context, "+"); + EditText input = ViewTreeSupport.numericInput(context, true, 5); + input.setText(String.valueOf(ViewTreeSupport.clamp(prefs.getInt(key, defaultValue), min, max))); + row.addView(minus, ViewTreeSupport.stepperButtonParams(context)); + row.addView(input, ViewTreeSupport.stepperInputParams(context)); + row.addView(plus, ViewTreeSupport.stepperButtonParams(context)); if (trailingView != null) { LinearLayout.LayoutParams trailingParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - trailingParams.leftMargin = dp(8); + trailingParams.leftMargin = ViewTreeSupport.dp(context, 8); row.addView(trailingView, trailingParams); } outer.addView(row); View.OnClickListener listener = v -> { int delta = v == minus ? -1 : 1; - int next = clamp(parseInt(input, defaultValue) + delta, min, max); + int next = ViewTreeSupport.clamp(ViewTreeSupport.parseInt(input, defaultValue) + delta, min, max); input.setText(String.valueOf(next)); prefs.edit().putInt(key, next).apply(); SbtSettings.ensureReadable(context); @@ -224,7 +223,7 @@ public final class LayoutHomeFragment extends Fragment { plus.setOnClickListener(listener); input.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { - int next = clamp(parseInt(input, defaultValue), min, max); + int next = ViewTreeSupport.clamp(ViewTreeSupport.parseInt(input, defaultValue), min, max); input.setText(String.valueOf(next)); prefs.edit().putInt(key, next).apply(); SbtSettings.ensureReadable(context); @@ -233,118 +232,23 @@ public final class LayoutHomeFragment extends Fragment { return outer; } - private CheckBox checkBox( - Context context, - SharedPreferences prefs, - int label, - String key, - boolean defaultValue - ) { - CheckBox checkBox = new CheckBox(context); - checkBox.setText(label); - checkBox.setChecked(prefs.getBoolean(key, defaultValue)); - checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> { - prefs.edit().putBoolean(key, isChecked).apply(); - SbtSettings.ensureReadable(context); - }); - return checkBox; - } - - private MaterialButton button(Context context, String text) { - MaterialButton button = new MaterialButton(context, null, com.google.android.material.R.attr.materialButtonOutlinedStyle); - button.setText(text); - button.setMinWidth(dp(40)); - button.setMinHeight(dp(40)); - button.setPadding(0, button.getPaddingTop(), 0, button.getPaddingBottom()); - return button; - } - - private LinearLayout.LayoutParams buttonParams() { - return new LinearLayout.LayoutParams(dp(40), ViewGroup.LayoutParams.WRAP_CONTENT); - } - - private LinearLayout.LayoutParams inputParams() { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - dp(48), - ViewGroup.LayoutParams.WRAP_CONTENT); - params.leftMargin = dp(8); - params.rightMargin = dp(8); - return params; - } - - private MaterialCardView card(Context context, int titleId, View content) { - MaterialCardView card = new MaterialCardView(context); - LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - cardParams.topMargin = dp(16); - card.setLayoutParams(cardParams); - card.setUseCompatPadding(true); - card.setStrokeColor(context.getColor(R.color.sbt_card_outline)); - card.setStrokeWidth(dp(1)); - LinearLayout inner = vertical(context); - inner.setPadding(dp(16), dp(16), dp(16), dp(16)); - TextView title = sectionTitle(context, titleId); - inner.addView(title); - inner.addView(content); - card.addView(inner); - return card; + private View card(Context context, int titleId, View content) { + return ViewTreeSupport.outlinedCard(context, sectionTitle(context, titleId), content); } private LinearLayout vertical(Context context) { - LinearLayout layout = new LinearLayout(context); - layout.setOrientation(LinearLayout.VERTICAL); - return layout; + return ViewTreeSupport.verticalLayout(context); } private TextView title(Context context, int stringId) { - TextView view = new TextView(context); - view.setText(stringId); - view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline5); - return view; + return ViewTreeSupport.titleText(context, stringId); } private TextView sectionTitle(Context context, int stringId) { - TextView view = new TextView(context); - view.setText(stringId); - view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Subtitle1); - view.setAllCaps(true); - view.setTypeface(view.getTypeface(), android.graphics.Typeface.BOLD); - return view; + return ViewTreeSupport.sectionTitleText(context, stringId, true); } private TextView body(Context context, int stringId) { - TextView view = new TextView(context); - view.setText(stringId); - view.setPadding(0, dp(8), 0, 0); - return view; - } - - private void setViewTreeEnabled(View view, boolean enabled) { - if (view == null) { - return; - } - view.setEnabled(enabled); - if (view instanceof ViewGroup group) { - for (int i = 0; i < group.getChildCount(); i++) { - setViewTreeEnabled(group.getChildAt(i), enabled); - } - } - } - - private int parseInt(EditText input, int fallback) { - try { - return Integer.parseInt(input.getText().toString().trim()); - } catch (Throwable ignored) { - return fallback; - } - } - - private int clamp(int value, int min, int max) { - return Math.max(min, Math.min(max, value)); - } - - private int dp(int value) { - return Math.round(value * getResources().getDisplayMetrics().density); + return ViewTreeSupport.bodyText(context, stringId, 0); } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutOrderingUi.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutOrderingUi.java index 84e8505..82d4302 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutOrderingUi.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutOrderingUi.java @@ -17,8 +17,6 @@ import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.card.MaterialCardView; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -41,7 +39,7 @@ final class LayoutOrderingUi { LinearLayout.LayoutParams listParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - listParams.topMargin = dp(context, 12); + listParams.topMargin = ViewTreeSupport.dp(context, 12); content.addView(list, listParams); bindOrderingList(context, prefs, list); @@ -50,7 +48,7 @@ final class LayoutOrderingUi { LinearLayout.LayoutParams sortTitleParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - sortTitleParams.topMargin = dp(context, 16); + sortTitleParams.topMargin = ViewTreeSupport.dp(context, 16); content.addView(sortTitle, sortTitleParams); RadioGroup sortGroup = new RadioGroup(context); @@ -70,7 +68,7 @@ final class LayoutOrderingUi { LinearLayout.LayoutParams sortParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - sortParams.topMargin = dp(context, 8); + sortParams.topMargin = ViewTreeSupport.dp(context, 8); content.addView(sortGroup, sortParams); return card(context, R.string.layout_ordering_title, content); @@ -227,52 +225,25 @@ final class LayoutOrderingUi { return tag instanceof String value ? value : "automatic"; } - private static MaterialCardView card(Context context, int titleId, View content) { - MaterialCardView card = new MaterialCardView(context); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - params.topMargin = dp(context, 16); - card.setLayoutParams(params); - card.setUseCompatPadding(true); - card.setStrokeColor(context.getColor(R.color.sbt_card_outline)); - card.setStrokeWidth(dp(context, 1)); - - LinearLayout inner = vertical(context); - inner.setPadding(dp(context, 16), dp(context, 16), dp(context, 16), dp(context, 16)); - inner.addView(sectionTitle(context, titleId)); - inner.addView(content); - card.addView(inner); - return card; + private static View card(Context context, int titleId, View content) { + return ViewTreeSupport.outlinedCard( + context, + ViewTreeSupport.sectionTitleText(context, titleId, true), + content); } private static LinearLayout vertical(Context context) { - LinearLayout layout = new LinearLayout(context); - layout.setOrientation(LinearLayout.VERTICAL); - return layout; - } - - private static TextView sectionTitle(Context context, int stringId) { - TextView view = new TextView(context); - view.setText(stringId); - view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Subtitle1); - view.setAllCaps(true); - view.setTypeface(view.getTypeface(), android.graphics.Typeface.BOLD); - return view; + return ViewTreeSupport.verticalLayout(context); } private static TextView body(Context context, int stringId) { TextView view = new TextView(context); view.setText(stringId); view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body2); - view.setPadding(0, dp(context, 8), 0, 0); + view.setPadding(0, ViewTreeSupport.dp(context, 8), 0, 0); return view; } - private static int dp(Context context, int value) { - return Math.round(value * context.getResources().getDisplayMetrics().density); - } - private record OrderingItem(String key, String label) { } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutSectionCopySupport.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutSectionCopySupport.java index 397481b..d8d7f62 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutSectionCopySupport.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutSectionCopySupport.java @@ -274,11 +274,11 @@ final class LayoutSectionCopySupport { if (mode == Mode.LOCKSCREEN) { return iconKeys( SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_LOCKSCREEN, - "layout_lock_notifications_count", - "layout_lock_notifications_position", - "layout_lock_notifications_middle_auto_nearest_side", - "layout_lock_notifications_middle_side", - "layout_lock_notifications_vertical_offset_px", + SbtSettings.KEY_LAYOUT_NOTIF_LOCK_COUNT, + SbtSettings.KEY_LAYOUT_NOTIF_LOCK_POSITION, + SbtSettings.KEY_LAYOUT_NOTIF_LOCK_MIDDLE_AUTO_NEAREST_SIDE, + SbtSettings.KEY_LAYOUT_NOTIF_LOCK_MIDDLE_SIDE, + SbtSettings.KEY_LAYOUT_NOTIF_LOCK_VERTICAL_OFFSET_PX, SbtSettings.layoutLockNotifIconHeightKey(), SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT, SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT, @@ -291,11 +291,11 @@ final class LayoutSectionCopySupport { if (mode == Mode.AOD) { return iconKeys( SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_AOD, - "layout_aod_notifications_count", - "layout_aod_notifications_position", - "layout_aod_notifications_middle_auto_nearest_side", - "layout_aod_notifications_middle_side", - "layout_aod_notifications_vertical_offset_px", + SbtSettings.KEY_LAYOUT_NOTIF_AOD_COUNT, + SbtSettings.KEY_LAYOUT_NOTIF_AOD_POSITION, + SbtSettings.KEY_LAYOUT_NOTIF_AOD_MIDDLE_AUTO_NEAREST_SIDE, + SbtSettings.KEY_LAYOUT_NOTIF_AOD_MIDDLE_SIDE, + SbtSettings.KEY_LAYOUT_NOTIF_AOD_VERTICAL_OFFSET_PX, SbtSettings.layoutAodNotifIconHeightKey(), SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT, SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT, @@ -326,11 +326,11 @@ final class LayoutSectionCopySupport { if (mode == Mode.LOCKSCREEN) { return iconKeys( SbtSettings.KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN, - "layout_lock_status_count", - "layout_lock_status_position", - "layout_lock_status_middle_auto_nearest_side", - "layout_lock_status_middle_side", - "layout_lock_status_vertical_offset_px", + SbtSettings.KEY_LAYOUT_STATUS_LOCK_COUNT, + SbtSettings.KEY_LAYOUT_STATUS_LOCK_POSITION, + SbtSettings.KEY_LAYOUT_STATUS_LOCK_MIDDLE_AUTO_NEAREST_SIDE, + SbtSettings.KEY_LAYOUT_STATUS_LOCK_MIDDLE_SIDE, + SbtSettings.KEY_LAYOUT_STATUS_LOCK_VERTICAL_OFFSET_PX, SbtSettings.layoutLockStatusIconHeightKey(), SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT, SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT, @@ -343,11 +343,11 @@ final class LayoutSectionCopySupport { if (mode == Mode.AOD) { return iconKeys( SbtSettings.KEY_LAYOUT_STATUS_ENABLED_AOD, - "layout_aod_status_count", - "layout_aod_status_position", - "layout_aod_status_middle_auto_nearest_side", - "layout_aod_status_middle_side", - "layout_aod_status_vertical_offset_px", + SbtSettings.KEY_LAYOUT_STATUS_AOD_COUNT, + SbtSettings.KEY_LAYOUT_STATUS_AOD_POSITION, + SbtSettings.KEY_LAYOUT_STATUS_AOD_MIDDLE_AUTO_NEAREST_SIDE, + SbtSettings.KEY_LAYOUT_STATUS_AOD_MIDDLE_SIDE, + SbtSettings.KEY_LAYOUT_STATUS_AOD_VERTICAL_OFFSET_PX, SbtSettings.layoutAodStatusIconHeightKey(), SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT, SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT, @@ -458,7 +458,7 @@ final class LayoutSectionCopySupport { LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - rowParams.leftMargin = dp(context, 12); + rowParams.leftMargin = ViewTreeSupport.dp(context, 12); if (parent.getOrientation() == LinearLayout.HORIZONTAL) { parent.addView(row, rowParams); } else { @@ -518,10 +518,6 @@ final class LayoutSectionCopySupport { } } - private static int dp(Context context, int value) { - return Math.round(value * context.getResources().getDisplayMetrics().density); - } - private record Option(@Nullable Mode mode, String label, boolean enabled) { @Override public String toString() { @@ -718,10 +714,7 @@ final class LayoutSectionCopySupport { } private String suffixed(String base, int index) { - if (base == null || index <= 0) { - return base; - } - return base + "_" + (index + 1); + return SbtSettings.indexedKey(base, index); } } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutXmlCardSupport.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutXmlCardSupport.java new file mode 100644 index 0000000..f773468 --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LayoutXmlCardSupport.java @@ -0,0 +1,94 @@ +package se.ajpanton.statusbartweak.shell.ui; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import com.google.android.material.card.MaterialCardView; + +import se.ajpanton.statusbartweak.R; + +final class LayoutXmlCardSupport { + private LayoutXmlCardSupport() { + } + + static View inflateCard(Context context, @Nullable ViewGroup parentForInflation, int cardId) { + View fullLayout = LayoutInflater.from(context) + .inflate(R.layout.fragment_layout, parentForInflation, false); + View card = fullLayout.findViewById(cardId); + if (card == null) { + return new View(context); + } + if (card.getParent() instanceof ViewGroup parent) { + parent.removeView(card); + } + return card; + } + + static void setCardTitle(View card, int titleId) { + TextView title = findCardTitle(card); + if (title != null) { + title.setText(titleId); + } + } + + static void setIndexedCardTitle( + Context context, + View card, + int titleId, + int index, + int count + ) { + TextView title = findCardTitle(card); + if (title == null) { + return; + } + if (count > 1) { + title.setText(context.getString(titleId) + " " + (index + 1)); + } else { + title.setText(titleId); + } + } + + @Nullable + static TextView findCardTitle(View card) { + if (!(card instanceof ViewGroup cardGroup) || cardGroup.getChildCount() <= 0) { + return null; + } + View inner = cardGroup.getChildAt(0); + if (!(inner instanceof ViewGroup innerGroup) || innerGroup.getChildCount() <= 0) { + return null; + } + View title = innerGroup.getChildAt(0); + return title instanceof TextView textView ? textView : null; + } + + @Nullable + static LinearLayout removeModeContainerForCheckbox(View root, int checkboxId) { + CheckBox checkbox = root.findViewById(checkboxId); + View modeContainer = directSectionChild(checkbox); + if (modeContainer == null || !(modeContainer.getParent() instanceof LinearLayout sectionContent)) { + return null; + } + sectionContent.removeView(modeContainer); + return sectionContent; + } + + @Nullable + static View directSectionChild(@Nullable View child) { + View current = child; + while (current != null && current.getParent() instanceof View parent) { + if (parent.getParent() instanceof MaterialCardView) { + return current; + } + current = parent; + } + return null; + } +} diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LockedSceneLayoutFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LockedSceneLayoutFragment.java index c4968f4..b838b8d 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LockedSceneLayoutFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/LockedSceneLayoutFragment.java @@ -20,8 +20,6 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.google.android.material.button.MaterialButton; -import com.google.android.material.card.MaterialCardView; - import java.util.LinkedHashMap; import se.ajpanton.statusbartweak.R; @@ -47,7 +45,11 @@ abstract class LockedSceneLayoutFragment extends Fragment { SharedPreferences prefs = context.getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE); ScrollView scroll = new ScrollView(context); LinearLayout root = vertical(context); - root.setPadding(dp(20), dp(20), dp(20), dp(20)); + root.setPadding( + ViewTreeSupport.dp(context, 20), + ViewTreeSupport.dp(context, 20), + ViewTreeSupport.dp(context, 20), + ViewTreeSupport.dp(context, 20)); scroll.addView(root); root.addView(title(context, aod ? R.string.section_aod : R.string.section_lockscreen)); @@ -146,7 +148,7 @@ abstract class LockedSceneLayoutFragment extends Fragment { SbtDefaults.LAYOUT_CARRIER_MIDDLE_SIDE_DEFAULT, SbtSettings.KEY_LAYOUT_CARRIER_VERTICAL_OFFSET_PX, SbtDefaults.LAYOUT_CARRIER_VERTICAL_OFFSET_PX_DEFAULT); - setCardTitle(carrierCard, R.string.layout_carrier_position_title); + LayoutXmlCardSupport.setCardTitle(carrierCard, R.string.layout_carrier_position_title); cardsByOrderKey.put("carrier", carrierCard); } cardsByOrderKey.put("status", configuredIconContainersCardFromLayout( @@ -223,13 +225,6 @@ abstract class LockedSceneLayoutFragment extends Fragment { } } - private void setCardTitle(View card, int titleId) { - TextView title = findCardTitle(card); - if (title != null) { - title.setText(titleId); - } - } - private View configuredPositionCardFromLayout( Context context, SharedPreferences prefs, @@ -258,7 +253,7 @@ abstract class LockedSceneLayoutFragment extends Fragment { String verticalOffsetKey, int verticalOffsetDefault ) { - View card = inflateLayoutCard(context, cardId); + View card = LayoutXmlCardSupport.inflateCard(context, dynamicContent, cardId); configureSingleEnabledCheckbox(card, prefs, enabledCheckboxId, enabledKey, enabledDefault); bindXmlPositionSection( card, @@ -459,9 +454,9 @@ abstract class LockedSceneLayoutFragment extends Fragment { int count, @Nullable Runnable onCountChanged ) { - View card = inflateLayoutCard(context, cardId); + View card = LayoutXmlCardSupport.inflateCard(context, dynamicContent, cardId); removeSiblingModeCheckboxes(card, modeCheckboxId); - LinearLayout sectionContent = removeModeContainerForCheckbox(card, modeCheckboxId); + LinearLayout sectionContent = LayoutXmlCardSupport.removeModeContainerForCheckbox(card, modeCheckboxId); if (sectionContent != null && countKey != null && onCountChanged != null) { int insertIndex = Math.min(1, sectionContent.getChildCount()); sectionContent.addView( @@ -480,7 +475,7 @@ abstract class LockedSceneLayoutFragment extends Fragment { Math.min(insertIndex + 1, sectionContent.getChildCount())); } } - updateIconCardTitle(card, titleId, index, count); + LayoutXmlCardSupport.setIndexedCardTitle(context, card, titleId, index, count); bindXmlPositionSection( card, prefs, @@ -511,43 +506,6 @@ abstract class LockedSceneLayoutFragment extends Fragment { return card; } - private void updateIconCardTitle(View card, int titleId, int index, int count) { - TextView title = findCardTitle(card); - if (title == null) { - return; - } - if (count > 1) { - title.setText(getString(titleId) + " " + (index + 1)); - } else { - title.setText(titleId); - } - } - - private View inflateLayoutCard(Context context, int cardId) { - View fullLayout = LayoutInflater.from(context).inflate(R.layout.fragment_layout, dynamicContent, false); - View card = fullLayout.findViewById(cardId); - if (card == null) { - return new View(context); - } - if (card.getParent() instanceof ViewGroup parent) { - parent.removeView(card); - } - return card; - } - - @Nullable - private TextView findCardTitle(View card) { - if (!(card instanceof ViewGroup cardGroup) || cardGroup.getChildCount() <= 0) { - return null; - } - View inner = cardGroup.getChildAt(0); - if (!(inner instanceof ViewGroup innerGroup) || innerGroup.getChildCount() <= 0) { - return null; - } - View title = innerGroup.getChildAt(0); - return title instanceof TextView textView ? textView : null; - } - private void configureSingleEnabledCheckbox( View root, SharedPreferences prefs, @@ -561,11 +519,13 @@ abstract class LockedSceneLayoutFragment extends Fragment { } hideSiblingCheckBoxes(enabled); enabled.setText(R.string.layout_item_enabled); - enabled.setChecked(prefs.getBoolean(key, defaultValue)); - enabled.setOnCheckedChangeListener((buttonView, isChecked) -> { - prefs.edit().putBoolean(key, isChecked).apply(); - SbtSettings.ensureReadable(requireContext()); - }); + ViewTreeSupport.bindBooleanPreference( + requireContext(), + prefs, + enabled, + key, + defaultValue, + false); } private void removeSiblingModeCheckboxes(View root, int keptCheckboxId) { @@ -577,47 +537,12 @@ abstract class LockedSceneLayoutFragment extends Fragment { if (kept == null) { return; } - View modeContainer = directSectionChild(kept); + View modeContainer = LayoutXmlCardSupport.directSectionChild(kept); if (modeContainer instanceof ViewGroup group) { - hideOtherCheckBoxes(group, kept); + ViewTreeSupport.hideCheckBoxesExcept(group, kept); } } - private void hideOtherCheckBoxes(View view, CheckBox kept) { - if (view instanceof CheckBox checkBox && checkBox != kept) { - checkBox.setVisibility(View.GONE); - return; - } - if (view instanceof ViewGroup group) { - for (int i = 0; i < group.getChildCount(); i++) { - hideOtherCheckBoxes(group.getChildAt(i), kept); - } - } - } - - @Nullable - private LinearLayout removeModeContainerForCheckbox(View root, int checkboxId) { - CheckBox checkbox = root.findViewById(checkboxId); - View modeContainer = directSectionChild(checkbox); - if (modeContainer == null || !(modeContainer.getParent() instanceof LinearLayout sectionContent)) { - return null; - } - sectionContent.removeView(modeContainer); - return sectionContent; - } - - @Nullable - private View directSectionChild(@Nullable View child) { - View current = child; - while (current != null && current.getParent() instanceof View parent) { - if (parent.getParent() instanceof MaterialCardView) { - return current; - } - current = parent; - } - return null; - } - private View cardsNotificationCard(Context context, SharedPreferences prefs, @Nullable Runnable refresh) { LinearLayout content = vertical(context); LayoutSectionCopySupport.addCardsDropdown(context, prefs, content, copyMode(), refresh); @@ -637,18 +562,20 @@ abstract class LockedSceneLayoutFragment extends Fragment { aod ? SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_DEFAULT : SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT, aod ? SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_MIN : SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MIN, aod ? SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_MAX : SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MAX)); - content.addView(checkBox( + content.addView(ViewTreeSupport.boundCheckBox( context, prefs, R.string.label_even_distribution, aod ? SbtSettings.KEY_CARDS_AOD_EVEN_DISTRIBUTION : SbtSettings.KEY_CARDS_LOCK_EVEN_DISTRIBUTION, - aod ? SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT : SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT)); - content.addView(checkBox( + aod ? SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT : SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT, + false)); + content.addView(ViewTreeSupport.boundCheckBox( context, prefs, R.string.label_limit_screen_width, aod ? SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH : SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH, - aod ? SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT : SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT)); + aod ? SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT : SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT, + false)); content.addView(sizeStepper( context, prefs, @@ -675,18 +602,19 @@ abstract class LockedSceneLayoutFragment extends Fragment { LinearLayout row = new LinearLayout(context); row.setOrientation(LinearLayout.HORIZONTAL); row.setGravity(Gravity.CENTER_VERTICAL); - MaterialButton minus = button(context, "-"); - MaterialButton plus = button(context, "+"); - EditText input = numericInput(context, false); + MaterialButton minus = ViewTreeSupport.outlinedStepperButton(context, "-"); + MaterialButton plus = ViewTreeSupport.outlinedStepperButton(context, "+"); + EditText input = ViewTreeSupport.numericInput(context, false, 4); input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(1)}); input.setText(String.valueOf(current)); - row.addView(minus, buttonParams()); - row.addView(input, inputParams()); - row.addView(plus, buttonParams()); + row.addView(minus, ViewTreeSupport.stepperButtonParams(context)); + row.addView(input, ViewTreeSupport.stepperInputParams(context)); + row.addView(plus, ViewTreeSupport.stepperButtonParams(context)); outer.addView(row); View.OnClickListener listener = view -> { - int next = clampIconContainerCount(parseInt(input, current) + (view == minus ? -1 : 1)); + int next = SbtSettings.clampIconContainerCount( + ViewTreeSupport.parseInt(input, current) + (view == minus ? -1 : 1)); persistIconContainerCount(prefs, countKey, legacyEnabledKey, next); onChanged.run(); }; @@ -694,7 +622,7 @@ abstract class LockedSceneLayoutFragment extends Fragment { plus.setOnClickListener(listener); input.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { - int next = clampIconContainerCount(parseInt(input, current)); + int next = SbtSettings.clampIconContainerCount(ViewTreeSupport.parseInt(input, current)); persistIconContainerCount(prefs, countKey, legacyEnabledKey, next); onChanged.run(); } @@ -771,7 +699,7 @@ abstract class LockedSceneLayoutFragment extends Fragment { if (input == null) { return; } - int start = clamp(prefs.getInt(key, defaultValue), min, max); + int start = ViewTreeSupport.clamp(prefs.getInt(key, defaultValue), min, max); input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(4)}); input.setText(String.valueOf(start)); bindXmlStepperButton(prefs, key, input, minusFast, -5, min, max); @@ -786,7 +714,7 @@ abstract class LockedSceneLayoutFragment extends Fragment { } input.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { - int next = clamp(parseInt(input, defaultValue), min, max); + int next = ViewTreeSupport.clamp(ViewTreeSupport.parseInt(input, defaultValue), min, max); input.setText(String.valueOf(next)); persistIntPref(prefs, key, next); } @@ -806,7 +734,7 @@ abstract class LockedSceneLayoutFragment extends Fragment { return; } button.setOnClickListener(v -> { - int next = clamp(parseInt(input, 0) + delta, min, max); + int next = ViewTreeSupport.clamp(ViewTreeSupport.parseInt(input, 0) + delta, min, max); input.setText(String.valueOf(next)); persistIntPref(prefs, key, next); }); @@ -872,22 +800,8 @@ abstract class LockedSceneLayoutFragment extends Fragment { if (view.getVisibility() != View.VISIBLE) { view.setVisibility(View.VISIBLE); } - view.setEnabled(enabled); + ViewTreeSupport.setEnabledRecursive(view, enabled); view.setAlpha(enabled ? 1f : 0.45f); - if (view instanceof ViewGroup group) { - for (int i = 0; i < group.getChildCount(); i++) { - updateMiddleOptionChild(group.getChildAt(i), enabled); - } - } - } - - private void updateMiddleOptionChild(View view, boolean enabled) { - view.setEnabled(enabled); - if (view instanceof ViewGroup group) { - for (int i = 0; i < group.getChildCount(); i++) { - updateMiddleOptionChild(group.getChildAt(i), enabled); - } - } } private View stepper(Context context, SharedPreferences prefs, int label, String key, int def, int min, int max) { @@ -913,23 +827,25 @@ abstract class LockedSceneLayoutFragment extends Fragment { LinearLayout row = new LinearLayout(context); row.setOrientation(LinearLayout.HORIZONTAL); row.setGravity(Gravity.CENTER_VERTICAL); - MaterialButton minus = button(context, "-"); - MaterialButton plus = button(context, "+"); - MaterialButton reset = showDefaultButton ? button(context, "D") : null; - EditText input = numericInput(context, true); - input.setText(String.valueOf(clamp(prefs.getInt(key, def), min, max))); - row.addView(minus, buttonParams()); - row.addView(input, inputParams()); - row.addView(plus, buttonParams()); + MaterialButton minus = ViewTreeSupport.outlinedStepperButton(context, "-"); + MaterialButton plus = ViewTreeSupport.outlinedStepperButton(context, "+"); + MaterialButton reset = showDefaultButton + ? ViewTreeSupport.outlinedStepperButton(context, "D") + : null; + EditText input = ViewTreeSupport.numericInput(context, true, 4); + input.setText(String.valueOf(ViewTreeSupport.clamp(prefs.getInt(key, def), min, max))); + row.addView(minus, ViewTreeSupport.stepperButtonParams(context)); + row.addView(input, ViewTreeSupport.stepperInputParams(context)); + row.addView(plus, ViewTreeSupport.stepperButtonParams(context)); if (reset != null) { - LinearLayout.LayoutParams resetParams = buttonParams(); - resetParams.leftMargin = dp(8); + LinearLayout.LayoutParams resetParams = ViewTreeSupport.stepperButtonParams(context); + resetParams.leftMargin = ViewTreeSupport.dp(context, 8); row.addView(reset, resetParams); } outer.addView(row); View.OnClickListener listener = view -> { int delta = view == minus ? -1 : 1; - int next = clamp(parseInt(input, def) + delta, min, max); + int next = ViewTreeSupport.clamp(ViewTreeSupport.parseInt(input, def) + delta, min, max); input.setText(String.valueOf(next)); persistIntPref(prefs, key, next); }; @@ -937,14 +853,14 @@ abstract class LockedSceneLayoutFragment extends Fragment { plus.setOnClickListener(listener); if (reset != null) { reset.setOnClickListener(v -> { - int next = clamp(def, min, max); + int next = ViewTreeSupport.clamp(def, min, max); input.setText(String.valueOf(next)); persistIntPref(prefs, key, next); }); } input.setOnFocusChangeListener((v, hasFocus) -> { if (!hasFocus) { - int next = clamp(parseInt(input, def), min, max); + int next = ViewTreeSupport.clamp(ViewTreeSupport.parseInt(input, def), min, max); input.setText(String.valueOf(next)); persistIntPref(prefs, key, next); } @@ -952,28 +868,6 @@ abstract class LockedSceneLayoutFragment extends Fragment { return outer; } - private EditText numericInput(Context context, boolean signed) { - EditText input = new EditText(context); - input.setSingleLine(true); - input.setGravity(Gravity.CENTER); - input.setInputType(signed - ? android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED - : android.text.InputType.TYPE_CLASS_NUMBER); - input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(4)}); - return input; - } - - private CheckBox checkBox(Context context, SharedPreferences prefs, int text, String key, boolean def) { - CheckBox checkBox = new CheckBox(context); - checkBox.setText(text); - checkBox.setChecked(prefs.getBoolean(key, def)); - checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> { - prefs.edit().putBoolean(key, isChecked).apply(); - SbtSettings.ensureReadable(context); - }); - return checkBox; - } - private void persistIntPref(SharedPreferences prefs, String key, int value) { prefs.edit().putInt(key, value).apply(); SbtSettings.ensureReadable(requireContext()); @@ -986,7 +880,7 @@ abstract class LockedSceneLayoutFragment extends Fragment { boolean legacyEnabledDefault ) { int fallback = prefs.getBoolean(legacyEnabledKey, legacyEnabledDefault) ? 1 : 0; - return clampIconContainerCount(prefs.getInt(countKey, fallback)); + return SbtSettings.clampIconContainerCount(prefs.getInt(countKey, fallback)); } private void persistIconContainerCount( @@ -995,7 +889,7 @@ abstract class LockedSceneLayoutFragment extends Fragment { String legacyEnabledKey, int count ) { - int clamped = clampIconContainerCount(count); + int clamped = SbtSettings.clampIconContainerCount(count); prefs.edit() .putInt(countKey, clamped) .putBoolean(legacyEnabledKey, clamped > 0) @@ -1003,19 +897,12 @@ abstract class LockedSceneLayoutFragment extends Fragment { SbtSettings.ensureReadable(requireContext()); } - private int clampIconContainerCount(int value) { - return clamp( - value, - SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN, - SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX); - } - private String sceneKey(String suffix) { return (aod ? "layout_aod_" : "layout_lock_") + suffix; } private String indexedSceneKey(String suffix, int index) { - return index <= 0 ? sceneKey(suffix) : sceneKey(suffix) + "_" + (index + 1); + return SbtSettings.indexedKey(sceneKey(suffix), index); } private LayoutSectionCopySupport.Mode copyMode() { @@ -1035,87 +922,23 @@ abstract class LockedSceneLayoutFragment extends Fragment { return content instanceof LinearLayout layout ? layout : null; } - private MaterialButton button(Context context, String text) { - MaterialButton button = new MaterialButton(context, null, com.google.android.material.R.attr.materialButtonOutlinedStyle); - button.setText(text); - button.setMinWidth(dp(40)); - button.setMinHeight(dp(40)); - button.setPadding(0, button.getPaddingTop(), 0, button.getPaddingBottom()); - return button; - } - - private LinearLayout.LayoutParams buttonParams() { - return new LinearLayout.LayoutParams(dp(40), ViewGroup.LayoutParams.WRAP_CONTENT); - } - - private LinearLayout.LayoutParams inputParams() { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - dp(48), - ViewGroup.LayoutParams.WRAP_CONTENT); - params.leftMargin = dp(8); - params.rightMargin = dp(8); - return params; - } - - private MaterialCardView card(Context context, int titleId, View content) { - MaterialCardView card = new MaterialCardView(context); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - params.topMargin = dp(16); - card.setLayoutParams(params); - card.setUseCompatPadding(true); - card.setStrokeColor(context.getColor(R.color.sbt_card_outline)); - card.setStrokeWidth(dp(1)); - LinearLayout inner = vertical(context); - inner.setPadding(dp(16), dp(16), dp(16), dp(16)); - inner.addView(sectionTitle(context, titleId)); - inner.addView(content); - card.addView(inner); - return card; + private View card(Context context, int titleId, View content) { + return ViewTreeSupport.outlinedCard(context, sectionTitle(context, titleId), content); } private LinearLayout vertical(Context context) { - LinearLayout layout = new LinearLayout(context); - layout.setOrientation(LinearLayout.VERTICAL); - return layout; + return ViewTreeSupport.verticalLayout(context); } private TextView title(Context context, int text) { - TextView view = new TextView(context); - view.setText(text); - view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline5); - return view; + return ViewTreeSupport.titleText(context, text); } private TextView sectionTitle(Context context, int text) { - TextView view = new TextView(context); - view.setText(text); - view.setAllCaps(true); - view.setTypeface(view.getTypeface(), android.graphics.Typeface.BOLD); - return view; + return ViewTreeSupport.sectionTitleText(context, text, false); } private TextView body(Context context, int text) { - TextView view = new TextView(context); - view.setText(text); - view.setPadding(0, dp(8), 0, dp(6)); - return view; - } - - private int parseInt(EditText input, int fallback) { - try { - return Integer.parseInt(input.getText().toString().trim()); - } catch (Throwable ignored) { - return fallback; - } - } - - private int clamp(int value, int min, int max) { - return Math.max(min, Math.min(max, value)); - } - - private int dp(int value) { - return Math.round(value * getResources().getDisplayMetrics().density); + return ViewTreeSupport.bodyText(context, text, 6); } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/MiscFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/MiscFragment.java index b34e84f..2491bcf 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/MiscFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/MiscFragment.java @@ -15,8 +15,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import com.google.android.material.switchmaterial.SwitchMaterial; - public class MiscFragment extends Fragment { @Nullable @@ -28,31 +26,21 @@ public class MiscFragment extends Fragment { .getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE); SbtSettings.ensureReadable(requireContext()); - bindSwitch(root, prefs, + ViewTreeSupport.bindBooleanPreference(requireContext(), prefs, root, R.id.misc_aod_keep_visible_during_call_switch, SbtSettings.KEY_AOD_KEEP_VISIBLE_DURING_CALL, - SbtDefaults.AOD_KEEP_VISIBLE_DURING_CALL_DEFAULT); - bindSwitch(root, prefs, + SbtDefaults.AOD_KEEP_VISIBLE_DURING_CALL_DEFAULT, + false); + ViewTreeSupport.bindBooleanPreference(requireContext(), prefs, root, R.id.misc_battery_lockscreen_unplugged_percent_switch, SbtSettings.KEY_BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT, - SbtDefaults.BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT_DEFAULT); - bindSwitch(root, prefs, + SbtDefaults.BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT_DEFAULT, + false); + ViewTreeSupport.bindBooleanPreference(requireContext(), prefs, root, R.id.misc_battery_aod_unplugged_percent_switch, SbtSettings.KEY_BATTERY_AOD_UNPLUGGED_PERCENT, - SbtDefaults.BATTERY_AOD_UNPLUGGED_PERCENT_DEFAULT); + SbtDefaults.BATTERY_AOD_UNPLUGGED_PERCENT_DEFAULT, + false); return root; } - - private void bindSwitch(View root, - SharedPreferences prefs, - int switchId, - String key, - boolean defaultValue) { - SwitchMaterial toggle = root.findViewById(switchId); - toggle.setChecked(prefs.getBoolean(key, defaultValue)); - toggle.setOnCheckedChangeListener((buttonView, isChecked) -> { - prefs.edit().putBoolean(key, isChecked).apply(); - SbtSettings.ensureReadable(requireContext()); - }); - } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/StatusChipsFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/StatusChipsFragment.java index 7d79ee2..bdb16e3 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/StatusChipsFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/StatusChipsFragment.java @@ -14,8 +14,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import com.google.android.material.switchmaterial.SwitchMaterial; - public class StatusChipsFragment extends Fragment { @Nullable @@ -27,27 +25,26 @@ public class StatusChipsFragment extends Fragment { .getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE); SbtSettings.ensureReadable(requireContext()); - bindSwitch(root, prefs, R.id.status_chips_hide_all_switch, - SbtSettings.KEY_STATUS_CHIPS_HIDE_ALL, false); - bindSwitch(root, prefs, R.id.status_chips_hide_media_unlocked_switch, - SbtSettings.KEY_STATUS_CHIPS_HIDE_MEDIA_UNLOCKED, false); - bindSwitch(root, prefs, R.id.status_chips_hide_navigation_unlocked_switch, - SbtSettings.KEY_STATUS_CHIPS_HIDE_NAV_UNLOCKED, false); - bindSwitch(root, prefs, R.id.status_chips_hide_call_unlocked_switch, - SbtSettings.KEY_STATUS_CHIPS_HIDE_CALL, false); + ViewTreeSupport.bindBooleanPreference(requireContext(), prefs, root, + R.id.status_chips_hide_all_switch, + SbtSettings.KEY_STATUS_CHIPS_HIDE_ALL, + false, + true); + ViewTreeSupport.bindBooleanPreference(requireContext(), prefs, root, + R.id.status_chips_hide_media_unlocked_switch, + SbtSettings.KEY_STATUS_CHIPS_HIDE_MEDIA_UNLOCKED, + false, + true); + ViewTreeSupport.bindBooleanPreference(requireContext(), prefs, root, + R.id.status_chips_hide_navigation_unlocked_switch, + SbtSettings.KEY_STATUS_CHIPS_HIDE_NAV_UNLOCKED, + false, + true); + ViewTreeSupport.bindBooleanPreference(requireContext(), prefs, root, + R.id.status_chips_hide_call_unlocked_switch, + SbtSettings.KEY_STATUS_CHIPS_HIDE_CALL, + false, + true); return root; } - - private void bindSwitch(View root, - SharedPreferences prefs, - int switchId, - String key, - boolean defaultValue) { - SwitchMaterial toggle = root.findViewById(switchId); - toggle.setChecked(prefs.getBoolean(key, defaultValue)); - toggle.setOnCheckedChangeListener((buttonView, isChecked) -> { - prefs.edit().putBoolean(key, isChecked).commit(); - SbtSettings.ensureReadable(requireContext()); - }); - } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/SystemIconsFragment.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/SystemIconsFragment.java index 6783e69..11cc229 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/SystemIconsFragment.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/SystemIconsFragment.java @@ -5,6 +5,7 @@ import se.ajpanton.statusbartweak.shell.settings.SbtDefaults; import se.ajpanton.statusbartweak.shell.settings.SbtSettings; import se.ajpanton.statusbartweak.shell.settings.SystemIconRules; +import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.res.ColorStateList; @@ -38,7 +39,6 @@ import java.util.HashMap; public class SystemIconsFragment extends Fragment { private static final String QUICKSTAR_PACKAGE = "com.samsung.android.qstuner"; - private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; private static final int TOGGLE_ICON_DP = 24; private static final List SECTIONS = Arrays.asList( @@ -156,7 +156,7 @@ public class SystemIconsFragment extends Fragment { View content = root.findViewById(R.id.system_icons_content); TextView disabledHint = root.findViewById(R.id.system_icons_disabled_hint); if (content != null) { - setViewTreeEnabled(content, enabled); + ViewTreeSupport.setEnabledRecursive(content, enabled); content.setAlpha(enabled ? 1f : 0.45f); } if (disabledHint != null) { @@ -165,19 +165,6 @@ public class SystemIconsFragment extends Fragment { } } - private void setViewTreeEnabled(View view, boolean enabled) { - if (view == null) { - return; - } - view.setEnabled(enabled); - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - for (int i = 0; i < group.getChildCount(); i++) { - setViewTreeEnabled(group.getChildAt(i), enabled); - } - } - } - private View createRow(ViewGroup parent, SharedPreferences prefs, Map blockedModes, @@ -189,7 +176,7 @@ public class SystemIconsFragment extends Fragment { row.setLayoutParams(new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - row.setPadding(0, dpToPx(context, 6), 0, dpToPx(context, 6)); + row.setPadding(0, ViewTreeSupport.dp(context, 6), 0, ViewTreeSupport.dp(context, 6)); LinearLayout modeStrip = new LinearLayout(context); modeStrip.setOrientation(LinearLayout.HORIZONTAL); @@ -210,7 +197,7 @@ public class SystemIconsFragment extends Fragment { 0, ViewGroup.LayoutParams.WRAP_CONTENT); labelParams.weight = 1f; - labelParams.leftMargin = dpToPx(context, 8); + labelParams.leftMargin = ViewTreeSupport.dp(context, 8); label.setLayoutParams(labelParams); label.setText(context.getString(entry.labelRes)); label.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1); @@ -228,13 +215,13 @@ public class SystemIconsFragment extends Fragment { cell.setOrientation(LinearLayout.HORIZONTAL); cell.setGravity(Gravity.CENTER); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - dpToPx(context, 32), ViewGroup.LayoutParams.WRAP_CONTENT); + ViewTreeSupport.dp(context, 32), ViewGroup.LayoutParams.WRAP_CONTENT); cell.setLayoutParams(params); FrameLayout iconCell = new FrameLayout(context); LinearLayout.LayoutParams iconCellParams = new LinearLayout.LayoutParams( - dpToPx(context, TOGGLE_ICON_DP), - dpToPx(context, TOGGLE_ICON_DP)); + ViewTreeSupport.dp(context, TOGGLE_ICON_DP), + ViewTreeSupport.dp(context, TOGGLE_ICON_DP)); iconCell.setLayoutParams(iconCellParams); ImageView iconView = new ImageView(context); @@ -320,7 +307,7 @@ public class SystemIconsFragment extends Fragment { for (String name : entry.quickStarDrawableNames) { Drawable drawable = loadPackageDrawable(context, QUICKSTAR_PACKAGE, name); if (drawable == null) { - drawable = loadPackageDrawable(context, SYSTEMUI_PACKAGE, name); + drawable = loadPackageDrawable(context, SbtSettings.PACKAGE_SYSTEMUI, name); } if (drawable != null) { return drawable; @@ -334,6 +321,7 @@ public class SystemIconsFragment extends Fragment { return null; } + @SuppressLint("DiscouragedApi") private Drawable loadPackageDrawable(Context context, String packageName, String drawableName) { if (context == null || drawableName == null || drawableName.trim().isEmpty()) { return null; @@ -363,7 +351,7 @@ public class SystemIconsFragment extends Fragment { quickStarContext = pkgContext; } return pkgContext; - } catch (Throwable ignored) { + } catch (Exception ignored) { if (QUICKSTAR_PACKAGE.equals(packageName)) { quickStarContext = null; } @@ -381,7 +369,7 @@ public class SystemIconsFragment extends Fragment { Drawable copy = state.newDrawable(context.getResources(), context.getTheme()); return copy != null ? copy.mutate() : drawable.mutate(); } - } catch (Throwable ignored) { + } catch (Exception ignored) { } return drawable.mutate(); } @@ -402,11 +390,6 @@ public class SystemIconsFragment extends Fragment { return drawable; } - private int dpToPx(Context context, int dp) { - float density = context.getResources().getDisplayMetrics().density; - return Math.round(dp * density); - } - private static final class SystemIconSection { final int labelRes; final List entries; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/ViewTreeSupport.java b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/ViewTreeSupport.java new file mode 100644 index 0000000..b307f0b --- /dev/null +++ b/app/src/main/java/se/ajpanton/statusbartweak/shell/ui/ViewTreeSupport.java @@ -0,0 +1,200 @@ +package se.ajpanton.statusbartweak.shell.ui; + +import android.content.Context; +import android.content.SharedPreferences; +import android.text.InputFilter; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.card.MaterialCardView; + +import se.ajpanton.statusbartweak.R; + +import se.ajpanton.statusbartweak.shell.settings.SbtSettings; + +final class ViewTreeSupport { + private ViewTreeSupport() { + } + + static void setEnabledRecursive(View view, boolean enabled) { + if (view == null) { + return; + } + view.setEnabled(enabled); + if (view instanceof ViewGroup group) { + for (int i = 0; i < group.getChildCount(); i++) { + setEnabledRecursive(group.getChildAt(i), enabled); + } + } + } + + static void hideCheckBoxesExcept(View view, CheckBox kept) { + if (view instanceof CheckBox checkBox && checkBox != kept) { + checkBox.setVisibility(View.GONE); + return; + } + if (view instanceof ViewGroup group) { + for (int i = 0; i < group.getChildCount(); i++) { + hideCheckBoxesExcept(group.getChildAt(i), kept); + } + } + } + + static int dp(Context context, int value) { + return Math.round(value * context.getResources().getDisplayMetrics().density); + } + + static int clamp(int value, int min, int max) { + return Math.max(min, Math.min(max, value)); + } + + static int parseInt(EditText input, int fallback) { + if (input == null || input.getText() == null) { + return fallback; + } + String text = input.getText().toString().trim(); + if (text.isEmpty() || "-".equals(text)) { + return fallback; + } + try { + return Integer.parseInt(text); + } catch (NumberFormatException ignored) { + return fallback; + } + } + + static void bindBooleanPreference(Context context, + SharedPreferences prefs, + View root, + int buttonId, + String key, + boolean defaultValue, + boolean commit) { + CompoundButton button = root != null ? root.findViewById(buttonId) : null; + bindBooleanPreference(context, prefs, button, key, defaultValue, commit); + } + + static void bindBooleanPreference(Context context, + SharedPreferences prefs, + CompoundButton button, + String key, + boolean defaultValue, + boolean commit) { + if (context == null || prefs == null || button == null || key == null) { + return; + } + button.setChecked(prefs.getBoolean(key, defaultValue)); + button.setOnCheckedChangeListener((buttonView, isChecked) -> { + SharedPreferences.Editor editor = prefs.edit().putBoolean(key, isChecked); + if (commit) { + editor.commit(); + } else { + editor.apply(); + } + SbtSettings.ensureReadable(context); + }); + } + + static CheckBox boundCheckBox(Context context, + SharedPreferences prefs, + int text, + String key, + boolean defaultValue, + boolean commit) { + CheckBox checkBox = new CheckBox(context); + checkBox.setText(text); + bindBooleanPreference(context, prefs, checkBox, key, defaultValue, commit); + return checkBox; + } + + static MaterialButton outlinedStepperButton(Context context, String text) { + MaterialButton button = new MaterialButton( + context, + null, + com.google.android.material.R.attr.materialButtonOutlinedStyle); + button.setText(text); + button.setMinWidth(dp(context, 40)); + button.setMinHeight(dp(context, 40)); + button.setPadding(0, button.getPaddingTop(), 0, button.getPaddingBottom()); + return button; + } + + static LinearLayout.LayoutParams stepperButtonParams(Context context) { + return new LinearLayout.LayoutParams(dp(context, 40), ViewGroup.LayoutParams.WRAP_CONTENT); + } + + static LinearLayout.LayoutParams stepperInputParams(Context context) { + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + dp(context, 48), + ViewGroup.LayoutParams.WRAP_CONTENT); + params.leftMargin = dp(context, 8); + params.rightMargin = dp(context, 8); + return params; + } + + static EditText numericInput(Context context, boolean signed, int maxLength) { + EditText input = new EditText(context); + input.setGravity(android.view.Gravity.CENTER); + input.setSingleLine(true); + input.setInputType(signed + ? android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED + : android.text.InputType.TYPE_CLASS_NUMBER); + input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)}); + return input; + } + + static LinearLayout verticalLayout(Context context) { + LinearLayout layout = new LinearLayout(context); + layout.setOrientation(LinearLayout.VERTICAL); + return layout; + } + + static TextView titleText(Context context, int text) { + TextView view = new TextView(context); + view.setText(text); + view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Headline5); + return view; + } + + static TextView sectionTitleText(Context context, int text, boolean applySubtitleAppearance) { + TextView view = new TextView(context); + view.setText(text); + if (applySubtitleAppearance) { + view.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Subtitle1); + } + view.setAllCaps(true); + view.setTypeface(view.getTypeface(), android.graphics.Typeface.BOLD); + return view; + } + + static TextView bodyText(Context context, int text, int bottomPaddingDp) { + TextView view = new TextView(context); + view.setText(text); + view.setPadding(0, dp(context, 8), 0, dp(context, bottomPaddingDp)); + return view; + } + + static MaterialCardView outlinedCard(Context context, View title, View content) { + MaterialCardView card = new MaterialCardView(context); + LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + cardParams.topMargin = dp(context, 16); + card.setLayoutParams(cardParams); + card.setUseCompatPadding(true); + card.setStrokeColor(context.getColor(R.color.sbt_card_outline)); + card.setStrokeWidth(dp(context, 1)); + LinearLayout inner = verticalLayout(context); + inner.setPadding(dp(context, 16), dp(context, 16), dp(context, 16), dp(context, 16)); + inner.addView(title); + inner.addView(content); + card.addView(inner); + return card; + } +} diff --git a/app/src/main/res/layout/fragment_layout.xml b/app/src/main/res/layout/fragment_layout.xml index 2de1489..3eb607f 100644 --- a/app/src/main/res/layout/fragment_layout.xml +++ b/app/src/main/res/layout/fragment_layout.xml @@ -16,39 +16,6 @@ android:text="@string/layout_title" android:textAppearance="?attr/textAppearanceHeadline5" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml deleted file mode 100644 index 6f3b755..0000000 --- a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62b611d..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b9a695..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9287f50..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9126ae3..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c5f42e0..98d68c9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,7 +100,10 @@ No apps yet. Generate a notification first. Close Regex - %1$s \u2022 %2$d exceptions + + %1$s \u2022 %2$d exception + %1$s \u2022 %2$d exceptions + Last seen: %1$s Default settings Add exception @@ -160,7 +163,6 @@ Clock Layout Unlocked - Master toggles Master toggle Use custom clock Use custom layout @@ -211,10 +213,8 @@ Else, or if the overlap is centred, move clock to: Left side Right side - Gap from cutout (px) Custom format Use custom date/time pattern - Ignore under-display cameras Examples: HH:mm, EEE d MMM HH:mm:ss, {#batterybar big @sans-serif-condensed}HH:mm Date/time patterns:\n\u2022 Uses Java SimpleDateFormat-style pattern letters\n\u2022 Common symbols: yyyy yy MMMM MMM MM EEEE EEE dd d HH hh mm ss a\n\nTags:\n\u2022 Supported tags: <i>, <u>, <small>, <big>, <font color=\'...\'>, <font face=\'...\'>\n\u2022 <b> is not supported\n\u2022 Custom font tags: <sans>, <serif>, <mono>, <condensed>\n\u2022 {/font} closes any open <font ...> tag\n\u2022 Opening a new colour or font face automatically closes the previous one Shorthand blocks:\n\u2022 {#FA0 big @sans-serif-condensed} is shorthand for opening multiple tags in order\n\u2022 In shorthand: / closes a tag, # sets font colour, @ sets font face\n\u2022 Each whitespace-separated token inside {} becomes its own tag\n\nLine breaks:\n\u2022 Use \\n, {\\n} or {\\n15}; the number is the line-height delta in px\n\u2022 Omit the number to use the default row spacing\n\u2022 { /\\n } style line breaks are written without spaces: {/\\n} or {/\\n15}\n\u2022 Normal line breaks propagate open tags onto the next row\n\u2022 A / line break ends all currently open tags before the next row starts\n\nPipes and grouping:\n\u2022 Maximum two pipes per row; more than two is a syntax error for that row only\n\u2022 Pipes inside single quotes or {} do not split the row\n\u2022 One pipe splits into left and right segments\n\u2022 Two pipes remove the middle section, then split using the outer pipes\n\u2022 Tags are logically continued across pipe splits, even if the middle section is ignored\n\u2022 Rows without pipes are independent; rows with pipes share one common pipe group\n\u2022 A syntax-error row becomes the text Syntax error, with no whole-clock fallback\n\nColours:\n\u2022 Supported colour literals: #RGB, #ARGB, #RRGGBB, #AARRGGBB\n\u2022 {#bright|#dark} means bright text on dark backgrounds, dark text on bright backgrounds\n\u2022 {#batterybar} copies the current battery bar colour\n\u2022 After |, if the next token is not #, the dark variant is built with math\n\u2022 Dark-variant math can start with inv or an operator (+ - * /)\n\u2022 Math runs strictly left to right; there is no operator precedence\n\u2022 Decimal numbers affect R, G and B only; alpha stays unchanged\n\u2022 Hex operands use 00..FF channel values for + and -, and channel/255 scaling for * and /\n\u2022 8-digit hex operands can also affect alpha diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml deleted file mode 100644 index 4df9255..0000000 --- a/app/src/main/res/xml/backup_rules.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml deleted file mode 100644 index 9ee9997..0000000 --- a/app/src/main/res/xml/data_extraction_rules.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file