Add fullscreen battery bar scenarios and dual SIM signal handling
This commit is contained in:
+557
-10
@@ -3,14 +3,20 @@ package se.ajpanton.statusbartweak.runtime.features.battery;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.os.BatteryManager;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowMetrics;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -25,13 +31,17 @@ 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.BatteryBarGeometry;
|
||||
import se.ajpanton.statusbartweak.shell.settings.BatteryBarStyle;
|
||||
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
|
||||
|
||||
final class BatteryBarController {
|
||||
|
||||
private static final int APPEARANCE_LIGHT_STATUS_BARS = 8;
|
||||
|
||||
private static final String[] ROOT_CLASSES = new String[] {
|
||||
"com.android.systemui.statusbar.phone.PhoneStatusBarView",
|
||||
"com.android.systemui.statusbar.phone.KeyguardStatusBarView"
|
||||
@@ -40,8 +50,16 @@ final class BatteryBarController {
|
||||
private final RuntimeContext runtimeContext;
|
||||
private final Set<View> roots = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
private final Map<View, BatteryBarView> batteryBarViews = new WeakHashMap<>();
|
||||
private final Map<View, OverlayBatteryBar> overlayBatteryBars = new WeakHashMap<>();
|
||||
private final Map<View, View.OnLayoutChangeListener> rootLayoutListeners = new WeakHashMap<>();
|
||||
private final Set<View> pendingOverlayRetries = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
private final Map<View, String> lastSignatures = new WeakHashMap<>();
|
||||
private final Map<View, String> lastOverlaySignatures = new WeakHashMap<>();
|
||||
private int systemBarAppearance;
|
||||
private int systemBarBehavior;
|
||||
private int systemBarRequestedVisibleTypes = WindowInsets.Type.statusBars();
|
||||
private boolean statusBarTransientShown;
|
||||
private String systemBarPackageName = "";
|
||||
|
||||
private boolean hooksInstalled;
|
||||
private boolean receiverRegistered;
|
||||
@@ -59,6 +77,10 @@ final class BatteryBarController {
|
||||
for (String className : ROOT_CLASSES) {
|
||||
installHooksForClass(classLoader, className);
|
||||
}
|
||||
installRootTransformHooks();
|
||||
installSystemBarAttributeListener(classLoader);
|
||||
installTransientBarListener(classLoader);
|
||||
runtimeContext.getModeStateRepository().addSceneListener((previous, current) -> refreshAllRoots());
|
||||
hooksInstalled = true;
|
||||
}
|
||||
|
||||
@@ -100,6 +122,113 @@ final class BatteryBarController {
|
||||
}
|
||||
return result;
|
||||
});
|
||||
XposedHookSupport.hookAllMethodsIfExists(framework, rootClass, "onApplyWindowInsets", chain -> {
|
||||
Object result = chain.proceed();
|
||||
Object target = chain.getThisObject();
|
||||
if (target instanceof View root) {
|
||||
applyBatteryBar(root);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private void installRootTransformHooks() {
|
||||
XposedInterface framework = runtimeContext.getFramework();
|
||||
XposedHookSupport.hookAllMethodsIfExists(framework, View.class, "setTranslationY", chain -> {
|
||||
Object result = chain.proceed();
|
||||
Object target = chain.getThisObject();
|
||||
if (target instanceof View root && roots.contains(root)) {
|
||||
applyBatteryBar(root);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
XposedHookSupport.hookAllMethodsIfExists(framework, View.class, "setY", chain -> {
|
||||
Object result = chain.proceed();
|
||||
Object target = chain.getThisObject();
|
||||
if (target instanceof View root && roots.contains(root)) {
|
||||
applyBatteryBar(root);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
XposedHookSupport.hookAllMethodsIfExists(framework, View.class, "setAlpha", chain -> {
|
||||
Object result = chain.proceed();
|
||||
Object target = chain.getThisObject();
|
||||
if (target instanceof View root && roots.contains(root)) {
|
||||
applyBatteryBar(root);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
XposedHookSupport.hookAllMethodsIfExists(framework, View.class, "setVisibility", chain -> {
|
||||
Object result = chain.proceed();
|
||||
Object target = chain.getThisObject();
|
||||
if (target instanceof View root && roots.contains(root)) {
|
||||
applyBatteryBar(root);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private void installSystemBarAttributeListener(ClassLoader classLoader) {
|
||||
Class<?> commandQueueClass = ReflectionSupport.findClassIfExists(
|
||||
"com.android.systemui.statusbar.CommandQueue",
|
||||
classLoader);
|
||||
if (commandQueueClass == null) {
|
||||
return;
|
||||
}
|
||||
XposedHookSupport.hookAllMethodsIfExists(
|
||||
runtimeContext.getFramework(),
|
||||
commandQueueClass,
|
||||
"onSystemBarAttributesChanged",
|
||||
chain -> {
|
||||
Object result = chain.proceed();
|
||||
updateSystemBarState(chain.getArgs());
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private void installTransientBarListener(ClassLoader classLoader) {
|
||||
Class<?> commandQueueClass = ReflectionSupport.findClassIfExists(
|
||||
"com.android.systemui.statusbar.CommandQueue",
|
||||
classLoader);
|
||||
if (commandQueueClass != null) {
|
||||
XposedHookSupport.hookAllMethodsIfExists(
|
||||
runtimeContext.getFramework(),
|
||||
commandQueueClass,
|
||||
"showTransient",
|
||||
chain -> {
|
||||
Object result = chain.proceed();
|
||||
updateTransientBarState(chain.getArgs(), true);
|
||||
return result;
|
||||
});
|
||||
XposedHookSupport.hookAllMethodsIfExists(
|
||||
runtimeContext.getFramework(),
|
||||
commandQueueClass,
|
||||
"abortTransient",
|
||||
chain -> {
|
||||
Object result = chain.proceed();
|
||||
updateTransientBarState(chain.getArgs(), false);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
installStatusBarAutoHideElementListener(classLoader);
|
||||
}
|
||||
|
||||
private void installStatusBarAutoHideElementListener(ClassLoader classLoader) {
|
||||
Class<?> statusBarAutoHideElementClass = ReflectionSupport.findClassIfExists(
|
||||
"com.android.systemui.statusbar.phone.CentralSurfacesImpl$5",
|
||||
classLoader);
|
||||
if (statusBarAutoHideElementClass == null) {
|
||||
return;
|
||||
}
|
||||
XposedHookSupport.hookAllMethodsIfExists(
|
||||
runtimeContext.getFramework(),
|
||||
statusBarAutoHideElementClass,
|
||||
"hide",
|
||||
chain -> {
|
||||
Object result = chain.proceed();
|
||||
updateStatusBarTransientShown(false);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private void trackAndApplyRoot(View root) {
|
||||
@@ -115,6 +244,7 @@ final class BatteryBarController {
|
||||
root.removeOnLayoutChangeListener(listener);
|
||||
}
|
||||
roots.remove(root);
|
||||
pendingOverlayRetries.remove(root);
|
||||
removeBatteryBarView(root);
|
||||
}
|
||||
|
||||
@@ -210,19 +340,88 @@ final class BatteryBarController {
|
||||
return;
|
||||
}
|
||||
int color = resolveBarColor(root, settings);
|
||||
String signature = buildSignature(root, settings, color);
|
||||
BatteryBarGeometry.Scenario scenario = scenarioForRoot(root);
|
||||
if (!shouldRenderOnRoot(root)) {
|
||||
removeBatteryBarView(root);
|
||||
return;
|
||||
}
|
||||
boolean transientStatusBarReveal = isFullscreenScenario(scenario)
|
||||
&& shouldRenderVisibleFullscreenBar(root);
|
||||
if (transientStatusBarReveal) {
|
||||
scenario = visibleStatusBarScenarioForRoot(root);
|
||||
}
|
||||
BatteryBarGeometry geometry = BatteryBarGeometry.resolve(settings, scenario);
|
||||
if (isFullscreenScenario(scenario)) {
|
||||
removeEmbeddedBatteryBarView(root);
|
||||
applyOverlayBatteryBar(root, settings, geometry, scenario, color);
|
||||
return;
|
||||
}
|
||||
removeOverlayBatteryBar(root);
|
||||
String signature = buildSignature(root, settings, color, geometry, scenario);
|
||||
BatteryBarView existingView = batteryBarViews.get(root);
|
||||
String lastSignature = lastSignatures.get(root);
|
||||
if (signature.equals(lastSignature) && existingView != null && existingView.getParent() == rootGroup) {
|
||||
existingView.bringToFront();
|
||||
return;
|
||||
}
|
||||
applyEmbeddedBatteryBar(rootGroup, settings, geometry, scenario, color);
|
||||
lastSignatures.put(root, signature);
|
||||
}
|
||||
|
||||
private void applyEmbeddedBatteryBar(
|
||||
ViewGroup rootGroup,
|
||||
SbtSettings settings,
|
||||
BatteryBarGeometry geometry,
|
||||
BatteryBarGeometry.Scenario scenario,
|
||||
int color
|
||||
) {
|
||||
BatteryBarView barView = ensureBatteryBarView(rootGroup);
|
||||
layoutBatteryBarView(rootGroup, barView);
|
||||
barView.update(rootGroup, settings, batteryLevel, plugged, color);
|
||||
barView.update(rootGroup, settings, geometry, batteryLevel, plugged, color);
|
||||
barView.bringToFront();
|
||||
barView.invalidate();
|
||||
lastSignatures.put(root, signature);
|
||||
lastSignatures.put(rootGroup, buildSignature(rootGroup, settings, color, geometry, scenario));
|
||||
}
|
||||
|
||||
private void applyOverlayBatteryBar(
|
||||
View root,
|
||||
SbtSettings settings,
|
||||
BatteryBarGeometry geometry,
|
||||
BatteryBarGeometry.Scenario scenario,
|
||||
int color
|
||||
) {
|
||||
String signature = buildSignature(root, settings, color, geometry, scenario);
|
||||
OverlayBatteryBar overlay = overlayBatteryBars.get(root);
|
||||
if (overlay == null) {
|
||||
overlay = new OverlayBatteryBar(root.getContext());
|
||||
overlayBatteryBars.put(root, overlay);
|
||||
}
|
||||
OverlayBounds bounds = fullscreenOverlayBounds(root);
|
||||
boolean attached = overlay.ensureAttached(bounds.width, bounds.height);
|
||||
if (!attached) {
|
||||
scheduleOverlayRetry(root);
|
||||
removeOverlayBatteryBar(root);
|
||||
return;
|
||||
}
|
||||
pendingOverlayRetries.remove(root);
|
||||
overlay.view.update(overlay.host, settings, geometry, batteryLevel, plugged, color);
|
||||
if (!signature.equals(lastOverlaySignatures.get(root))) {
|
||||
overlay.updateLayout(bounds.width, bounds.height);
|
||||
lastOverlaySignatures.put(root, signature);
|
||||
}
|
||||
overlay.view.invalidate();
|
||||
}
|
||||
|
||||
private void scheduleOverlayRetry(View root) {
|
||||
if (root == null || !root.isAttachedToWindow() || !pendingOverlayRetries.add(root)) {
|
||||
return;
|
||||
}
|
||||
root.postDelayed(() -> {
|
||||
pendingOverlayRetries.remove(root);
|
||||
if (roots.contains(root) && root.isAttachedToWindow()) {
|
||||
applyBatteryBar(root);
|
||||
}
|
||||
}, 250L);
|
||||
}
|
||||
|
||||
private BatteryBarView ensureBatteryBarView(ViewGroup root) {
|
||||
@@ -278,9 +477,16 @@ final class BatteryBarController {
|
||||
);
|
||||
}
|
||||
|
||||
private String buildSignature(View root, SbtSettings settings, int color) {
|
||||
private String buildSignature(
|
||||
View root,
|
||||
SbtSettings settings,
|
||||
int color,
|
||||
BatteryBarGeometry geometry,
|
||||
BatteryBarGeometry.Scenario scenario
|
||||
) {
|
||||
return root.getWidth() + "|"
|
||||
+ root.getHeight() + "|"
|
||||
+ scenario + "|"
|
||||
+ batteryLevel + "|"
|
||||
+ plugged + "|"
|
||||
+ color + "|"
|
||||
@@ -289,6 +495,7 @@ final class BatteryBarController {
|
||||
+ settings.batteryBarAlignment + "|"
|
||||
+ settings.batteryBarThicknessDp + "|"
|
||||
+ settings.batteryBarEdgeOffsetDp + "|"
|
||||
+ (geometry != null ? geometry.signature() : "") + "|"
|
||||
+ settings.batteryBarMinLevel + "|"
|
||||
+ settings.batteryBarMaxLevel + "|"
|
||||
+ settings.batteryBarDefaultDischargeColor + "|"
|
||||
@@ -296,7 +503,228 @@ final class BatteryBarController {
|
||||
+ BatteryBarStyle.encodeThresholds(settings.batteryBarThresholds);
|
||||
}
|
||||
|
||||
private BatteryBarGeometry.Scenario scenarioForRoot(View root) {
|
||||
if (isKeyguardRoot(root)) {
|
||||
return BatteryBarGeometry.Scenario.LOCKSCREEN;
|
||||
}
|
||||
boolean fullscreen = isPhoneRoot(root)
|
||||
&& isUnlockedScene()
|
||||
&& (isStatusBarSurfaceHidden(root) || isFullscreenSystemBarState());
|
||||
boolean landscape = isLandscapeForRoot(root, fullscreen);
|
||||
if (fullscreen) {
|
||||
return landscape
|
||||
? BatteryBarGeometry.Scenario.FULLSCREEN_LANDSCAPE
|
||||
: BatteryBarGeometry.Scenario.FULLSCREEN_PORTRAIT;
|
||||
}
|
||||
if (isPhoneRoot(root) && isUnlockedScene() && isTransparentStatusBarActive()) {
|
||||
return landscape
|
||||
? BatteryBarGeometry.Scenario.TRANSPARENT_LANDSCAPE
|
||||
: BatteryBarGeometry.Scenario.TRANSPARENT_PORTRAIT;
|
||||
}
|
||||
return landscape
|
||||
? BatteryBarGeometry.Scenario.UNLOCKED_LANDSCAPE
|
||||
: BatteryBarGeometry.Scenario.UNLOCKED_PORTRAIT;
|
||||
}
|
||||
|
||||
private BatteryBarGeometry.Scenario visibleStatusBarScenarioForRoot(View root) {
|
||||
boolean landscape = isLandscapeForRoot(root, isFullscreenSystemBarState());
|
||||
if (isPhoneRoot(root) && isUnlockedScene() && isTransparentStatusBarActive()) {
|
||||
return landscape
|
||||
? BatteryBarGeometry.Scenario.TRANSPARENT_LANDSCAPE
|
||||
: BatteryBarGeometry.Scenario.TRANSPARENT_PORTRAIT;
|
||||
}
|
||||
return landscape
|
||||
? BatteryBarGeometry.Scenario.UNLOCKED_LANDSCAPE
|
||||
: BatteryBarGeometry.Scenario.UNLOCKED_PORTRAIT;
|
||||
}
|
||||
|
||||
private boolean isFullscreenScenario(BatteryBarGeometry.Scenario scenario) {
|
||||
return scenario == BatteryBarGeometry.Scenario.FULLSCREEN_PORTRAIT
|
||||
|| scenario == BatteryBarGeometry.Scenario.FULLSCREEN_LANDSCAPE;
|
||||
}
|
||||
|
||||
private boolean shouldRenderOnRoot(View root) {
|
||||
SceneKey scene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||
if (isKeyguardRoot(root)) {
|
||||
return scene == null || !scene.isUnlocked();
|
||||
}
|
||||
if (isPhoneRoot(root)) {
|
||||
return scene == null || scene.isUnlocked();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isUnlockedScene() {
|
||||
SceneKey scene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||
return scene == null || scene.isUnlocked();
|
||||
}
|
||||
|
||||
private boolean isPhoneRoot(View root) {
|
||||
return root != null && root.getClass().getName().contains("PhoneStatusBarView");
|
||||
}
|
||||
|
||||
private boolean isKeyguardRoot(View root) {
|
||||
return root != null && root.getClass().getName().contains("KeyguardStatusBarView");
|
||||
}
|
||||
|
||||
private boolean isLandscapeForRoot(View root, boolean preferDisplayBounds) {
|
||||
if (root == null) {
|
||||
return false;
|
||||
}
|
||||
if (preferDisplayBounds) {
|
||||
Rect bounds = currentWindowBounds(root.getContext());
|
||||
if (bounds.width() > 0 && bounds.height() > 0 && bounds.width() != bounds.height()) {
|
||||
return bounds.width() > bounds.height();
|
||||
}
|
||||
}
|
||||
return root.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
|
||||
}
|
||||
|
||||
private boolean isTransparentStatusBarActive() {
|
||||
return (systemBarAppearance & APPEARANCE_LIGHT_STATUS_BARS) != 0;
|
||||
}
|
||||
|
||||
private boolean isFullscreenSystemBarState() {
|
||||
return (systemBarRequestedVisibleTypes & WindowInsets.Type.statusBars()) == 0;
|
||||
}
|
||||
|
||||
private boolean shouldRenderVisibleFullscreenBar(View root) {
|
||||
return root != null && isFullscreenSystemBarState() && statusBarTransientShown;
|
||||
}
|
||||
|
||||
private void updateTransientBarState(java.util.List<Object> args, boolean visible) {
|
||||
Integer displayId = args != null && !args.isEmpty() ? asInteger(args.get(0)) : null;
|
||||
boolean statusBarsAffected = transientArgsIncludeStatusBars(args);
|
||||
if (displayId == null || displayId != 0 || !statusBarsAffected) {
|
||||
return;
|
||||
}
|
||||
updateStatusBarTransientShown(visible);
|
||||
}
|
||||
|
||||
private void updateStatusBarTransientShown(boolean shown) {
|
||||
if (statusBarTransientShown == shown) {
|
||||
return;
|
||||
}
|
||||
statusBarTransientShown = shown;
|
||||
postRefreshAllRoots();
|
||||
}
|
||||
|
||||
private boolean transientArgsIncludeStatusBars(java.util.List<Object> args) {
|
||||
if (args == null || args.size() < 2) {
|
||||
return true;
|
||||
}
|
||||
Object typesArg = args.get(1);
|
||||
if (!(typesArg instanceof int[] types)) {
|
||||
return true;
|
||||
}
|
||||
int statusBars = WindowInsets.Type.statusBars();
|
||||
for (int type : types) {
|
||||
if ((type & statusBars) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private OverlayBounds fullscreenOverlayBounds(View root) {
|
||||
int width = root.getWidth();
|
||||
int height = root.getHeight();
|
||||
Rect bounds = currentWindowBounds(root.getContext());
|
||||
if (bounds.width() > 0) {
|
||||
width = bounds.width();
|
||||
}
|
||||
return new OverlayBounds(width, height);
|
||||
}
|
||||
|
||||
private static Rect currentWindowBounds(Context context) {
|
||||
if (context == null) {
|
||||
return new Rect();
|
||||
}
|
||||
WindowManager windowManager = context.getSystemService(WindowManager.class);
|
||||
if (windowManager == null) {
|
||||
return new Rect();
|
||||
}
|
||||
try {
|
||||
WindowMetrics metrics = windowManager.getCurrentWindowMetrics();
|
||||
return metrics != null ? metrics.getBounds() : new Rect();
|
||||
} catch (Throwable ignored) {
|
||||
return new Rect();
|
||||
}
|
||||
}
|
||||
|
||||
private record OverlayBounds(int width, int height) {
|
||||
}
|
||||
|
||||
private void updateSystemBarState(java.util.List<Object> args) {
|
||||
if (args == null || args.size() < 2) {
|
||||
return;
|
||||
}
|
||||
Integer displayId = asInteger(args.get(0));
|
||||
Integer appearance = asInteger(args.get(1));
|
||||
Integer behavior = args.size() > 4 ? asInteger(args.get(4)) : null;
|
||||
Integer requestedVisibleTypes = args.size() > 5 ? asInteger(args.get(5)) : null;
|
||||
if (displayId == null || displayId != 0 || appearance == null) {
|
||||
return;
|
||||
}
|
||||
String packageName = args.size() > 6 && args.get(6) instanceof String value ? value : "";
|
||||
boolean changed = systemBarAppearance != appearance
|
||||
|| (behavior != null && systemBarBehavior != behavior)
|
||||
|| (requestedVisibleTypes != null
|
||||
&& systemBarRequestedVisibleTypes != requestedVisibleTypes)
|
||||
|| !systemBarPackageName.equals(packageName);
|
||||
systemBarAppearance = appearance;
|
||||
if (behavior != null) {
|
||||
systemBarBehavior = behavior;
|
||||
}
|
||||
if (requestedVisibleTypes != null) {
|
||||
systemBarRequestedVisibleTypes = requestedVisibleTypes;
|
||||
}
|
||||
systemBarPackageName = packageName;
|
||||
if (changed) {
|
||||
postRefreshAllRoots();
|
||||
}
|
||||
}
|
||||
|
||||
private void postRefreshAllRoots() {
|
||||
for (View root : new ArrayList<>(roots)) {
|
||||
if (root != null && root.isAttachedToWindow()) {
|
||||
root.post(() -> {
|
||||
if (roots.contains(root) && root.isAttachedToWindow()) {
|
||||
applyBatteryBar(root);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isStatusBarSurfaceHidden(View root) {
|
||||
if (root == null) {
|
||||
return false;
|
||||
}
|
||||
if (!root.isShown()) {
|
||||
return true;
|
||||
}
|
||||
int[] location = new int[2];
|
||||
root.getLocationOnScreen(location);
|
||||
float left = location[0] + root.getTranslationX();
|
||||
float top = location[1] + root.getTranslationY();
|
||||
float right = left + root.getWidth();
|
||||
float bottom = top + root.getHeight();
|
||||
int screenWidth = root.getResources().getDisplayMetrics().widthPixels;
|
||||
int screenHeight = root.getResources().getDisplayMetrics().heightPixels;
|
||||
return bottom <= 0f || top >= screenHeight || right <= 0f || left >= screenWidth;
|
||||
}
|
||||
|
||||
private static Integer asInteger(Object value) {
|
||||
return value instanceof Integer integer ? integer : null;
|
||||
}
|
||||
|
||||
private void removeBatteryBarView(View root) {
|
||||
removeEmbeddedBatteryBarView(root);
|
||||
removeOverlayBatteryBar(root);
|
||||
}
|
||||
|
||||
private void removeEmbeddedBatteryBarView(View root) {
|
||||
BatteryBarView view = batteryBarViews.remove(root);
|
||||
lastSignatures.remove(root);
|
||||
if (view != null) {
|
||||
@@ -304,6 +732,14 @@ final class BatteryBarController {
|
||||
}
|
||||
}
|
||||
|
||||
private void removeOverlayBatteryBar(View root) {
|
||||
OverlayBatteryBar overlay = overlayBatteryBars.remove(root);
|
||||
lastOverlaySignatures.remove(root);
|
||||
if (overlay != null) {
|
||||
overlay.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private int resolveBarColor(View root, SbtSettings settings) {
|
||||
int fallback = resolveClockColor(root);
|
||||
return BatteryBarStyle.resolveColor(
|
||||
@@ -358,11 +794,117 @@ final class BatteryBarController {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class OverlayBatteryBar {
|
||||
private final WindowManager windowManager;
|
||||
private final FrameLayout host;
|
||||
private final BatteryBarView view;
|
||||
private boolean attached;
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
OverlayBatteryBar(Context context) {
|
||||
Context safeContext = context != null ? context : null;
|
||||
windowManager = safeContext != null
|
||||
? safeContext.getSystemService(WindowManager.class)
|
||||
: null;
|
||||
host = new FrameLayout(context);
|
||||
host.setClickable(false);
|
||||
host.setFocusable(false);
|
||||
host.setFocusableInTouchMode(false);
|
||||
view = new BatteryBarView(context);
|
||||
view.setClickable(false);
|
||||
view.setFocusable(false);
|
||||
view.setFocusableInTouchMode(false);
|
||||
host.addView(view, new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
boolean ensureAttached(int width, int height) {
|
||||
if (windowManager == null || width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (attached) {
|
||||
return updateLayout(width, height);
|
||||
}
|
||||
try {
|
||||
windowManager.addView(host, layoutParams(width, height));
|
||||
attached = true;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
layoutHost(width, height);
|
||||
return true;
|
||||
} catch (Throwable ignored) {
|
||||
attached = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean updateLayout(int width, int height) {
|
||||
if (!attached || windowManager == null || width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (this.width == width && this.height == height) {
|
||||
return true;
|
||||
}
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
try {
|
||||
windowManager.updateViewLayout(host, layoutParams(width, height));
|
||||
layoutHost(width, height);
|
||||
return true;
|
||||
} catch (Throwable ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void remove() {
|
||||
if (!attached || windowManager == null) {
|
||||
attached = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
windowManager.removeViewImmediate(host);
|
||||
} catch (Throwable ignored) {
|
||||
} finally {
|
||||
attached = false;
|
||||
}
|
||||
}
|
||||
|
||||
private WindowManager.LayoutParams layoutParams(int width, int height) {
|
||||
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
|
||||
width,
|
||||
height,
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|
||||
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
|
||||
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
||||
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
params.gravity = Gravity.TOP | Gravity.START;
|
||||
params.x = 0;
|
||||
params.y = 0;
|
||||
params.layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
|
||||
params.setTitle("StatusBarTweakBatteryBar");
|
||||
return params;
|
||||
}
|
||||
|
||||
private void layoutHost(int width, int height) {
|
||||
int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
|
||||
int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
|
||||
host.measure(widthSpec, heightSpec);
|
||||
host.layout(0, 0, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BatteryBarView extends View {
|
||||
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Rect drawRect = new Rect();
|
||||
private ViewGroup host;
|
||||
private SbtSettings settings;
|
||||
private BatteryBarGeometry geometry;
|
||||
private int batteryLevel = -1;
|
||||
private boolean charging;
|
||||
|
||||
@@ -375,12 +917,14 @@ final class BatteryBarController {
|
||||
void update(
|
||||
ViewGroup host,
|
||||
SbtSettings settings,
|
||||
BatteryBarGeometry geometry,
|
||||
int batteryLevel,
|
||||
boolean charging,
|
||||
int color
|
||||
) {
|
||||
this.host = host;
|
||||
this.settings = settings;
|
||||
this.geometry = geometry;
|
||||
this.batteryLevel = batteryLevel;
|
||||
this.charging = charging;
|
||||
paint.setColor(color);
|
||||
@@ -398,14 +942,17 @@ final class BatteryBarController {
|
||||
if (width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
int thickness = Math.max(1, dpToPx(getContext(), settings.batteryBarThicknessDp));
|
||||
BatteryBarGeometry activeGeometry = geometry != null
|
||||
? geometry
|
||||
: BatteryBarGeometry.global(settings);
|
||||
int thickness = Math.max(1, dpToPx(getContext(), activeGeometry.thicknessDp));
|
||||
int edgeOffset = Math.max(0, dpToPx(getContext(), settings.batteryBarEdgeOffsetDp));
|
||||
int left = edgeOffset;
|
||||
int right = width - edgeOffset;
|
||||
if (right <= left) {
|
||||
return;
|
||||
}
|
||||
int top = resolveTop(settings, thickness, height);
|
||||
int top = resolveTop(activeGeometry, thickness, height);
|
||||
int bottom = Math.min(height, top + thickness);
|
||||
if (bottom <= top) {
|
||||
return;
|
||||
@@ -416,9 +963,9 @@ final class BatteryBarController {
|
||||
if (barWidth <= 0) {
|
||||
return;
|
||||
}
|
||||
if ("rtl".equals(settings.batteryBarAlignment)) {
|
||||
if ("rtl".equals(activeGeometry.alignment)) {
|
||||
drawRect.set(right - barWidth, top, right, bottom);
|
||||
} else if ("center".equals(settings.batteryBarAlignment)) {
|
||||
} else if ("center".equals(activeGeometry.alignment)) {
|
||||
int centerX = (left + right) / 2;
|
||||
int half = barWidth / 2;
|
||||
drawRect.set(centerX - half, top, centerX - half + barWidth, bottom);
|
||||
@@ -427,8 +974,8 @@ final class BatteryBarController {
|
||||
}
|
||||
}
|
||||
|
||||
private int resolveTop(SbtSettings settings, int thickness, int height) {
|
||||
if ("bottom".equals(settings.batteryBarPosition)) {
|
||||
private int resolveTop(BatteryBarGeometry geometry, int thickness, int height) {
|
||||
if (geometry != null && "bottom".equals(geometry.position)) {
|
||||
return Math.max(0, height - thickness);
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -444,6 +444,9 @@ final class SnapshotRenderer {
|
||||
if (lowerKey.contains("wifi")) {
|
||||
return "wifi";
|
||||
}
|
||||
if (lowerKey.contains("mobile2")) {
|
||||
return "mobile2";
|
||||
}
|
||||
if (lowerKey.contains("mobile")) {
|
||||
return "mobile";
|
||||
}
|
||||
@@ -452,6 +455,9 @@ final class SnapshotRenderer {
|
||||
if (idName.contains("wifi") || className.contains("wifi")) {
|
||||
return "wifi";
|
||||
}
|
||||
if (idName.contains("mobile2") || className.contains("mobile2")) {
|
||||
return "mobile2";
|
||||
}
|
||||
if (idName.contains("mobile") || className.contains("mobile")) {
|
||||
return "mobile";
|
||||
}
|
||||
|
||||
+1
-1
@@ -174,7 +174,7 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
}
|
||||
CollectedIcons collectedIcons = previousIcons;
|
||||
if (forceStockRefresh || sceneChanged || stockSourcesChanged || collectedIcons == null) {
|
||||
collectedIcons = sourceCollector.collect(root, anchors, scene);
|
||||
collectedIcons = sourceCollector.collect(root, anchors, scene, settings);
|
||||
lastCollectedIconsByRoot.put(root, collectedIcons);
|
||||
} else if (forceChipRefresh && collectedIcons != null) {
|
||||
collectedIcons = sourceCollector.collectChips(root, anchors, collectedIcons);
|
||||
|
||||
+288
-1
@@ -23,6 +23,7 @@ import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle;
|
||||
import se.ajpanton.statusbartweak.runtime.mode.SceneKey;
|
||||
import se.ajpanton.statusbartweak.runtime.notifications.NotificationActiveKeyStore;
|
||||
import se.ajpanton.statusbartweak.runtime.notifications.NotificationUnreadTracker;
|
||||
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
|
||||
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
|
||||
|
||||
final class UnlockedStockSourceCollector {
|
||||
@@ -52,7 +53,7 @@ final class UnlockedStockSourceCollector {
|
||||
return anchors;
|
||||
}
|
||||
|
||||
CollectedIcons collect(View root, RootAnchors anchors, SceneKey scene) {
|
||||
CollectedIcons collect(View root, RootAnchors anchors, SceneKey scene, SbtSettings settings) {
|
||||
ArrayList<SnapshotSource> statusIcons = new ArrayList<>();
|
||||
ArrayList<SnapshotSource> notificationIcons = new ArrayList<>();
|
||||
ArrayList<SnapshotSource> statusChips = new ArrayList<>();
|
||||
@@ -73,6 +74,7 @@ final class UnlockedStockSourceCollector {
|
||||
if (anchors.battery != null && !ViewGeometry.isDescendantOf(anchors.battery, anchors.statusRoot)) {
|
||||
collectStatusSnapshotViews(anchors.battery, statusIcons);
|
||||
}
|
||||
selectDualSimStatusSignals(statusIcons, settings);
|
||||
normalizeStatusIconSourceBounds(root, statusIcons);
|
||||
if (scene != null && scene.isAod()) {
|
||||
if (scene.notificationDisplayStyle() == NotificationDisplayStyle.ICONS
|
||||
@@ -551,6 +553,290 @@ final class UnlockedStockSourceCollector {
|
||||
Math.max(1, height));
|
||||
}
|
||||
|
||||
private void selectDualSimStatusSignals(ArrayList<SnapshotSource> sources, SbtSettings settings) {
|
||||
if (sources == null || sources.size() < 2) {
|
||||
return;
|
||||
}
|
||||
String mode = settings != null
|
||||
? settings.systemIconDualSimSignalMode
|
||||
: SbtDefaults.SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE_DEFAULT;
|
||||
if (SbtSettings.SYSTEM_ICON_DUAL_SIM_SEPARATE.equals(mode)) {
|
||||
deduplicateSameStatusSignalViews(sources);
|
||||
return;
|
||||
}
|
||||
selectMobileStatusSignal(sources, mode);
|
||||
deduplicateSameStatusSignalViews(sources);
|
||||
}
|
||||
|
||||
private void deduplicateSameStatusSignalViews(ArrayList<SnapshotSource> sources) {
|
||||
LinkedHashMap<View, Integer> firstIndexByView = new LinkedHashMap<>();
|
||||
for (int i = 0; i < sources.size(); i++) {
|
||||
SnapshotSource source = sources.get(i);
|
||||
if (statusSignalKind(source) == null || source.view() == null) {
|
||||
continue;
|
||||
}
|
||||
Integer firstIndex = firstIndexByView.get(source.view());
|
||||
if (firstIndex == null) {
|
||||
firstIndexByView.put(source.view(), i);
|
||||
continue;
|
||||
}
|
||||
SnapshotSource existing = sources.get(firstIndex);
|
||||
if (statusSignalSourceScore(source) > statusSignalSourceScore(existing)) {
|
||||
sources.set(firstIndex, source);
|
||||
}
|
||||
sources.remove(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
private void selectMobileStatusSignal(ArrayList<SnapshotSource> sources, String mode) {
|
||||
ArrayList<Integer> mobileIndexes = new ArrayList<>();
|
||||
for (int i = 0; i < sources.size(); i++) {
|
||||
String signal = statusSignalKind(sources.get(i));
|
||||
if (signal != null
|
||||
&& signal.startsWith("mobile")
|
||||
&& !isMobileRadioTechnologySource(sources.get(i).view())) {
|
||||
mobileIndexes.add(i);
|
||||
}
|
||||
}
|
||||
if (mobileIndexes.size() < 2) {
|
||||
return;
|
||||
}
|
||||
int keptIndex = selectedMobileStatusSignalIndex(sources, mobileIndexes, mode);
|
||||
ArrayList<SnapshotSource> preservedRadioTechSources = new ArrayList<>();
|
||||
for (int i = mobileIndexes.size() - 1; i >= 0; i--) {
|
||||
int index = mobileIndexes.get(i);
|
||||
if (index != keptIndex) {
|
||||
SnapshotSource radioTechSource = mobileRadioTechnologyChildSource(sources.get(index));
|
||||
if (radioTechSource != null) {
|
||||
preservedRadioTechSources.add(radioTechSource);
|
||||
}
|
||||
sources.remove(index);
|
||||
}
|
||||
}
|
||||
sources.addAll(preservedRadioTechSources);
|
||||
}
|
||||
|
||||
private int selectedMobileStatusSignalIndex(
|
||||
ArrayList<SnapshotSource> sources,
|
||||
ArrayList<Integer> mobileIndexes,
|
||||
String mode
|
||||
) {
|
||||
if (SbtSettings.SYSTEM_ICON_DUAL_SIM_SECOND.equals(mode)) {
|
||||
return mobileIndexes.get(1);
|
||||
}
|
||||
if (SbtSettings.SYSTEM_ICON_DUAL_SIM_STRONGEST.equals(mode)
|
||||
|| SbtSettings.SYSTEM_ICON_DUAL_SIM_WEAKEST.equals(mode)) {
|
||||
int selected = selectedBySignalStrength(sources, mobileIndexes,
|
||||
SbtSettings.SYSTEM_ICON_DUAL_SIM_STRONGEST.equals(mode));
|
||||
if (selected >= 0) {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
if (SbtSettings.SYSTEM_ICON_DUAL_SIM_DATA.equals(mode)) {
|
||||
int selected = selectedByRole(sources, mobileIndexes, "data");
|
||||
if (selected >= 0) {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
if (SbtSettings.SYSTEM_ICON_DUAL_SIM_PHONE.equals(mode)) {
|
||||
int selected = selectedByRole(sources, mobileIndexes, "phone", "voice", "call");
|
||||
if (selected >= 0) {
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
return mobileIndexes.get(0);
|
||||
}
|
||||
|
||||
private int selectedBySignalStrength(
|
||||
ArrayList<SnapshotSource> sources,
|
||||
ArrayList<Integer> indexes,
|
||||
boolean strongest
|
||||
) {
|
||||
int selectedIndex = -1;
|
||||
int selectedStrength = 0;
|
||||
for (int index : indexes) {
|
||||
int strength = signalStrength(sources.get(index));
|
||||
if (strength < 0) {
|
||||
continue;
|
||||
}
|
||||
if (selectedIndex < 0
|
||||
|| (strongest && strength > selectedStrength)
|
||||
|| (!strongest && strength < selectedStrength)) {
|
||||
selectedIndex = index;
|
||||
selectedStrength = strength;
|
||||
}
|
||||
}
|
||||
return selectedIndex;
|
||||
}
|
||||
|
||||
private int selectedByRole(
|
||||
ArrayList<SnapshotSource> sources,
|
||||
ArrayList<Integer> indexes,
|
||||
String... needles
|
||||
) {
|
||||
for (int index : indexes) {
|
||||
String text = statusSignalText(sources.get(index)).toLowerCase(Locale.ROOT);
|
||||
for (String needle : needles) {
|
||||
if (text.contains(needle)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private String statusSignalKind(SnapshotSource source) {
|
||||
String value = statusSignalText(source).toLowerCase(Locale.ROOT);
|
||||
if (value.contains("wifi")) {
|
||||
return "wifi";
|
||||
}
|
||||
if (value.contains("mobile2")) {
|
||||
return "mobile2";
|
||||
}
|
||||
if (value.contains("mobile")) {
|
||||
return "mobile";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String statusSignalText(SnapshotSource source) {
|
||||
View view = source != null ? source.view() : null;
|
||||
if (view == null) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder text = new StringBuilder();
|
||||
text.append(safeText(source.keyHint())).append(' ');
|
||||
text.append(reflectString(view, "getSlot", "mSlot", "slot")).append(' ');
|
||||
text.append(ViewIdNames.idName(view)).append(' ');
|
||||
CharSequence contentDescription = view.getContentDescription();
|
||||
if (contentDescription != null) {
|
||||
text.append(contentDescription).append(' ');
|
||||
}
|
||||
text.append(view.getClass().getName()).append(' ');
|
||||
Object parent = view.getParent();
|
||||
int depth = 0;
|
||||
while (parent instanceof View parentView && depth++ < 3) {
|
||||
text.append(ViewIdNames.idName(parentView)).append(' ');
|
||||
CharSequence parentDescription = parentView.getContentDescription();
|
||||
if (parentDescription != null) {
|
||||
text.append(parentDescription).append(' ');
|
||||
}
|
||||
text.append(parentView.getClass().getName()).append(' ');
|
||||
parent = parentView.getParent();
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
private boolean isMobileRadioTechnologySource(View view) {
|
||||
if (view == null) {
|
||||
return false;
|
||||
}
|
||||
String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT);
|
||||
String className = view.getClass().getName().toLowerCase(Locale.ROOT);
|
||||
CharSequence contentDescription = view.getContentDescription();
|
||||
String content = contentDescription != null
|
||||
? contentDescription.toString().toLowerCase(Locale.ROOT)
|
||||
: "";
|
||||
return idName.contains("mobile_type")
|
||||
|| idName.contains("network_type")
|
||||
|| idName.contains("data_type")
|
||||
|| className.contains("mobiletype")
|
||||
|| className.contains("networktype")
|
||||
|| className.contains("datatype")
|
||||
|| isRadioTechnologyText(content);
|
||||
}
|
||||
|
||||
private boolean isRadioTechnologyText(String text) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
String value = text.toLowerCase(Locale.ROOT);
|
||||
return value.equals("5g")
|
||||
|| value.equals("4g")
|
||||
|| value.equals("4g+")
|
||||
|| value.equals("3g")
|
||||
|| value.equals("2g")
|
||||
|| value.equals("lte")
|
||||
|| value.equals("h+")
|
||||
|| value.equals("nr");
|
||||
}
|
||||
|
||||
private SnapshotSource mobileRadioTechnologyChildSource(SnapshotSource source) {
|
||||
View sourceView = source != null ? source.view() : null;
|
||||
if (!(sourceView instanceof ViewGroup group)) {
|
||||
return null;
|
||||
}
|
||||
View radioTechView = firstMobileRadioTechnologyChild(group);
|
||||
if (radioTechView == null) {
|
||||
return null;
|
||||
}
|
||||
return new SnapshotSource(radioTechView, SnapshotMode.VIEW, "mobile_type");
|
||||
}
|
||||
|
||||
private View firstMobileRadioTechnologyChild(ViewGroup group) {
|
||||
for (int i = 0; i < group.getChildCount(); i++) {
|
||||
View child = group.getChildAt(i);
|
||||
if (isMobileRadioTechnologySource(child)
|
||||
&& child.getVisibility() == View.VISIBLE
|
||||
&& measuredWidth(child) > 0
|
||||
&& measuredHeight(child) > 0) {
|
||||
return child;
|
||||
}
|
||||
if (child instanceof ViewGroup childGroup) {
|
||||
View nested = firstMobileRadioTechnologyChild(childGroup);
|
||||
if (nested != null) {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int signalStrength(SnapshotSource source) {
|
||||
String text = statusSignalText(source).toLowerCase(Locale.ROOT);
|
||||
int parsed = parseSignalBars(text);
|
||||
if (parsed >= 0) {
|
||||
return parsed;
|
||||
}
|
||||
View view = source != null ? source.view() : null;
|
||||
Drawable drawable = view instanceof ImageView imageView ? imageView.getDrawable() : null;
|
||||
return drawable != null ? drawable.getLevel() : -1;
|
||||
}
|
||||
|
||||
private int parseSignalBars(String text) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
if (!Character.isDigit(c)) {
|
||||
continue;
|
||||
}
|
||||
int value = Character.digit(c, 10);
|
||||
String suffix = text.substring(i + 1, Math.min(text.length(), i + 10));
|
||||
if (suffix.contains("bar") || suffix.contains("level")) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int statusSignalSourceScore(SnapshotSource source) {
|
||||
View view = source != null ? source.view() : null;
|
||||
if (view == null) {
|
||||
return 0;
|
||||
}
|
||||
String className = view.getClass().getName();
|
||||
int score = Math.max(0, view.getWidth()) * Math.max(0, view.getHeight());
|
||||
if (isModernSignalContainer(view)) {
|
||||
score += 1_000_000;
|
||||
} else if (className.contains("StatusBarMobile") || className.contains("StatusBarWifi")) {
|
||||
score += 100_000;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
private int syntheticStatusIconWidth(View view, int fallbackSize) {
|
||||
int width = measuredWidth(view);
|
||||
if (width > 0) {
|
||||
@@ -1262,6 +1548,7 @@ final class UnlockedStockSourceCollector {
|
||||
settings.layoutStatusMiddleSide,
|
||||
settings.layoutStatusVerticalOffsetPx,
|
||||
settings.systemIconBlockedModes,
|
||||
settings.systemIconDualSimSignalMode,
|
||||
settings.appIconBlockedModes,
|
||||
settings.appIconExceptionRules,
|
||||
settings.statusChipsHideAll,
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package se.ajpanton.statusbartweak.shell.settings;
|
||||
|
||||
public final class BatteryBarGeometry {
|
||||
|
||||
public enum Scenario {
|
||||
UNLOCKED_PORTRAIT("unlocked_portrait"),
|
||||
UNLOCKED_LANDSCAPE("unlocked_landscape"),
|
||||
TRANSPARENT_PORTRAIT("transparent_portrait"),
|
||||
TRANSPARENT_LANDSCAPE("transparent_landscape"),
|
||||
FULLSCREEN_PORTRAIT("fullscreen_portrait"),
|
||||
FULLSCREEN_LANDSCAPE("fullscreen_landscape"),
|
||||
LOCKSCREEN("lockscreen");
|
||||
|
||||
public final String id;
|
||||
|
||||
Scenario(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
public final String position;
|
||||
public final String alignment;
|
||||
public final int thicknessDp;
|
||||
|
||||
public BatteryBarGeometry(
|
||||
String position,
|
||||
String alignment,
|
||||
int thicknessDp
|
||||
) {
|
||||
this.position = sanitizePosition(position);
|
||||
this.alignment = sanitizeAlignment(alignment);
|
||||
this.thicknessDp = clamp(
|
||||
thicknessDp,
|
||||
SbtDefaults.BATTERY_BAR_THICKNESS_DP_MIN,
|
||||
SbtDefaults.BATTERY_BAR_THICKNESS_DP_MAX);
|
||||
}
|
||||
|
||||
public static BatteryBarGeometry global(SbtSettings settings) {
|
||||
if (settings == null) {
|
||||
return new BatteryBarGeometry(
|
||||
SbtDefaults.BATTERY_BAR_POSITION_DEFAULT,
|
||||
SbtDefaults.BATTERY_BAR_ALIGNMENT_DEFAULT,
|
||||
SbtDefaults.BATTERY_BAR_THICKNESS_DP_DEFAULT);
|
||||
}
|
||||
return new BatteryBarGeometry(
|
||||
settings.batteryBarPosition,
|
||||
settings.batteryBarAlignment,
|
||||
settings.batteryBarThicknessDp);
|
||||
}
|
||||
|
||||
public static BatteryBarGeometry resolve(
|
||||
SbtSettings settings,
|
||||
Scenario scenario
|
||||
) {
|
||||
BatteryBarGeometry fallback = global(settings);
|
||||
if (settings == null || scenario == null) {
|
||||
return fallback;
|
||||
}
|
||||
if (!settings.batteryBarGeometryOverrideEnabledScenarios.contains(scenario.id)) {
|
||||
return fallback;
|
||||
}
|
||||
return decode(settings.batteryBarGeometryOverrides.get(scenario.id), fallback);
|
||||
}
|
||||
|
||||
public static BatteryBarGeometry decode(String value, BatteryBarGeometry fallback) {
|
||||
BatteryBarGeometry safeFallback = fallback != null ? fallback : global(null);
|
||||
if (value == null || value.isEmpty()) {
|
||||
return safeFallback;
|
||||
}
|
||||
String[] parts = value.split("\\|", -1);
|
||||
if (parts.length != 3 && parts.length != 4) {
|
||||
return safeFallback;
|
||||
}
|
||||
return new BatteryBarGeometry(
|
||||
parts[0],
|
||||
parts[1],
|
||||
parseInt(parts[2], safeFallback.thicknessDp));
|
||||
}
|
||||
|
||||
public String encode() {
|
||||
return position + "|" + alignment + "|" + thicknessDp;
|
||||
}
|
||||
|
||||
public String signature() {
|
||||
return encode();
|
||||
}
|
||||
|
||||
public static String enabledKey(Scenario scenario) {
|
||||
return SbtSettings.batteryBarGeometryOverrideEnabledKey(scenario.id);
|
||||
}
|
||||
|
||||
public static String valueKey(Scenario scenario) {
|
||||
return SbtSettings.batteryBarGeometryKey(scenario.id);
|
||||
}
|
||||
|
||||
private static String sanitizePosition(String position) {
|
||||
return "bottom".equals(position) ? "bottom" : "top";
|
||||
}
|
||||
|
||||
private static String sanitizeAlignment(String alignment) {
|
||||
if ("rtl".equals(alignment) || "center".equals(alignment)) {
|
||||
return alignment;
|
||||
}
|
||||
return "ltr";
|
||||
}
|
||||
|
||||
private static int parseInt(String value, int fallback) {
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
private static int clamp(int value, int min, int max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
}
|
||||
@@ -134,6 +134,7 @@ public final class SbtDefaults {
|
||||
public static final String LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT = "left";
|
||||
public static final int LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT = 0;
|
||||
public static final int LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT = 38;
|
||||
public static final String SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE_DEFAULT = "separate";
|
||||
public static final int LAYOUT_ICON_HEIGHT_STEPS_MIN = 10;
|
||||
public static final int LAYOUT_ICON_HEIGHT_STEPS_MAX = 100;
|
||||
public static final int LAYOUT_ICON_CONTAINER_COUNT_MIN = 0;
|
||||
|
||||
@@ -72,6 +72,9 @@ public final class SbtSettings {
|
||||
public static final String KEY_BATTERY_BAR_ALIGNMENT = "battery_bar_alignment";
|
||||
public static final String KEY_BATTERY_BAR_THICKNESS_DP = "battery_bar_thickness_dp";
|
||||
public static final String KEY_BATTERY_BAR_EDGE_OFFSET_DP = "battery_bar_edge_offset_dp";
|
||||
public static final String KEY_BATTERY_BAR_GEOMETRY_OVERRIDE_ENABLED_PREFIX =
|
||||
"battery_bar_geometry_override_enabled_";
|
||||
public static final String KEY_BATTERY_BAR_GEOMETRY_PREFIX = "battery_bar_geometry_";
|
||||
public static final String KEY_BATTERY_BAR_MIN_LEVEL = "battery_bar_min_level";
|
||||
public static final String KEY_BATTERY_BAR_MAX_LEVEL = "battery_bar_max_level";
|
||||
public static final String KEY_BATTERY_BAR_DEFAULT_DISCHARGE_COLOR = "battery_bar_default_discharge_color";
|
||||
@@ -156,6 +159,14 @@ public final class SbtSettings {
|
||||
public static final String KEY_LAYOUT_STATUS_ICON_HEIGHT_STEPS = "layout_status_icon_height_steps";
|
||||
public static final String KEY_SYSTEM_ICON_BLOCKED_MODES = SystemIconRules.PREF_KEY_BLOCKED_MODES;
|
||||
public static final String KEY_SYSTEM_ICON_HIDE_SLOTS = "system_icon_hide_slots";
|
||||
public static final String KEY_SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE = "system_icon_dual_sim_signal_mode";
|
||||
public static final String SYSTEM_ICON_DUAL_SIM_SEPARATE = "separate";
|
||||
public static final String SYSTEM_ICON_DUAL_SIM_FIRST = "first";
|
||||
public static final String SYSTEM_ICON_DUAL_SIM_SECOND = "second";
|
||||
public static final String SYSTEM_ICON_DUAL_SIM_STRONGEST = "strongest";
|
||||
public static final String SYSTEM_ICON_DUAL_SIM_WEAKEST = "weakest";
|
||||
public static final String SYSTEM_ICON_DUAL_SIM_DATA = "data";
|
||||
public static final String SYSTEM_ICON_DUAL_SIM_PHONE = "phone";
|
||||
public static final String KEY_APP_ICON_BLOCKED_MODES = AppIconRules.PREF_KEY_BLOCKED_MODES;
|
||||
public static final String KEY_APP_ICON_EXCEPTION_RULES = AppIconRules.PREF_KEY_EXCEPTION_RULES;
|
||||
|
||||
@@ -251,6 +262,14 @@ public final class SbtSettings {
|
||||
return base == null || index <= 0 ? base : base + "_" + (index + 1);
|
||||
}
|
||||
|
||||
public static String batteryBarGeometryOverrideEnabledKey(String scenarioId) {
|
||||
return KEY_BATTERY_BAR_GEOMETRY_OVERRIDE_ENABLED_PREFIX + scenarioId;
|
||||
}
|
||||
|
||||
public static String batteryBarGeometryKey(String scenarioId) {
|
||||
return KEY_BATTERY_BAR_GEOMETRY_PREFIX + scenarioId;
|
||||
}
|
||||
|
||||
private static String suffixedKey(String base, int index) {
|
||||
return indexedKey(base, index);
|
||||
}
|
||||
@@ -281,6 +300,8 @@ public final class SbtSettings {
|
||||
public final String batteryBarAlignment;
|
||||
public final int batteryBarThicknessDp;
|
||||
public final int batteryBarEdgeOffsetDp;
|
||||
public final Set<String> batteryBarGeometryOverrideEnabledScenarios;
|
||||
public final Map<String, String> batteryBarGeometryOverrides;
|
||||
public final int batteryBarMinLevel;
|
||||
public final int batteryBarMaxLevel;
|
||||
public final String batteryBarDefaultDischargeColor;
|
||||
@@ -362,6 +383,7 @@ public final class SbtSettings {
|
||||
public final int layoutStatusAodIconHeightSteps;
|
||||
public final Map<String, String> clockCameraTypeOverrides;
|
||||
public final Map<String, Integer> systemIconBlockedModes;
|
||||
public final String systemIconDualSimSignalMode;
|
||||
public final boolean statusChipsHideAll;
|
||||
public final boolean statusChipsHideMediaUnlocked;
|
||||
public final boolean statusChipsHideNavUnlocked;
|
||||
@@ -403,6 +425,8 @@ public final class SbtSettings {
|
||||
String batteryBarAlignment,
|
||||
int batteryBarThicknessDp,
|
||||
int batteryBarEdgeOffsetDp,
|
||||
Set<String> batteryBarGeometryOverrideEnabledScenarios,
|
||||
Map<String, String> batteryBarGeometryOverrides,
|
||||
int batteryBarMinLevel,
|
||||
int batteryBarMaxLevel,
|
||||
String batteryBarDefaultDischargeColor,
|
||||
@@ -484,6 +508,7 @@ public final class SbtSettings {
|
||||
int[] layoutStatusAodVerticalOffsetsPx,
|
||||
Map<String, String> clockCameraTypeOverrides,
|
||||
Map<String, Integer> systemIconBlockedModes,
|
||||
String systemIconDualSimSignalMode,
|
||||
boolean statusChipsHideAll,
|
||||
boolean statusChipsHideMediaUnlocked,
|
||||
boolean statusChipsHideNavUnlocked,
|
||||
@@ -534,6 +559,9 @@ public final class SbtSettings {
|
||||
batteryBarEdgeOffsetDp,
|
||||
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MIN,
|
||||
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MAX);
|
||||
this.batteryBarGeometryOverrideEnabledScenarios =
|
||||
sanitizeBatteryBarScenarioSet(batteryBarGeometryOverrideEnabledScenarios);
|
||||
this.batteryBarGeometryOverrides = sanitizeBatteryBarScenarioMap(batteryBarGeometryOverrides);
|
||||
int clampedMinLevel = clampInt(
|
||||
batteryBarMinLevel,
|
||||
SbtDefaults.BATTERY_BAR_MIN_LEVEL_MIN,
|
||||
@@ -753,6 +781,9 @@ public final class SbtSettings {
|
||||
this.systemIconBlockedModes = systemIconBlockedModes != null
|
||||
? Collections.unmodifiableMap(new HashMap<>(systemIconBlockedModes))
|
||||
: Collections.emptyMap();
|
||||
this.systemIconDualSimSignalMode = validSystemIconDualSimSignalMode(systemIconDualSimSignalMode)
|
||||
? systemIconDualSimSignalMode
|
||||
: SbtDefaults.SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE_DEFAULT;
|
||||
this.statusChipsHideAll = statusChipsHideAll;
|
||||
this.statusChipsHideMediaUnlocked = statusChipsHideMediaUnlocked;
|
||||
this.statusChipsHideNavUnlocked = statusChipsHideNavUnlocked;
|
||||
@@ -810,6 +841,8 @@ public final class SbtSettings {
|
||||
SbtDefaults.BATTERY_BAR_ALIGNMENT_DEFAULT,
|
||||
SbtDefaults.BATTERY_BAR_THICKNESS_DP_DEFAULT,
|
||||
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_DEFAULT,
|
||||
Collections.emptySet(),
|
||||
Collections.emptyMap(),
|
||||
SbtDefaults.BATTERY_BAR_MIN_LEVEL_DEFAULT,
|
||||
SbtDefaults.BATTERY_BAR_MAX_LEVEL_DEFAULT,
|
||||
SbtDefaults.BATTERY_BAR_DEFAULT_DISCHARGE_COLOR_DEFAULT,
|
||||
@@ -891,6 +924,7 @@ public final class SbtSettings {
|
||||
null,
|
||||
Collections.emptyMap(),
|
||||
Collections.emptyMap(),
|
||||
SbtDefaults.SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE_DEFAULT,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
@@ -914,6 +948,22 @@ public final class SbtSettings {
|
||||
return "dot";
|
||||
}
|
||||
|
||||
public static boolean validSystemIconDualSimSignalMode(String mode) {
|
||||
return SYSTEM_ICON_DUAL_SIM_SEPARATE.equals(mode)
|
||||
|| SYSTEM_ICON_DUAL_SIM_FIRST.equals(mode)
|
||||
|| SYSTEM_ICON_DUAL_SIM_SECOND.equals(mode)
|
||||
|| SYSTEM_ICON_DUAL_SIM_STRONGEST.equals(mode)
|
||||
|| SYSTEM_ICON_DUAL_SIM_WEAKEST.equals(mode)
|
||||
|| SYSTEM_ICON_DUAL_SIM_DATA.equals(mode)
|
||||
|| SYSTEM_ICON_DUAL_SIM_PHONE.equals(mode);
|
||||
}
|
||||
|
||||
public static String readSystemIconDualSimSignalMode(String mode) {
|
||||
return validSystemIconDualSimSignalMode(mode)
|
||||
? mode
|
||||
: SbtDefaults.SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE_DEFAULT;
|
||||
}
|
||||
|
||||
public static SbtSettings from(Context context) {
|
||||
if (context == null) {
|
||||
return defaults();
|
||||
@@ -1057,6 +1107,8 @@ public final class SbtSettings {
|
||||
clampInt(prefs.getInt(KEY_BATTERY_BAR_EDGE_OFFSET_DP, SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_DEFAULT),
|
||||
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MIN,
|
||||
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MAX),
|
||||
readBatteryBarScenarioSetFromPrefs(prefs),
|
||||
readBatteryBarScenarioMapFromPrefs(prefs),
|
||||
clampInt(prefs.getInt(KEY_BATTERY_BAR_MIN_LEVEL, SbtDefaults.BATTERY_BAR_MIN_LEVEL_DEFAULT),
|
||||
SbtDefaults.BATTERY_BAR_MIN_LEVEL_MIN,
|
||||
SbtDefaults.BATTERY_BAR_MIN_LEVEL_MAX),
|
||||
@@ -1262,6 +1314,9 @@ public final class SbtSettings {
|
||||
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
|
||||
ClockCameraTypeSupport.loadOverrides(prefs, KEY_CLOCK_CAMERA_TYPE_OVERRIDES),
|
||||
systemIconBlockedModes,
|
||||
readSystemIconDualSimSignalMode(prefs.getString(
|
||||
KEY_SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE,
|
||||
SbtDefaults.SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE_DEFAULT)),
|
||||
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),
|
||||
@@ -1355,6 +1410,8 @@ public final class SbtSettings {
|
||||
clampInt(bundle.getInt(KEY_BATTERY_BAR_EDGE_OFFSET_DP, SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_DEFAULT),
|
||||
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MIN,
|
||||
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MAX),
|
||||
readBatteryBarScenarioSetFromBundle(bundle),
|
||||
readBatteryBarScenarioMapFromBundle(bundle),
|
||||
clampInt(bundle.getInt(KEY_BATTERY_BAR_MIN_LEVEL, SbtDefaults.BATTERY_BAR_MIN_LEVEL_DEFAULT),
|
||||
SbtDefaults.BATTERY_BAR_MIN_LEVEL_MIN,
|
||||
SbtDefaults.BATTERY_BAR_MIN_LEVEL_MAX),
|
||||
@@ -1560,6 +1617,9 @@ public final class SbtSettings {
|
||||
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
|
||||
ClockCameraTypeSupport.parseOverrides(readStringSetFromBundle(bundle, KEY_CLOCK_CAMERA_TYPE_OVERRIDES)),
|
||||
systemIconBlockedModes,
|
||||
readSystemIconDualSimSignalMode(bundle.getString(
|
||||
KEY_SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE,
|
||||
SbtDefaults.SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE_DEFAULT)),
|
||||
bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_ALL, false),
|
||||
bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_MEDIA_UNLOCKED, false),
|
||||
bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_NAV_UNLOCKED, false),
|
||||
@@ -1728,6 +1788,100 @@ public final class SbtSettings {
|
||||
return new HashSet<>(list);
|
||||
}
|
||||
|
||||
private static Set<String> readBatteryBarScenarioSetFromPrefs(SharedPreferences prefs) {
|
||||
if (prefs == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
HashSet<String> result = new HashSet<>();
|
||||
for (BatteryBarGeometry.Scenario scenario : BatteryBarGeometry.Scenario.values()) {
|
||||
if (prefs.getBoolean(batteryBarGeometryOverrideEnabledKey(scenario.id), false)) {
|
||||
result.add(scenario.id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Set<String> readBatteryBarScenarioSetFromBundle(Bundle bundle) {
|
||||
if (bundle == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
HashSet<String> result = new HashSet<>();
|
||||
for (BatteryBarGeometry.Scenario scenario : BatteryBarGeometry.Scenario.values()) {
|
||||
if (bundle.getBoolean(batteryBarGeometryOverrideEnabledKey(scenario.id), false)) {
|
||||
result.add(scenario.id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, String> readBatteryBarScenarioMapFromPrefs(SharedPreferences prefs) {
|
||||
if (prefs == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
HashMap<String, String> result = new HashMap<>();
|
||||
for (BatteryBarGeometry.Scenario scenario : BatteryBarGeometry.Scenario.values()) {
|
||||
String value = prefs.getString(batteryBarGeometryKey(scenario.id), null);
|
||||
if (value != null && !value.isEmpty()) {
|
||||
result.put(scenario.id, value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Map<String, String> readBatteryBarScenarioMapFromBundle(Bundle bundle) {
|
||||
if (bundle == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
HashMap<String, String> result = new HashMap<>();
|
||||
for (BatteryBarGeometry.Scenario scenario : BatteryBarGeometry.Scenario.values()) {
|
||||
String value = bundle.getString(batteryBarGeometryKey(scenario.id), null);
|
||||
if (value != null && !value.isEmpty()) {
|
||||
result.put(scenario.id, value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Set<String> sanitizeBatteryBarScenarioSet(Set<String> source) {
|
||||
if (source == null || source.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
HashSet<String> result = new HashSet<>();
|
||||
for (String id : source) {
|
||||
if (isBatteryBarScenarioId(id)) {
|
||||
result.add(id);
|
||||
}
|
||||
}
|
||||
return result.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(result);
|
||||
}
|
||||
|
||||
private static Map<String, String> sanitizeBatteryBarScenarioMap(Map<String, String> source) {
|
||||
if (source == null || source.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
HashMap<String, String> result = new HashMap<>();
|
||||
for (Map.Entry<String, String> entry : source.entrySet()) {
|
||||
String id = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
if (isBatteryBarScenarioId(id) && value != null && !value.isEmpty()) {
|
||||
result.put(id, value);
|
||||
}
|
||||
}
|
||||
return result.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
private static boolean isBatteryBarScenarioId(String id) {
|
||||
if (id == null) {
|
||||
return false;
|
||||
}
|
||||
for (BatteryBarGeometry.Scenario scenario : BatteryBarGeometry.Scenario.values()) {
|
||||
if (scenario.id.equals(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Map<String, Integer> readSystemIconBlockedModesFromPrefs(SharedPreferences prefs) {
|
||||
if (prefs == null) {
|
||||
return Collections.emptyMap();
|
||||
|
||||
@@ -99,6 +99,12 @@ public class SbtSettingsProvider extends ContentProvider {
|
||||
prefs.getInt(SbtSettings.KEY_BATTERY_BAR_THICKNESS_DP, SbtDefaults.BATTERY_BAR_THICKNESS_DP_DEFAULT));
|
||||
out.putInt(SbtSettings.KEY_BATTERY_BAR_EDGE_OFFSET_DP,
|
||||
prefs.getInt(SbtSettings.KEY_BATTERY_BAR_EDGE_OFFSET_DP, SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_DEFAULT));
|
||||
for (BatteryBarGeometry.Scenario scenario : BatteryBarGeometry.Scenario.values()) {
|
||||
String enabledKey = BatteryBarGeometry.enabledKey(scenario);
|
||||
String valueKey = BatteryBarGeometry.valueKey(scenario);
|
||||
out.putBoolean(enabledKey, prefs.getBoolean(enabledKey, false));
|
||||
out.putString(valueKey, prefs.getString(valueKey, null));
|
||||
}
|
||||
out.putInt(SbtSettings.KEY_BATTERY_BAR_MIN_LEVEL,
|
||||
prefs.getInt(SbtSettings.KEY_BATTERY_BAR_MIN_LEVEL, SbtDefaults.BATTERY_BAR_MIN_LEVEL_DEFAULT));
|
||||
out.putInt(SbtSettings.KEY_BATTERY_BAR_MAX_LEVEL,
|
||||
@@ -328,6 +334,10 @@ public class SbtSettingsProvider extends ContentProvider {
|
||||
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
|
||||
SbtSettings::layoutStatusVerticalOffsetKey,
|
||||
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT);
|
||||
out.putString(SbtSettings.KEY_SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE,
|
||||
SbtSettings.readSystemIconDualSimSignalMode(prefs.getString(
|
||||
SbtSettings.KEY_SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE,
|
||||
SbtDefaults.SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE_DEFAULT)));
|
||||
out.putInt(SbtSettings.KEY_LAYOUT_STATUS_LOCK_COUNT,
|
||||
prefs.getInt(SbtSettings.KEY_LAYOUT_STATUS_LOCK_COUNT,
|
||||
prefs.getBoolean(
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import se.ajpanton.statusbartweak.R;
|
||||
import se.ajpanton.statusbartweak.shell.settings.BatteryBarGeometry;
|
||||
import se.ajpanton.statusbartweak.shell.settings.BatteryBarStyle;
|
||||
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
|
||||
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
|
||||
@@ -75,6 +76,8 @@ public final class BatteryBarFragment extends Fragment {
|
||||
MaterialButton addThresholdButton = root.findViewById(R.id.battery_bar_add_threshold_button);
|
||||
LinearLayout thresholdsContainer = root.findViewById(R.id.battery_bar_thresholds_container);
|
||||
View geometrySection = root.findViewById(R.id.battery_bar_geometry_section);
|
||||
LinearLayout scenarioOverridesContainer =
|
||||
root.findViewById(R.id.battery_bar_scenario_overrides_container);
|
||||
mappingSlider.setValueFrom(0f);
|
||||
mappingSlider.setValueTo(100f);
|
||||
mappingSlider.setStepSize(1f);
|
||||
@@ -167,6 +170,8 @@ public final class BatteryBarFragment extends Fragment {
|
||||
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MIN,
|
||||
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MAX,
|
||||
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_DEFAULT);
|
||||
renderScenarioOverrides(scenarioOverridesContainer, prefs);
|
||||
setSectionEnabled(geometrySection, enabled.isChecked());
|
||||
|
||||
mappingSlider.addOnChangeListener((slider, value, fromUser) -> {
|
||||
if (!fromUser) {
|
||||
@@ -347,6 +352,252 @@ public final class BatteryBarFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void renderScenarioOverrides(LinearLayout container, SharedPreferences prefs) {
|
||||
if (container == null) {
|
||||
return;
|
||||
}
|
||||
container.removeAllViews();
|
||||
for (BatteryBarGeometry.Scenario scenario : BatteryBarGeometry.Scenario.values()) {
|
||||
addScenarioOverrideRow(container, prefs, scenario, scenarioLabelRes(scenario));
|
||||
}
|
||||
}
|
||||
|
||||
private void addScenarioOverrideRow(
|
||||
LinearLayout container,
|
||||
SharedPreferences prefs,
|
||||
BatteryBarGeometry.Scenario scenario,
|
||||
int labelRes
|
||||
) {
|
||||
Context context = container.getContext();
|
||||
LinearLayout row = new LinearLayout(context);
|
||||
row.setGravity(Gravity.CENTER_VERTICAL);
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
rowParams.topMargin = ViewTreeSupport.dp(context, 8);
|
||||
row.setLayoutParams(rowParams);
|
||||
|
||||
LinearLayout textColumn = new LinearLayout(context);
|
||||
textColumn.setOrientation(LinearLayout.VERTICAL);
|
||||
textColumn.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
0,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
1f));
|
||||
|
||||
SwitchMaterial enabledSwitch = new SwitchMaterial(context);
|
||||
enabledSwitch.setText(labelRes);
|
||||
enabledSwitch.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
textColumn.addView(enabledSwitch);
|
||||
|
||||
TextView summary = new TextView(context);
|
||||
summary.setTextAppearance(android.R.style.TextAppearance_Material_Body2);
|
||||
summary.setPadding(ViewTreeSupport.dp(context, 48), 0, 0, 0);
|
||||
textColumn.addView(summary);
|
||||
|
||||
MaterialButton editButton = new MaterialButton(context, null, com.google.android.material.R.attr.materialButtonOutlinedStyle);
|
||||
editButton.setText(R.string.battery_bar_edit_override);
|
||||
|
||||
row.addView(textColumn);
|
||||
row.addView(editButton);
|
||||
container.addView(row);
|
||||
bindScenarioOverride(scenario, labelRes, enabledSwitch, editButton, summary, prefs);
|
||||
}
|
||||
|
||||
private int scenarioLabelRes(BatteryBarGeometry.Scenario scenario) {
|
||||
if (scenario == BatteryBarGeometry.Scenario.UNLOCKED_LANDSCAPE) {
|
||||
return R.string.battery_bar_unlocked_landscape_override;
|
||||
}
|
||||
if (scenario == BatteryBarGeometry.Scenario.TRANSPARENT_PORTRAIT) {
|
||||
return R.string.battery_bar_transparent_portrait_override;
|
||||
}
|
||||
if (scenario == BatteryBarGeometry.Scenario.TRANSPARENT_LANDSCAPE) {
|
||||
return R.string.battery_bar_transparent_landscape_override;
|
||||
}
|
||||
if (scenario == BatteryBarGeometry.Scenario.FULLSCREEN_PORTRAIT) {
|
||||
return R.string.battery_bar_fullscreen_portrait_override;
|
||||
}
|
||||
if (scenario == BatteryBarGeometry.Scenario.FULLSCREEN_LANDSCAPE) {
|
||||
return R.string.battery_bar_fullscreen_landscape_override;
|
||||
}
|
||||
if (scenario == BatteryBarGeometry.Scenario.LOCKSCREEN) {
|
||||
return R.string.battery_bar_lockscreen_override;
|
||||
}
|
||||
return R.string.battery_bar_unlocked_portrait_override;
|
||||
}
|
||||
|
||||
private void bindScenarioOverride(
|
||||
BatteryBarGeometry.Scenario scenario,
|
||||
int labelRes,
|
||||
SwitchMaterial enabledSwitch,
|
||||
MaterialButton editButton,
|
||||
TextView summary,
|
||||
SharedPreferences prefs
|
||||
) {
|
||||
if (scenario == null || prefs == null) {
|
||||
return;
|
||||
}
|
||||
String enabledKey = BatteryBarGeometry.enabledKey(scenario);
|
||||
boolean enabled = prefs.getBoolean(enabledKey, false);
|
||||
if (enabledSwitch != null) {
|
||||
enabledSwitch.setChecked(enabled);
|
||||
enabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
SharedPreferences.Editor editor = prefs.edit().putBoolean(enabledKey, isChecked);
|
||||
if (isChecked && !prefs.contains(BatteryBarGeometry.valueKey(scenario))) {
|
||||
editor.putString(
|
||||
BatteryBarGeometry.valueKey(scenario),
|
||||
globalGeometryFromPrefs(prefs).encode());
|
||||
}
|
||||
editor.apply();
|
||||
SbtSettings.ensureReadable(requireContext());
|
||||
updateScenarioSummary(scenario, summary, editButton, prefs);
|
||||
});
|
||||
}
|
||||
if (editButton != null) {
|
||||
editButton.setOnClickListener(v -> showScenarioGeometryDialog(
|
||||
scenario,
|
||||
labelRes,
|
||||
prefs,
|
||||
summary,
|
||||
editButton));
|
||||
}
|
||||
updateScenarioSummary(scenario, summary, editButton, prefs);
|
||||
}
|
||||
|
||||
private void updateScenarioSummary(
|
||||
BatteryBarGeometry.Scenario scenario,
|
||||
TextView summary,
|
||||
MaterialButton editButton,
|
||||
SharedPreferences prefs
|
||||
) {
|
||||
if (scenario == null || prefs == null) {
|
||||
return;
|
||||
}
|
||||
boolean enabled = prefs.getBoolean(BatteryBarGeometry.enabledKey(scenario), false);
|
||||
BatteryBarGeometry geometry = scenarioGeometryFromPrefs(prefs, scenario);
|
||||
if (summary != null) {
|
||||
summary.setText(getString(
|
||||
R.string.battery_bar_override_summary,
|
||||
geometry.position,
|
||||
geometry.alignment,
|
||||
geometry.thicknessDp));
|
||||
summary.setAlpha(enabled ? 1f : 0.65f);
|
||||
}
|
||||
if (editButton != null) {
|
||||
editButton.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
private BatteryBarGeometry globalGeometryFromPrefs(SharedPreferences prefs) {
|
||||
return new BatteryBarGeometry(
|
||||
prefs.getString(
|
||||
SbtSettings.KEY_BATTERY_BAR_POSITION,
|
||||
SbtDefaults.BATTERY_BAR_POSITION_DEFAULT),
|
||||
prefs.getString(
|
||||
SbtSettings.KEY_BATTERY_BAR_ALIGNMENT,
|
||||
SbtDefaults.BATTERY_BAR_ALIGNMENT_DEFAULT),
|
||||
prefs.getInt(
|
||||
SbtSettings.KEY_BATTERY_BAR_THICKNESS_DP,
|
||||
SbtDefaults.BATTERY_BAR_THICKNESS_DP_DEFAULT));
|
||||
}
|
||||
|
||||
private BatteryBarGeometry scenarioGeometryFromPrefs(
|
||||
SharedPreferences prefs,
|
||||
BatteryBarGeometry.Scenario scenario
|
||||
) {
|
||||
return BatteryBarGeometry.decode(
|
||||
prefs.getString(BatteryBarGeometry.valueKey(scenario), null),
|
||||
globalGeometryFromPrefs(prefs));
|
||||
}
|
||||
|
||||
private void showScenarioGeometryDialog(
|
||||
BatteryBarGeometry.Scenario scenario,
|
||||
int labelRes,
|
||||
SharedPreferences prefs,
|
||||
TextView summary,
|
||||
MaterialButton editButton
|
||||
) {
|
||||
BatteryBarGeometry current = scenarioGeometryFromPrefs(prefs, scenario);
|
||||
LinearLayout content = new LinearLayout(requireContext());
|
||||
content.setOrientation(LinearLayout.VERTICAL);
|
||||
int padding = Math.round(20 * getResources().getDisplayMetrics().density);
|
||||
content.setPadding(padding, 0, padding, 0);
|
||||
|
||||
TextView positionTitle = dialogLabel(R.string.battery_bar_position_title);
|
||||
content.addView(positionTitle);
|
||||
RadioGroup positionGroup = new RadioGroup(requireContext());
|
||||
RadioButton positionTop = dialogRadio(R.string.battery_bar_position_top);
|
||||
RadioButton positionBottom = dialogRadio(R.string.battery_bar_position_bottom);
|
||||
positionGroup.addView(positionTop);
|
||||
positionGroup.addView(positionBottom);
|
||||
applyPositionSelection(positionTop, positionBottom, current.position);
|
||||
content.addView(positionGroup);
|
||||
|
||||
TextView alignmentTitle = dialogLabel(R.string.battery_bar_alignment_title);
|
||||
alignmentTitle.setPadding(0, padding / 2, 0, 0);
|
||||
content.addView(alignmentTitle);
|
||||
RadioGroup alignmentGroup = new RadioGroup(requireContext());
|
||||
RadioButton alignmentLtr = dialogRadio(R.string.battery_bar_alignment_ltr);
|
||||
RadioButton alignmentCenter = dialogRadio(R.string.battery_bar_alignment_center);
|
||||
RadioButton alignmentRtl = dialogRadio(R.string.battery_bar_alignment_rtl);
|
||||
alignmentGroup.addView(alignmentLtr);
|
||||
alignmentGroup.addView(alignmentCenter);
|
||||
alignmentGroup.addView(alignmentRtl);
|
||||
applyAlignmentSelection(alignmentLtr, alignmentRtl, alignmentCenter, current.alignment);
|
||||
content.addView(alignmentGroup);
|
||||
|
||||
EditText thicknessInput = dialogNumberInput(current.thicknessDp);
|
||||
content.addView(dialogLabel(R.string.battery_bar_thickness_title));
|
||||
content.addView(thicknessInput);
|
||||
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(getString(R.string.battery_bar_override_dialog_title, getString(labelRes)))
|
||||
.setView(content)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
BatteryBarGeometry updated = new BatteryBarGeometry(
|
||||
positionBottom.isChecked() ? "bottom" : "top",
|
||||
alignmentRtl.isChecked()
|
||||
? "rtl"
|
||||
: alignmentCenter.isChecked() ? "center" : "ltr",
|
||||
ViewTreeSupport.clamp(
|
||||
ViewTreeSupport.parseInt(thicknessInput, current.thicknessDp),
|
||||
SbtDefaults.BATTERY_BAR_THICKNESS_DP_MIN,
|
||||
SbtDefaults.BATTERY_BAR_THICKNESS_DP_MAX));
|
||||
prefs.edit()
|
||||
.putString(BatteryBarGeometry.valueKey(scenario), updated.encode())
|
||||
.apply();
|
||||
SbtSettings.ensureReadable(requireContext());
|
||||
updateScenarioSummary(scenario, summary, editButton, prefs);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private TextView dialogLabel(int resId) {
|
||||
TextView view = new TextView(requireContext());
|
||||
view.setText(resId);
|
||||
view.setTextAppearance(android.R.style.TextAppearance_Material_Body1);
|
||||
return view;
|
||||
}
|
||||
|
||||
private RadioButton dialogRadio(int resId) {
|
||||
RadioButton radioButton = new RadioButton(requireContext());
|
||||
radioButton.setId(View.generateViewId());
|
||||
radioButton.setText(resId);
|
||||
return radioButton;
|
||||
}
|
||||
|
||||
private EditText dialogNumberInput(int value) {
|
||||
EditText input = new EditText(requireContext());
|
||||
input.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
input.setSingleLine(true);
|
||||
input.setGravity(Gravity.CENTER);
|
||||
input.setText(String.valueOf(value));
|
||||
return input;
|
||||
}
|
||||
|
||||
private void adjustMapping(SharedPreferences prefs,
|
||||
RangeSlider slider,
|
||||
TextView minLabel,
|
||||
|
||||
@@ -10,9 +10,11 @@ import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.NinePatchDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -20,6 +22,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -40,6 +43,8 @@ public class SystemIconsFragment extends Fragment {
|
||||
|
||||
private static final String QUICKSTAR_PACKAGE = "com.samsung.android.qstuner";
|
||||
private static final int TOGGLE_ICON_DP = 24;
|
||||
private static final int MODE_TOGGLE_COLUMN_DP = 96;
|
||||
private static final int DROPDOWN_ARROW_DP = 18;
|
||||
|
||||
private static final List<SystemIconSection> SECTIONS = Arrays.asList(
|
||||
new SystemIconSection(
|
||||
@@ -124,6 +129,7 @@ public class SystemIconsFragment extends Fragment {
|
||||
.getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE);
|
||||
SbtSettings.ensureReadable(requireContext());
|
||||
Map<String, Integer> blockedModes = new HashMap<>(SbtSettings.from(requireContext()).systemIconBlockedModes);
|
||||
int leadingColumnWidth = leadingColumnWidth(requireContext());
|
||||
|
||||
Map<Integer, LinearLayout> sectionContainers = new HashMap<>();
|
||||
sectionContainers.put(R.string.system_icons_group_connectivity,
|
||||
@@ -139,13 +145,189 @@ public class SystemIconsFragment extends Fragment {
|
||||
continue;
|
||||
}
|
||||
for (SystemIconEntry entry : section.entries) {
|
||||
sectionContainer.addView(createRow(sectionContainer, prefs, blockedModes, entry));
|
||||
sectionContainer.addView(createRow(
|
||||
sectionContainer,
|
||||
prefs,
|
||||
blockedModes,
|
||||
entry,
|
||||
leadingColumnWidth));
|
||||
}
|
||||
}
|
||||
applyLayoutAvailability(root, prefs);
|
||||
return root;
|
||||
}
|
||||
|
||||
private int leadingColumnWidth(Context context) {
|
||||
int toggleColumnWidth = ViewTreeSupport.dp(context, MODE_TOGGLE_COLUMN_DP);
|
||||
TextView probe = new TextView(context);
|
||||
probe.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1);
|
||||
float textWidth = 0f;
|
||||
for (String label : dualSimSignalModeLabels(context)) {
|
||||
textWidth = Math.max(textWidth, probe.getPaint().measureText(label));
|
||||
}
|
||||
int dropdownWidth = Math.round(textWidth)
|
||||
+ ViewTreeSupport.dp(context, DROPDOWN_ARROW_DP + 4);
|
||||
return Math.max(toggleColumnWidth, dropdownWidth);
|
||||
}
|
||||
|
||||
private void bindDualSimSignalMode(
|
||||
LinearLayout section,
|
||||
SharedPreferences prefs,
|
||||
int leadingColumnWidth
|
||||
) {
|
||||
if (section == null || prefs == null) {
|
||||
return;
|
||||
}
|
||||
Context context = section.getContext();
|
||||
LinearLayout row = new LinearLayout(context);
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
row.setGravity(Gravity.CENTER_VERTICAL);
|
||||
LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
rowParams.topMargin = ViewTreeSupport.dp(context, 4);
|
||||
section.addView(row, rowParams);
|
||||
|
||||
String[] labels = dualSimSignalModeLabels(context);
|
||||
String current = SbtSettings.readSystemIconDualSimSignalMode(prefs.getString(
|
||||
SbtSettings.KEY_SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE,
|
||||
SbtDefaults.SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE_DEFAULT));
|
||||
LinearLayout dropdownCell = new LinearLayout(context);
|
||||
dropdownCell.setOrientation(LinearLayout.HORIZONTAL);
|
||||
dropdownCell.setGravity(Gravity.CENTER_VERTICAL);
|
||||
dropdownCell.setClickable(true);
|
||||
dropdownCell.setFocusable(true);
|
||||
LinearLayout.LayoutParams dropdownCellParams = new LinearLayout.LayoutParams(
|
||||
leadingColumnWidth,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
row.addView(dropdownCell, dropdownCellParams);
|
||||
|
||||
TextView selected = new TextView(context);
|
||||
selected.setText(labels[indexForDualSimSignalMode(current)]);
|
||||
selected.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1);
|
||||
selected.setSingleLine(true);
|
||||
selected.setEllipsize(TextUtils.TruncateAt.END);
|
||||
LinearLayout.LayoutParams selectedParams = new LinearLayout.LayoutParams(
|
||||
0,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
selectedParams.weight = 1f;
|
||||
dropdownCell.addView(selected, selectedParams);
|
||||
|
||||
TextView arrow = new TextView(context);
|
||||
arrow.setText("\u25BE");
|
||||
arrow.setGravity(Gravity.CENTER);
|
||||
arrow.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1);
|
||||
dropdownCell.addView(arrow, new LinearLayout.LayoutParams(
|
||||
ViewTreeSupport.dp(context, DROPDOWN_ARROW_DP),
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
dropdownCell.setOnClickListener(v -> showDualSimSignalModePopup(
|
||||
dropdownCell,
|
||||
selected,
|
||||
prefs));
|
||||
|
||||
TextView label = new TextView(context);
|
||||
label.setText(R.string.system_icons_dual_sim_signal_mode);
|
||||
label.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1);
|
||||
LinearLayout.LayoutParams labelParams = new LinearLayout.LayoutParams(
|
||||
0,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
labelParams.weight = 1f;
|
||||
labelParams.leftMargin = ViewTreeSupport.dp(context, 8);
|
||||
row.addView(label, labelParams);
|
||||
}
|
||||
|
||||
private void showDualSimSignalModePopup(
|
||||
View anchor,
|
||||
TextView selected,
|
||||
SharedPreferences prefs
|
||||
) {
|
||||
if (anchor == null || selected == null || prefs == null) {
|
||||
return;
|
||||
}
|
||||
Context context = anchor.getContext();
|
||||
LinearLayout content = new LinearLayout(context);
|
||||
content.setOrientation(LinearLayout.VERTICAL);
|
||||
int background = MaterialColors.getColor(
|
||||
context,
|
||||
com.google.android.material.R.attr.colorSurface,
|
||||
0xFF202020);
|
||||
content.setBackgroundColor(background);
|
||||
String[] labels = dualSimSignalModeLabels(context);
|
||||
for (int i = 0; i < labels.length; i++) {
|
||||
int index = i;
|
||||
TextView item = new TextView(context);
|
||||
item.setText(labels[i]);
|
||||
item.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1);
|
||||
item.setSingleLine(true);
|
||||
item.setPadding(
|
||||
0,
|
||||
ViewTreeSupport.dp(context, 12),
|
||||
0,
|
||||
ViewTreeSupport.dp(context, 12));
|
||||
content.addView(item, new LinearLayout.LayoutParams(
|
||||
Math.max(anchor.getWidth(), ViewTreeSupport.dp(context, MODE_TOGGLE_COLUMN_DP)),
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
item.setOnClickListener(v -> {
|
||||
String mode = dualSimSignalModeForIndex(index);
|
||||
prefs.edit()
|
||||
.putString(SbtSettings.KEY_SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE, mode)
|
||||
.apply();
|
||||
SbtSettings.ensureReadable(requireContext());
|
||||
selected.setText(labels[index]);
|
||||
Object popup = content.getTag();
|
||||
if (popup instanceof PopupWindow popupWindow) {
|
||||
popupWindow.dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
PopupWindow popupWindow = new PopupWindow(
|
||||
content,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
true);
|
||||
popupWindow.setBackgroundDrawable(new ColorDrawable(0x00000000));
|
||||
popupWindow.setOutsideTouchable(true);
|
||||
popupWindow.setElevation(ViewTreeSupport.dp(context, 8));
|
||||
content.setTag(popupWindow);
|
||||
popupWindow.showAsDropDown(anchor);
|
||||
}
|
||||
|
||||
private String[] dualSimSignalModeLabels(Context context) {
|
||||
return new String[] {
|
||||
context.getString(R.string.system_icons_dual_sim_separate),
|
||||
context.getString(R.string.system_icons_dual_sim_sim1),
|
||||
context.getString(R.string.system_icons_dual_sim_sim2),
|
||||
context.getString(R.string.system_icons_dual_sim_strongest),
|
||||
context.getString(R.string.system_icons_dual_sim_weakest),
|
||||
context.getString(R.string.system_icons_dual_sim_data),
|
||||
context.getString(R.string.system_icons_dual_sim_phone)
|
||||
};
|
||||
}
|
||||
|
||||
private String dualSimSignalModeForIndex(int index) {
|
||||
return switch (index) {
|
||||
case 1 -> SbtSettings.SYSTEM_ICON_DUAL_SIM_FIRST;
|
||||
case 2 -> SbtSettings.SYSTEM_ICON_DUAL_SIM_SECOND;
|
||||
case 3 -> SbtSettings.SYSTEM_ICON_DUAL_SIM_STRONGEST;
|
||||
case 4 -> SbtSettings.SYSTEM_ICON_DUAL_SIM_WEAKEST;
|
||||
case 5 -> SbtSettings.SYSTEM_ICON_DUAL_SIM_DATA;
|
||||
case 6 -> SbtSettings.SYSTEM_ICON_DUAL_SIM_PHONE;
|
||||
default -> SbtSettings.SYSTEM_ICON_DUAL_SIM_SEPARATE;
|
||||
};
|
||||
}
|
||||
|
||||
private int indexForDualSimSignalMode(String mode) {
|
||||
return switch (mode) {
|
||||
case SbtSettings.SYSTEM_ICON_DUAL_SIM_FIRST -> 1;
|
||||
case SbtSettings.SYSTEM_ICON_DUAL_SIM_SECOND -> 2;
|
||||
case SbtSettings.SYSTEM_ICON_DUAL_SIM_STRONGEST -> 3;
|
||||
case SbtSettings.SYSTEM_ICON_DUAL_SIM_WEAKEST -> 4;
|
||||
case SbtSettings.SYSTEM_ICON_DUAL_SIM_DATA -> 5;
|
||||
case SbtSettings.SYSTEM_ICON_DUAL_SIM_PHONE -> 6;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private void applyLayoutAvailability(View root, SharedPreferences prefs) {
|
||||
if (root == null || prefs == null) {
|
||||
return;
|
||||
@@ -168,7 +350,8 @@ public class SystemIconsFragment extends Fragment {
|
||||
private View createRow(ViewGroup parent,
|
||||
SharedPreferences prefs,
|
||||
Map<String, Integer> blockedModes,
|
||||
SystemIconEntry entry) {
|
||||
SystemIconEntry entry,
|
||||
int leadingColumnWidth) {
|
||||
Context context = parent.getContext();
|
||||
LinearLayout row = new LinearLayout(context);
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
@@ -181,7 +364,7 @@ public class SystemIconsFragment extends Fragment {
|
||||
LinearLayout modeStrip = new LinearLayout(context);
|
||||
modeStrip.setOrientation(LinearLayout.HORIZONTAL);
|
||||
LinearLayout.LayoutParams modeStripParams = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
leadingColumnWidth,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
modeStrip.setLayoutParams(modeStripParams);
|
||||
modeStrip.addView(createModeToggle(context, prefs, blockedModes, entry,
|
||||
@@ -202,6 +385,16 @@ public class SystemIconsFragment extends Fragment {
|
||||
label.setText(context.getString(entry.labelRes));
|
||||
label.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1);
|
||||
row.addView(label);
|
||||
if (entry.slots.contains("mobile")) {
|
||||
LinearLayout container = new LinearLayout(context);
|
||||
container.setOrientation(LinearLayout.VERTICAL);
|
||||
container.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
container.addView(row);
|
||||
bindDualSimSignalMode(container, prefs, leadingColumnWidth);
|
||||
return container;
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
@@ -299,6 +299,45 @@
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
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:letterSpacing="0.03"
|
||||
android:text="@string/battery_bar_scenario_overrides_title"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="?attr/textAppearanceSubtitle1"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/battery_bar_scenario_overrides_hint"
|
||||
android:textAppearance="?attr/textAppearanceBody2" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/battery_bar_scenario_overrides_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -53,6 +53,14 @@
|
||||
<string name="system_icons_title">Hide status icons</string>
|
||||
<string name="system_icons_hint">Status-icon rules for the custom layout engine.</string>
|
||||
<string name="system_icons_hide_hint">Icons represent, in order:\nAOD, Lockscreen, Unlocked.</string>
|
||||
<string name="system_icons_dual_sim_signal_mode">Dual-SIM signal icons</string>
|
||||
<string name="system_icons_dual_sim_separate">Separate</string>
|
||||
<string name="system_icons_dual_sim_sim1">SIM1</string>
|
||||
<string name="system_icons_dual_sim_sim2">SIM2</string>
|
||||
<string name="system_icons_dual_sim_strongest">Strongest</string>
|
||||
<string name="system_icons_dual_sim_weakest">Weakest</string>
|
||||
<string name="system_icons_dual_sim_data">Data SIM</string>
|
||||
<string name="system_icons_dual_sim_phone">Phone SIM</string>
|
||||
<string name="system_icons_group_connectivity">Connectivity</string>
|
||||
<string name="system_icons_group_notifications">Notifications</string>
|
||||
<string name="system_icons_group_system">System</string>
|
||||
@@ -135,6 +143,18 @@
|
||||
<string name="battery_bar_alignment_center">↔</string>
|
||||
<string name="battery_bar_thickness_title">Thickness (dp)</string>
|
||||
<string name="battery_bar_edge_offset_title">Edge offset (dp)</string>
|
||||
<string name="battery_bar_scenario_overrides_title">Scenario overrides</string>
|
||||
<string name="battery_bar_scenario_overrides_hint">Optional geometry overrides. Disabled scenarios use the default geometry above.</string>
|
||||
<string name="battery_bar_unlocked_portrait_override">Unlocked portrait</string>
|
||||
<string name="battery_bar_unlocked_landscape_override">Unlocked landscape</string>
|
||||
<string name="battery_bar_transparent_portrait_override">Transparent statusbar portrait</string>
|
||||
<string name="battery_bar_transparent_landscape_override">Transparent statusbar landscape</string>
|
||||
<string name="battery_bar_fullscreen_portrait_override">Fullscreen portrait</string>
|
||||
<string name="battery_bar_fullscreen_landscape_override">Fullscreen landscape</string>
|
||||
<string name="battery_bar_lockscreen_override">Lockscreen</string>
|
||||
<string name="battery_bar_edit_override">Edit</string>
|
||||
<string name="battery_bar_override_dialog_title">Edit %1$s geometry</string>
|
||||
<string name="battery_bar_override_summary">%1$s, %2$s, %3$ddp thick</string>
|
||||
<string name="battery_bar_mapping_title">Battery mapping</string>
|
||||
<string name="battery_bar_mapping_min_label">...0 at %1$d%%</string>
|
||||
<string name="battery_bar_mapping_max_label">...full at %1$d%%</string>
|
||||
|
||||
Reference in New Issue
Block a user