Post-AOD cleanup checkpoint

This commit is contained in:
ajp_anton
2026-05-30 17:51:11 +00:00
parent 78b7d30838
commit 22dfdf2cb0
87 changed files with 2328 additions and 3540 deletions
+7 -3
View File
@@ -1,9 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
@@ -34,7 +37,8 @@
<provider
android:name=".shell.settings.SbtSettingsProvider"
android:authorities="se.ajpanton.statusbartweak.settings"
android:exported="true" />
android:exported="true"
tools:ignore="ExportedContentProvider" />
</application>
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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));
}
}
@@ -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 : "";
@@ -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
@@ -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;
}
}
}
@@ -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) {
@@ -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());
@@ -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<View> 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;
}
@@ -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());
@@ -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());
@@ -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;
@@ -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<Integer> pipePositions = ClockMarkupSupport.findPipePositions(markup);
List<Integer> 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),
"</" + name + ">",
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));
}
@@ -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<Object> args = chain.getArgs();
List<Object> 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);
@@ -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);
}
@@ -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;
@@ -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;
}
}
}
@@ -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;
@@ -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());
@@ -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 "";
}
}
}
@@ -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());
@@ -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");
@@ -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<Object> dispatchers =
Collections.newSetFromMap(new WeakHashMap<>());
private final Map<Object, State> 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<Object, State> 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
) {
}
}
@@ -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<View, HiddenRowState> 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;
@@ -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;
}
}
@@ -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<Object> 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<String> mergedSlots = new LinkedHashSet<>();
for (Object object : new ArrayList<>(knownObjects)) {
rememberObject(object);
Object iconList = ReflectionSupport.getFieldValue(object, "mStatusBarIconList");
if (iconList == null) {
continue;
}
Set<String> 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<String> activeSlots(Object iconList, Set<String> hiddenSlots) {
LinkedHashSet<String> result = new LinkedHashSet<>();
addActiveSlots(
result,
ReflectionSupport.getFieldValue(iconList, "mSlots"),
hiddenSlots);
addActiveSlots(
result,
ReflectionSupport.getFieldValue(iconList, "mViewOnlySlots"),
hiddenSlots);
return result;
}
private void addActiveSlots(
LinkedHashSet<String> out,
Object slotsObj,
Set<String> 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<String> copyStringSet(Object value) {
LinkedHashSet<String> 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 "";
}
}
@@ -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());
}
}
@@ -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<Box, Double> entry : relaxedWallByEndBox.entrySet()) {
for (Map.Entry<Box, Double> entry : relaxedWallByEndBox.entrySet()) {
ArrayList<FinalConstraint> constraints = constraintsByEndBox.get(entry.getKey());
if (constraints == null) {
continue;
@@ -241,7 +242,7 @@ public final class PackedStatusBarLayoutSolver {
LinkedHashMap<String, ArrayList<LayoutItem>> iconGroups = groupedIconItems(allItems);
HashMap<String, ArrayList<Integer>> remainingIconWidths = new HashMap<>();
HashMap<String, Integer> remainingIconStarts = new HashMap<>();
for (java.util.Map.Entry<String, ArrayList<LayoutItem>> entry : iconGroups.entrySet()) {
for (Map.Entry<String, ArrayList<LayoutItem>> entry : iconGroups.entrySet()) {
remainingIconWidths.put(entry.getKey(), new ArrayList<>(entry.getValue().get(0).iconWidths()));
remainingIconStarts.put(entry.getKey(), 0);
}
@@ -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(
@@ -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();
}
}
@@ -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 "";
}
}
}
@@ -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<Bounds> statusBoundsListForRoot(View root, View excludedContainer) {
ArrayList<Bounds> 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<View> statusSourcesForRoot(View root, View excludedContainer) {
ArrayList<View> 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;
}
}
@@ -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;
}
@@ -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 {
@@ -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<View> 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;
@@ -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<ViewGroup> 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<SnapshotSource> collectSources(ViewGroup sourceRoot) {
ArrayList<SnapshotSource> result = new ArrayList<>();
ArrayDeque<View> 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);
@@ -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) {
@@ -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;
@@ -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 "";
}
}
}
@@ -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,
@@ -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<String> 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<Map.Entry<String, HeldSignal>> iterator = heldSignals.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, HeldSignal> 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<String, ImageView> proxies,
HashSet<String> desiredKeys
) {
ArrayList<String> staleKeys = new ArrayList<>();
for (Map.Entry<String, ImageView> entry : proxies.entrySet()) {
int removed = 0;
Iterator<Map.Entry<String, ImageView>> iterator = proxies.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, ImageView> 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;
}
@@ -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<String> slots) {
public static boolean setActiveSlots(List<String> slots) {
ArrayList<String> normalized = normalize(slots);
synchronized (LOCK) {
if (activeSlots.equals(normalized)) {
@@ -26,17 +28,17 @@ public final class StatusIconSlotVisibilityStore {
}
}
private static ArrayList<String> normalize(ArrayList<String> slots) {
ArrayList<String> normalized = new ArrayList<>();
private static ArrayList<String> normalize(List<String> slots) {
if (slots == null) {
return normalized;
return new ArrayList<>();
}
LinkedHashSet<String> 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);
}
}
@@ -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<View> 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<View> 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 "";
}
}
}
@@ -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);
}
}
@@ -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<View, Integer> clockBaselineDeltaByRoot = new WeakHashMap<>();
@@ -45,12 +44,7 @@ public final class UnlockedIconSnapshotRenderController {
private final WeakHashMap<View, Boolean> 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) {
@@ -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<SnapshotPlacement> icons) {
int count = 0;
if (icons == null) {
return 0;
}
for (SnapshotPlacement icon : icons) {
if (isShrinkableIcon(icon)) {
count++;
}
}
return count;
}
private boolean hasOverflowDot(ArrayList<SnapshotPlacement> 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,
@@ -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<View, Integer> 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<UnlockedLayoutBand> originalOrder = new ArrayList<>(bands);
IdentityHashMap<UnlockedLayoutBand, Integer> 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<PackedStatusBarLayoutSolver.LayoutItem> items) {
java.util.LinkedHashMap<String, ArrayList<PackedStatusBarLayoutSolver.LayoutItem>> groups =
new java.util.LinkedHashMap<>();
LinkedHashMap<String, ArrayList<PackedStatusBarLayoutSolver.LayoutItem>> 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<SnapshotPlacement> placements) {
if (placements == null || placements.isEmpty()) {
return 0;
}
return Math.max(0, placements.get(0).bounds.height);
}
private void splitClockIfNeeded(
View root,
ArrayList<UnlockedLayoutBand> bands,
@@ -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<View, RootAnchors> 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<SnapshotSource> 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<String, SnapshotSource> 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<SnapshotSource> 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,
@@ -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);
}
}
@@ -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;
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
@@ -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;
}
}
@@ -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<String, Integer> loadBlockedModes(SharedPreferences prefs) {
if (prefs == null) {
return new HashMap<>();
}
Set<String> 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<String, Integer> map) {
if (prefs == null) {
return;
}
Set<String> encoded = encodeBlockedModes(map);
prefs.edit().putStringSet(PREF_KEY_BLOCKED_MODES, encoded).apply();
writeStringSet(prefs, PREF_KEY_BLOCKED_MODES, encodeBlockedModes(map));
}
public static Map<String, SeenApp> loadSeenSession(SharedPreferences prefs) {
if (prefs == null) {
return new HashMap<>();
}
Set<String> raw = prefs.getStringSet(PREF_KEY_SEEN_SESSION, Collections.emptySet());
return parseSeenSession(raw);
return parseSeenSession(readStringSet(prefs, PREF_KEY_SEEN_SESSION));
}
public static Map<String, ArrayList<ExceptionRule>> loadExceptionRules(SharedPreferences prefs) {
if (prefs == null) {
return new HashMap<>();
}
Set<String> 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<String, ArrayList<ExceptionRule>> map) {
if (prefs == null) {
return;
}
Set<String> 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<String, SeenApp> map) {
if (prefs == null) {
return;
}
Set<String> 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<String> 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<String> value) {
if (prefs == null) {
return;
}
prefs.edit().putStringSet(key, value).apply();
}
public static boolean noteSeen(Map<String, SeenApp> 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<String> snapshot(Set<String> 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;
}
@@ -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<Threshold> 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<String> encodeThresholds(List<Threshold> thresholds) {
ArrayList<String> 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) {
@@ -147,6 +147,13 @@ public final class ClockCameraTypeSupport {
return out.isEmpty() ? Collections.emptyMap() : out;
}
public static Map<String, String> loadOverrides(SharedPreferences prefs, String key) {
if (prefs == null || TextUtils.isEmpty(key)) {
return Collections.emptyMap();
}
return parseOverrides(prefs.getStringSet(key, Collections.emptySet()));
}
public static Set<String> encodeOverrides(Map<String, String> overrides) {
if (overrides == null || overrides.isEmpty()) {
return Collections.emptySet();
@@ -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<String> copyStringSet(Set<String> source) {
private static Set<String> readStringSetFromPrefs(SharedPreferences prefs, String key) {
if (prefs == null) {
return Collections.emptySet();
}
Set<String> source = prefs.getStringSet(key, Collections.emptySet());
if (source == null || source.isEmpty()) {
return Collections.emptySet();
}
return new HashSet<>(source);
}
private static Set<String> readBlockedSetFromBundle(Bundle bundle) {
if (bundle == null) {
return Collections.emptySet();
}
ArrayList<String> list = bundle.getStringArrayList(KEY_APP_ICON_BLOCKED_MODES);
if (list == null || list.isEmpty()) {
return Collections.emptySet();
}
return new HashSet<>(list);
}
private static Set<String> 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<String> readRuleSetFromBundle(Bundle bundle) {
if (bundle == null) {
return Collections.emptySet();
}
ArrayList<String> list = bundle.getStringArrayList(KEY_APP_ICON_EXCEPTION_RULES);
if (list == null || list.isEmpty()) {
return Collections.emptySet();
}
return new HashSet<>(list);
}
private static Map<String, Integer> readSystemIconBlockedModesFromPrefs(SharedPreferences prefs) {
if (prefs == null) {
return Collections.emptyMap();
}
Map<String, Integer> parsed = SystemIconRules.parseBlockedModes(copyStringSet(
prefs.getStringSet(KEY_SYSTEM_ICON_BLOCKED_MODES, Collections.emptySet())));
Map<String, Integer> parsed = SystemIconRules.parseBlockedModes(
readStringSetFromPrefs(prefs, KEY_SYSTEM_ICON_BLOCKED_MODES));
HashMap<String, Integer> merged = new HashMap<>(parsed);
Set<String> 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<String, Integer> parsed = SystemIconRules.parseBlockedModes(
readStringSetFromBundle(bundle, KEY_SYSTEM_ICON_BLOCKED_MODES));
HashMap<String, Integer> merged = new HashMap<>(parsed);
Set<String> 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<String, Integer> merged, Set<String> 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");
}
}
@@ -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<String> values
) {
out.putStringArrayList(key, new ArrayList<>(
values != null ? values : Collections.<String>emptySet()));
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
@@ -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;
}
@@ -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<BatteryBarStyle.Threshold> 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<BatteryBarStyle.Threshold> 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) {
@@ -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);
}
}
}
}
@@ -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";
@@ -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);
}
@@ -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<String, String> overrides = ClockCameraTypeSupport.parseOverrides(
prefs != null
? prefs.getStringSet(SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES, java.util.Collections.emptySet())
: java.util.Collections.emptySet());
Map<String, String> 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<String> 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<String, String> overrides = new java.util.HashMap<>(
ClockCameraTypeSupport.parseOverrides(
prefs.getStringSet(SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES, java.util.Collections.emptySet())));
Map<String, String> 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<String, String> overrides = new java.util.HashMap<>(
ClockCameraTypeSupport.parseOverrides(
prefs.getStringSet(SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES, java.util.Collections.emptySet())));
Map<String, String> 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);
}
}
@@ -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<OrderingItem> 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<OrderingItem> parseOrderingItems(@Nullable String encoded) {
if (encoded == null || encoded.isEmpty() || "clock,notifications,status".equals(encoded)) {
encoded = SbtDefaults.LAYOUT_ITEM_ORDER_DEFAULT;
}
ArrayList<OrderingItem> 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<OrderingItem> 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<OrderingItem> 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<OrderingViewHolder> {
interface DragStarter {
void startDrag(OrderingViewHolder holder);
}
interface OrderChangedListener {
void onOrderChanged(String encodedOrder);
}
private final List<OrderingItem> items;
private final OrderChangedListener orderChangedListener;
private DragStarter dragStarter;
OrderingAdapter(List<OrderingItem> 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);
}
}
}
@@ -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);
}
}
@@ -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) {
}
@@ -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);
}
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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());
});
}
}
@@ -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());
});
}
}
@@ -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<SystemIconSection> 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<String, Integer> 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<SystemIconEntry> entries;
@@ -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;
}
}
-335
View File
@@ -16,39 +16,6 @@
android:text="@string/layout_title"
android:textAppearance="?attr/textAppearanceHeadline5" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/layout_master_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:cardUseCompatPadding="true"
app:strokeColor="@color/sbt_card_outline"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/layout_master_toggle_title"
android:textAppearance="?attr/textAppearanceSubtitle1"
android:textAllCaps="true"
android:letterSpacing="0.03"
android:textStyle="bold" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/clock_enabled_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/layout_enabled" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/layout_clock_card"
android:layout_width="match_parent"
@@ -934,307 +901,5 @@
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/layout_ordering_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="20dp"
app:cardUseCompatPadding="true"
app:strokeColor="@color/sbt_card_outline"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/layout_ordering_title"
android:textAppearance="?attr/textAppearanceSubtitle1"
android:textAllCaps="true"
android:letterSpacing="0.03"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/layout_ordering_hint"
android:textAppearance="?attr/textAppearanceBody2" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/layout_ordering_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:nestedScrollingEnabled="false" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/layout_sort_icons_title"
android:textAppearance="?attr/textAppearanceBody2"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/layout_sort_icons_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/layout_sort_icons_stock"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="1"
android:singleLine="true"
android:text="@string/layout_sort_icons_stock" />
<RadioButton
android:id="@+id/layout_sort_icons_reversed"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="1"
android:singleLine="true"
android:text="@string/layout_sort_icons_reversed" />
<RadioButton
android:id="@+id/layout_sort_icons_automatic"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="true"
android:maxLines="1"
android:singleLine="true"
android:text="@string/layout_sort_icons_automatic" />
</RadioGroup>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/layout_padding_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="20dp"
app:cardUseCompatPadding="true"
app:strokeColor="@color/sbt_card_outline"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/layout_padding_title"
android:textAppearance="?attr/textAppearanceSubtitle1"
android:textAllCaps="true"
android:letterSpacing="0.03"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/layout_cutout_gap_label"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/layout_cutout_gap_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/layout_cutout_gap_input"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="4"
android:gravity="center"
android:importantForAutofill="no"
android:inputType="numberSigned"
android:maxLines="1"
android:singleLine="true" />
<com.google.android.material.button.MaterialButton
android:id="@+id/layout_cutout_gap_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/layout_left_padding_label"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/layout_left_padding_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/layout_left_padding_input"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="4"
android:gravity="center"
android:importantForAutofill="no"
android:inputType="numberSigned"
android:maxLines="1"
android:singleLine="true" />
<com.google.android.material.button.MaterialButton
android:id="@+id/layout_left_padding_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="+" />
<CheckBox
android:id="@+id/layout_left_padding_add_stock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/layout_padding_add_stock" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/layout_right_padding_label"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/layout_right_padding_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/layout_right_padding_input"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="4"
android:gravity="center"
android:importantForAutofill="no"
android:inputType="numberSigned"
android:maxLines="1"
android:singleLine="true" />
<com.google.android.material.button.MaterialButton
android:id="@+id/layout_right_padding_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="+" />
<CheckBox
android:id="@+id/layout_right_padding_add_stock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/layout_padding_add_stock" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/layout_item_padding_label"
android:textAppearance="?attr/textAppearanceBody2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/layout_item_padding_minus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="-" />
<EditText
android:id="@+id/layout_item_padding_input"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:ems="4"
android:gravity="center"
android:importantForAutofill="no"
android:inputType="number"
android:maxLines="1"
android:singleLine="true" />
<com.google.android.material.button.MaterialButton
android:id="@+id/layout_item_padding_plus"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<CheckBox
android:id="@+id/layout_ignore_under_display_cameras"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:checked="true"
android:text="@string/layout_ignore_under_display_cameras" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

+4 -4
View File
@@ -100,7 +100,10 @@
<string name="hidden_apps_empty">No apps yet. Generate a notification first.</string>
<string name="hidden_apps_close">Close</string>
<string name="hidden_apps_exception_regex_input_hint">Regex</string>
<string name="hidden_apps_package_with_exceptions">%1$s \u2022 %2$d exceptions</string>
<plurals name="hidden_apps_package_with_exceptions">
<item quantity="one">%1$s \u2022 %2$d exception</item>
<item quantity="other">%1$s \u2022 %2$d exceptions</item>
</plurals>
<string name="hidden_apps_last_seen">Last seen: %1$s</string>
<string name="hidden_apps_default_modes_title">Default settings</string>
<string name="hidden_apps_add_exception">Add exception</string>
@@ -160,7 +163,6 @@
<string name="clock_title">Clock</string>
<string name="layout_home_title">Layout</string>
<string name="layout_title">Unlocked</string>
<string name="clock_master_toggles_title">Master toggles</string>
<string name="layout_master_toggle_title">Master toggle</string>
<string name="clock_enabled">Use custom clock</string>
<string name="layout_enabled">Use custom layout</string>
@@ -211,10 +213,8 @@
<string name="clock_middle_cutout_prompt">Else, or if the overlap is centred, move clock to:</string>
<string name="clock_middle_side_left">Left side</string>
<string name="clock_middle_side_right">Right side</string>
<string name="clock_middle_cutout_gap_label">Gap from cutout (px)</string>
<string name="clock_custom_format_title">Custom format</string>
<string name="clock_custom_format_enabled">Use custom date/time pattern</string>
<string name="clock_ignore_under_display_cameras">Ignore under-display cameras</string>
<string name="clock_custom_format_hint">Examples: HH:mm, EEE d MMM HH:mm:ss, {#batterybar big @sans-serif-condensed}HH:mm</string>
<string name="clock_custom_format_help_common">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: &lt;i&gt;, &lt;u&gt;, &lt;small&gt;, &lt;big&gt;, &lt;font color=\'...\'&gt;, &lt;font face=\'...\'&gt;\n\u2022 &lt;b&gt; is not supported\n\u2022 Custom font tags: &lt;sans&gt;, &lt;serif&gt;, &lt;mono&gt;, &lt;condensed&gt;\n\u2022 {/font} closes any open &lt;font ...&gt; tag\n\u2022 Opening a new colour or font face automatically closes the previous one</string>
<string name="clock_custom_format_help_styling">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</string>
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older than API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>