Add fullscreen battery bar scenarios and dual SIM signal handling

This commit is contained in:
ajp_anton
2026-06-06 14:48:45 +00:00
parent ec6cb91b14
commit 37d793a1be
12 changed files with 1641 additions and 15 deletions
@@ -3,14 +3,20 @@ package se.ajpanton.statusbartweak.runtime.features.battery;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewParent; import android.view.ViewParent;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.TextView; 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.features.RuntimeContext;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; 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.BroadcastReceiverSupport;
import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache; 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.BatteryBarStyle;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings; import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class BatteryBarController { final class BatteryBarController {
private static final int APPEARANCE_LIGHT_STATUS_BARS = 8;
private static final String[] ROOT_CLASSES = new String[] { private static final String[] ROOT_CLASSES = new String[] {
"com.android.systemui.statusbar.phone.PhoneStatusBarView", "com.android.systemui.statusbar.phone.PhoneStatusBarView",
"com.android.systemui.statusbar.phone.KeyguardStatusBarView" "com.android.systemui.statusbar.phone.KeyguardStatusBarView"
@@ -40,8 +50,16 @@ final class BatteryBarController {
private final RuntimeContext runtimeContext; private final RuntimeContext runtimeContext;
private final Set<View> roots = Collections.newSetFromMap(new WeakHashMap<>()); private final Set<View> roots = Collections.newSetFromMap(new WeakHashMap<>());
private final Map<View, BatteryBarView> batteryBarViews = 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 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> 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 hooksInstalled;
private boolean receiverRegistered; private boolean receiverRegistered;
@@ -59,6 +77,10 @@ final class BatteryBarController {
for (String className : ROOT_CLASSES) { for (String className : ROOT_CLASSES) {
installHooksForClass(classLoader, className); installHooksForClass(classLoader, className);
} }
installRootTransformHooks();
installSystemBarAttributeListener(classLoader);
installTransientBarListener(classLoader);
runtimeContext.getModeStateRepository().addSceneListener((previous, current) -> refreshAllRoots());
hooksInstalled = true; hooksInstalled = true;
} }
@@ -100,6 +122,113 @@ final class BatteryBarController {
} }
return result; 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) { private void trackAndApplyRoot(View root) {
@@ -115,6 +244,7 @@ final class BatteryBarController {
root.removeOnLayoutChangeListener(listener); root.removeOnLayoutChangeListener(listener);
} }
roots.remove(root); roots.remove(root);
pendingOverlayRetries.remove(root);
removeBatteryBarView(root); removeBatteryBarView(root);
} }
@@ -210,19 +340,88 @@ final class BatteryBarController {
return; return;
} }
int color = resolveBarColor(root, settings); 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); BatteryBarView existingView = batteryBarViews.get(root);
String lastSignature = lastSignatures.get(root); String lastSignature = lastSignatures.get(root);
if (signature.equals(lastSignature) && existingView != null && existingView.getParent() == rootGroup) { if (signature.equals(lastSignature) && existingView != null && existingView.getParent() == rootGroup) {
existingView.bringToFront(); existingView.bringToFront();
return; 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); BatteryBarView barView = ensureBatteryBarView(rootGroup);
layoutBatteryBarView(rootGroup, barView); layoutBatteryBarView(rootGroup, barView);
barView.update(rootGroup, settings, batteryLevel, plugged, color); barView.update(rootGroup, settings, geometry, batteryLevel, plugged, color);
barView.bringToFront(); barView.bringToFront();
barView.invalidate(); 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) { 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() + "|" return root.getWidth() + "|"
+ root.getHeight() + "|" + root.getHeight() + "|"
+ scenario + "|"
+ batteryLevel + "|" + batteryLevel + "|"
+ plugged + "|" + plugged + "|"
+ color + "|" + color + "|"
@@ -289,6 +495,7 @@ final class BatteryBarController {
+ settings.batteryBarAlignment + "|" + settings.batteryBarAlignment + "|"
+ settings.batteryBarThicknessDp + "|" + settings.batteryBarThicknessDp + "|"
+ settings.batteryBarEdgeOffsetDp + "|" + settings.batteryBarEdgeOffsetDp + "|"
+ (geometry != null ? geometry.signature() : "") + "|"
+ settings.batteryBarMinLevel + "|" + settings.batteryBarMinLevel + "|"
+ settings.batteryBarMaxLevel + "|" + settings.batteryBarMaxLevel + "|"
+ settings.batteryBarDefaultDischargeColor + "|" + settings.batteryBarDefaultDischargeColor + "|"
@@ -296,7 +503,228 @@ final class BatteryBarController {
+ BatteryBarStyle.encodeThresholds(settings.batteryBarThresholds); + 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) { private void removeBatteryBarView(View root) {
removeEmbeddedBatteryBarView(root);
removeOverlayBatteryBar(root);
}
private void removeEmbeddedBatteryBarView(View root) {
BatteryBarView view = batteryBarViews.remove(root); BatteryBarView view = batteryBarViews.remove(root);
lastSignatures.remove(root); lastSignatures.remove(root);
if (view != null) { 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) { private int resolveBarColor(View root, SbtSettings settings) {
int fallback = resolveClockColor(root); int fallback = resolveClockColor(root);
return BatteryBarStyle.resolveColor( 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 static final class BatteryBarView extends View {
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Rect drawRect = new Rect(); private final Rect drawRect = new Rect();
private ViewGroup host; private ViewGroup host;
private SbtSettings settings; private SbtSettings settings;
private BatteryBarGeometry geometry;
private int batteryLevel = -1; private int batteryLevel = -1;
private boolean charging; private boolean charging;
@@ -375,12 +917,14 @@ final class BatteryBarController {
void update( void update(
ViewGroup host, ViewGroup host,
SbtSettings settings, SbtSettings settings,
BatteryBarGeometry geometry,
int batteryLevel, int batteryLevel,
boolean charging, boolean charging,
int color int color
) { ) {
this.host = host; this.host = host;
this.settings = settings; this.settings = settings;
this.geometry = geometry;
this.batteryLevel = batteryLevel; this.batteryLevel = batteryLevel;
this.charging = charging; this.charging = charging;
paint.setColor(color); paint.setColor(color);
@@ -398,14 +942,17 @@ final class BatteryBarController {
if (width <= 0 || height <= 0) { if (width <= 0 || height <= 0) {
return; 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 edgeOffset = Math.max(0, dpToPx(getContext(), settings.batteryBarEdgeOffsetDp));
int left = edgeOffset; int left = edgeOffset;
int right = width - edgeOffset; int right = width - edgeOffset;
if (right <= left) { if (right <= left) {
return; return;
} }
int top = resolveTop(settings, thickness, height); int top = resolveTop(activeGeometry, thickness, height);
int bottom = Math.min(height, top + thickness); int bottom = Math.min(height, top + thickness);
if (bottom <= top) { if (bottom <= top) {
return; return;
@@ -416,9 +963,9 @@ final class BatteryBarController {
if (barWidth <= 0) { if (barWidth <= 0) {
return; return;
} }
if ("rtl".equals(settings.batteryBarAlignment)) { if ("rtl".equals(activeGeometry.alignment)) {
drawRect.set(right - barWidth, top, right, bottom); 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 centerX = (left + right) / 2;
int half = barWidth / 2; int half = barWidth / 2;
drawRect.set(centerX - half, top, centerX - half + barWidth, bottom); 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) { private int resolveTop(BatteryBarGeometry geometry, int thickness, int height) {
if ("bottom".equals(settings.batteryBarPosition)) { if (geometry != null && "bottom".equals(geometry.position)) {
return Math.max(0, height - thickness); return Math.max(0, height - thickness);
} }
return 0; return 0;
@@ -444,6 +444,9 @@ final class SnapshotRenderer {
if (lowerKey.contains("wifi")) { if (lowerKey.contains("wifi")) {
return "wifi"; return "wifi";
} }
if (lowerKey.contains("mobile2")) {
return "mobile2";
}
if (lowerKey.contains("mobile")) { if (lowerKey.contains("mobile")) {
return "mobile"; return "mobile";
} }
@@ -452,6 +455,9 @@ final class SnapshotRenderer {
if (idName.contains("wifi") || className.contains("wifi")) { if (idName.contains("wifi") || className.contains("wifi")) {
return "wifi"; return "wifi";
} }
if (idName.contains("mobile2") || className.contains("mobile2")) {
return "mobile2";
}
if (idName.contains("mobile") || className.contains("mobile")) { if (idName.contains("mobile") || className.contains("mobile")) {
return "mobile"; return "mobile";
} }
@@ -174,7 +174,7 @@ public final class UnlockedIconSnapshotRenderController {
} }
CollectedIcons collectedIcons = previousIcons; CollectedIcons collectedIcons = previousIcons;
if (forceStockRefresh || sceneChanged || stockSourcesChanged || collectedIcons == null) { if (forceStockRefresh || sceneChanged || stockSourcesChanged || collectedIcons == null) {
collectedIcons = sourceCollector.collect(root, anchors, scene); collectedIcons = sourceCollector.collect(root, anchors, scene, settings);
lastCollectedIconsByRoot.put(root, collectedIcons); lastCollectedIconsByRoot.put(root, collectedIcons);
} else if (forceChipRefresh && collectedIcons != null) { } else if (forceChipRefresh && collectedIcons != null) {
collectedIcons = sourceCollector.collectChips(root, anchors, collectedIcons); collectedIcons = sourceCollector.collectChips(root, anchors, collectedIcons);
@@ -23,6 +23,7 @@ import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle;
import se.ajpanton.statusbartweak.runtime.mode.SceneKey; import se.ajpanton.statusbartweak.runtime.mode.SceneKey;
import se.ajpanton.statusbartweak.runtime.notifications.NotificationActiveKeyStore; import se.ajpanton.statusbartweak.runtime.notifications.NotificationActiveKeyStore;
import se.ajpanton.statusbartweak.runtime.notifications.NotificationUnreadTracker; import se.ajpanton.statusbartweak.runtime.notifications.NotificationUnreadTracker;
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings; import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class UnlockedStockSourceCollector { final class UnlockedStockSourceCollector {
@@ -52,7 +53,7 @@ final class UnlockedStockSourceCollector {
return anchors; 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> statusIcons = new ArrayList<>();
ArrayList<SnapshotSource> notificationIcons = new ArrayList<>(); ArrayList<SnapshotSource> notificationIcons = new ArrayList<>();
ArrayList<SnapshotSource> statusChips = new ArrayList<>(); ArrayList<SnapshotSource> statusChips = new ArrayList<>();
@@ -73,6 +74,7 @@ final class UnlockedStockSourceCollector {
if (anchors.battery != null && !ViewGeometry.isDescendantOf(anchors.battery, anchors.statusRoot)) { if (anchors.battery != null && !ViewGeometry.isDescendantOf(anchors.battery, anchors.statusRoot)) {
collectStatusSnapshotViews(anchors.battery, statusIcons); collectStatusSnapshotViews(anchors.battery, statusIcons);
} }
selectDualSimStatusSignals(statusIcons, settings);
normalizeStatusIconSourceBounds(root, statusIcons); normalizeStatusIconSourceBounds(root, statusIcons);
if (scene != null && scene.isAod()) { if (scene != null && scene.isAod()) {
if (scene.notificationDisplayStyle() == NotificationDisplayStyle.ICONS if (scene.notificationDisplayStyle() == NotificationDisplayStyle.ICONS
@@ -551,6 +553,290 @@ final class UnlockedStockSourceCollector {
Math.max(1, height)); 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) { private int syntheticStatusIconWidth(View view, int fallbackSize) {
int width = measuredWidth(view); int width = measuredWidth(view);
if (width > 0) { if (width > 0) {
@@ -1262,6 +1548,7 @@ final class UnlockedStockSourceCollector {
settings.layoutStatusMiddleSide, settings.layoutStatusMiddleSide,
settings.layoutStatusVerticalOffsetPx, settings.layoutStatusVerticalOffsetPx,
settings.systemIconBlockedModes, settings.systemIconBlockedModes,
settings.systemIconDualSimSignalMode,
settings.appIconBlockedModes, settings.appIconBlockedModes,
settings.appIconExceptionRules, settings.appIconExceptionRules,
settings.statusChipsHideAll, 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 String LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT = "left";
public static final int LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT = 0; 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 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_MIN = 10;
public static final int LAYOUT_ICON_HEIGHT_STEPS_MAX = 100; public static final int LAYOUT_ICON_HEIGHT_STEPS_MAX = 100;
public static final int LAYOUT_ICON_CONTAINER_COUNT_MIN = 0; 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_ALIGNMENT = "battery_bar_alignment";
public static final String KEY_BATTERY_BAR_THICKNESS_DP = "battery_bar_thickness_dp"; 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_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_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_MAX_LEVEL = "battery_bar_max_level";
public static final String KEY_BATTERY_BAR_DEFAULT_DISCHARGE_COLOR = "battery_bar_default_discharge_color"; 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_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_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_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_BLOCKED_MODES = AppIconRules.PREF_KEY_BLOCKED_MODES;
public static final String KEY_APP_ICON_EXCEPTION_RULES = AppIconRules.PREF_KEY_EXCEPTION_RULES; 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); 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) { private static String suffixedKey(String base, int index) {
return indexedKey(base, index); return indexedKey(base, index);
} }
@@ -281,6 +300,8 @@ public final class SbtSettings {
public final String batteryBarAlignment; public final String batteryBarAlignment;
public final int batteryBarThicknessDp; public final int batteryBarThicknessDp;
public final int batteryBarEdgeOffsetDp; public final int batteryBarEdgeOffsetDp;
public final Set<String> batteryBarGeometryOverrideEnabledScenarios;
public final Map<String, String> batteryBarGeometryOverrides;
public final int batteryBarMinLevel; public final int batteryBarMinLevel;
public final int batteryBarMaxLevel; public final int batteryBarMaxLevel;
public final String batteryBarDefaultDischargeColor; public final String batteryBarDefaultDischargeColor;
@@ -362,6 +383,7 @@ public final class SbtSettings {
public final int layoutStatusAodIconHeightSteps; public final int layoutStatusAodIconHeightSteps;
public final Map<String, String> clockCameraTypeOverrides; public final Map<String, String> clockCameraTypeOverrides;
public final Map<String, Integer> systemIconBlockedModes; public final Map<String, Integer> systemIconBlockedModes;
public final String systemIconDualSimSignalMode;
public final boolean statusChipsHideAll; public final boolean statusChipsHideAll;
public final boolean statusChipsHideMediaUnlocked; public final boolean statusChipsHideMediaUnlocked;
public final boolean statusChipsHideNavUnlocked; public final boolean statusChipsHideNavUnlocked;
@@ -403,6 +425,8 @@ public final class SbtSettings {
String batteryBarAlignment, String batteryBarAlignment,
int batteryBarThicknessDp, int batteryBarThicknessDp,
int batteryBarEdgeOffsetDp, int batteryBarEdgeOffsetDp,
Set<String> batteryBarGeometryOverrideEnabledScenarios,
Map<String, String> batteryBarGeometryOverrides,
int batteryBarMinLevel, int batteryBarMinLevel,
int batteryBarMaxLevel, int batteryBarMaxLevel,
String batteryBarDefaultDischargeColor, String batteryBarDefaultDischargeColor,
@@ -484,6 +508,7 @@ public final class SbtSettings {
int[] layoutStatusAodVerticalOffsetsPx, int[] layoutStatusAodVerticalOffsetsPx,
Map<String, String> clockCameraTypeOverrides, Map<String, String> clockCameraTypeOverrides,
Map<String, Integer> systemIconBlockedModes, Map<String, Integer> systemIconBlockedModes,
String systemIconDualSimSignalMode,
boolean statusChipsHideAll, boolean statusChipsHideAll,
boolean statusChipsHideMediaUnlocked, boolean statusChipsHideMediaUnlocked,
boolean statusChipsHideNavUnlocked, boolean statusChipsHideNavUnlocked,
@@ -534,6 +559,9 @@ public final class SbtSettings {
batteryBarEdgeOffsetDp, batteryBarEdgeOffsetDp,
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MIN, SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MIN,
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MAX); SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MAX);
this.batteryBarGeometryOverrideEnabledScenarios =
sanitizeBatteryBarScenarioSet(batteryBarGeometryOverrideEnabledScenarios);
this.batteryBarGeometryOverrides = sanitizeBatteryBarScenarioMap(batteryBarGeometryOverrides);
int clampedMinLevel = clampInt( int clampedMinLevel = clampInt(
batteryBarMinLevel, batteryBarMinLevel,
SbtDefaults.BATTERY_BAR_MIN_LEVEL_MIN, SbtDefaults.BATTERY_BAR_MIN_LEVEL_MIN,
@@ -753,6 +781,9 @@ public final class SbtSettings {
this.systemIconBlockedModes = systemIconBlockedModes != null this.systemIconBlockedModes = systemIconBlockedModes != null
? Collections.unmodifiableMap(new HashMap<>(systemIconBlockedModes)) ? Collections.unmodifiableMap(new HashMap<>(systemIconBlockedModes))
: Collections.emptyMap(); : Collections.emptyMap();
this.systemIconDualSimSignalMode = validSystemIconDualSimSignalMode(systemIconDualSimSignalMode)
? systemIconDualSimSignalMode
: SbtDefaults.SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE_DEFAULT;
this.statusChipsHideAll = statusChipsHideAll; this.statusChipsHideAll = statusChipsHideAll;
this.statusChipsHideMediaUnlocked = statusChipsHideMediaUnlocked; this.statusChipsHideMediaUnlocked = statusChipsHideMediaUnlocked;
this.statusChipsHideNavUnlocked = statusChipsHideNavUnlocked; this.statusChipsHideNavUnlocked = statusChipsHideNavUnlocked;
@@ -810,6 +841,8 @@ public final class SbtSettings {
SbtDefaults.BATTERY_BAR_ALIGNMENT_DEFAULT, SbtDefaults.BATTERY_BAR_ALIGNMENT_DEFAULT,
SbtDefaults.BATTERY_BAR_THICKNESS_DP_DEFAULT, SbtDefaults.BATTERY_BAR_THICKNESS_DP_DEFAULT,
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_DEFAULT, SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_DEFAULT,
Collections.emptySet(),
Collections.emptyMap(),
SbtDefaults.BATTERY_BAR_MIN_LEVEL_DEFAULT, SbtDefaults.BATTERY_BAR_MIN_LEVEL_DEFAULT,
SbtDefaults.BATTERY_BAR_MAX_LEVEL_DEFAULT, SbtDefaults.BATTERY_BAR_MAX_LEVEL_DEFAULT,
SbtDefaults.BATTERY_BAR_DEFAULT_DISCHARGE_COLOR_DEFAULT, SbtDefaults.BATTERY_BAR_DEFAULT_DISCHARGE_COLOR_DEFAULT,
@@ -891,6 +924,7 @@ public final class SbtSettings {
null, null,
Collections.emptyMap(), Collections.emptyMap(),
Collections.emptyMap(), Collections.emptyMap(),
SbtDefaults.SYSTEM_ICON_DUAL_SIM_SIGNAL_MODE_DEFAULT,
false, false,
false, false,
false, false,
@@ -914,6 +948,22 @@ public final class SbtSettings {
return "dot"; 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) { public static SbtSettings from(Context context) {
if (context == null) { if (context == null) {
return defaults(); 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), 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_MIN,
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MAX), 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), 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_MIN,
SbtDefaults.BATTERY_BAR_MIN_LEVEL_MAX), SbtDefaults.BATTERY_BAR_MIN_LEVEL_MAX),
@@ -1262,6 +1314,9 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT), SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
ClockCameraTypeSupport.loadOverrides(prefs, KEY_CLOCK_CAMERA_TYPE_OVERRIDES), ClockCameraTypeSupport.loadOverrides(prefs, KEY_CLOCK_CAMERA_TYPE_OVERRIDES),
systemIconBlockedModes, 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_ALL, false),
prefs.getBoolean(KEY_STATUS_CHIPS_HIDE_MEDIA_UNLOCKED, 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_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), 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_MIN,
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MAX), 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), 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_MIN,
SbtDefaults.BATTERY_BAR_MIN_LEVEL_MAX), SbtDefaults.BATTERY_BAR_MIN_LEVEL_MAX),
@@ -1560,6 +1617,9 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT), SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
ClockCameraTypeSupport.parseOverrides(readStringSetFromBundle(bundle, KEY_CLOCK_CAMERA_TYPE_OVERRIDES)), ClockCameraTypeSupport.parseOverrides(readStringSetFromBundle(bundle, KEY_CLOCK_CAMERA_TYPE_OVERRIDES)),
systemIconBlockedModes, 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_ALL, false),
bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_MEDIA_UNLOCKED, false), bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_MEDIA_UNLOCKED, false),
bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_NAV_UNLOCKED, false), bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_NAV_UNLOCKED, false),
@@ -1728,6 +1788,100 @@ public final class SbtSettings {
return new HashSet<>(list); 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) { private static Map<String, Integer> readSystemIconBlockedModesFromPrefs(SharedPreferences prefs) {
if (prefs == null) { if (prefs == null) {
return Collections.emptyMap(); 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)); prefs.getInt(SbtSettings.KEY_BATTERY_BAR_THICKNESS_DP, SbtDefaults.BATTERY_BAR_THICKNESS_DP_DEFAULT));
out.putInt(SbtSettings.KEY_BATTERY_BAR_EDGE_OFFSET_DP, 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)); 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, out.putInt(SbtSettings.KEY_BATTERY_BAR_MIN_LEVEL,
prefs.getInt(SbtSettings.KEY_BATTERY_BAR_MIN_LEVEL, SbtDefaults.BATTERY_BAR_MIN_LEVEL_DEFAULT)); prefs.getInt(SbtSettings.KEY_BATTERY_BAR_MIN_LEVEL, SbtDefaults.BATTERY_BAR_MIN_LEVEL_DEFAULT));
out.putInt(SbtSettings.KEY_BATTERY_BAR_MAX_LEVEL, out.putInt(SbtSettings.KEY_BATTERY_BAR_MAX_LEVEL,
@@ -328,6 +334,10 @@ public class SbtSettingsProvider extends ContentProvider {
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT, SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtSettings::layoutStatusVerticalOffsetKey, SbtSettings::layoutStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT); 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, out.putInt(SbtSettings.KEY_LAYOUT_STATUS_LOCK_COUNT,
prefs.getInt(SbtSettings.KEY_LAYOUT_STATUS_LOCK_COUNT, prefs.getInt(SbtSettings.KEY_LAYOUT_STATUS_LOCK_COUNT,
prefs.getBoolean( prefs.getBoolean(
@@ -32,6 +32,7 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import se.ajpanton.statusbartweak.R; 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.BatteryBarStyle;
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults; import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings; 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); MaterialButton addThresholdButton = root.findViewById(R.id.battery_bar_add_threshold_button);
LinearLayout thresholdsContainer = root.findViewById(R.id.battery_bar_thresholds_container); LinearLayout thresholdsContainer = root.findViewById(R.id.battery_bar_thresholds_container);
View geometrySection = root.findViewById(R.id.battery_bar_geometry_section); 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.setValueFrom(0f);
mappingSlider.setValueTo(100f); mappingSlider.setValueTo(100f);
mappingSlider.setStepSize(1f); 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_MIN,
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MAX, SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_MAX,
SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_DEFAULT); SbtDefaults.BATTERY_BAR_EDGE_OFFSET_DP_DEFAULT);
renderScenarioOverrides(scenarioOverridesContainer, prefs);
setSectionEnabled(geometrySection, enabled.isChecked());
mappingSlider.addOnChangeListener((slider, value, fromUser) -> { mappingSlider.addOnChangeListener((slider, value, fromUser) -> {
if (!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, private void adjustMapping(SharedPreferences prefs,
RangeSlider slider, RangeSlider slider,
TextView minLabel, TextView minLabel,
@@ -10,9 +10,11 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable; import android.graphics.drawable.NinePatchDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -20,6 +22,7 @@ import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; 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 String QUICKSTAR_PACKAGE = "com.samsung.android.qstuner";
private static final int TOGGLE_ICON_DP = 24; 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( private static final List<SystemIconSection> SECTIONS = Arrays.asList(
new SystemIconSection( new SystemIconSection(
@@ -124,6 +129,7 @@ public class SystemIconsFragment extends Fragment {
.getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE); .getSharedPreferences(SbtSettings.PREFS_NAME, Context.MODE_PRIVATE);
SbtSettings.ensureReadable(requireContext()); SbtSettings.ensureReadable(requireContext());
Map<String, Integer> blockedModes = new HashMap<>(SbtSettings.from(requireContext()).systemIconBlockedModes); Map<String, Integer> blockedModes = new HashMap<>(SbtSettings.from(requireContext()).systemIconBlockedModes);
int leadingColumnWidth = leadingColumnWidth(requireContext());
Map<Integer, LinearLayout> sectionContainers = new HashMap<>(); Map<Integer, LinearLayout> sectionContainers = new HashMap<>();
sectionContainers.put(R.string.system_icons_group_connectivity, sectionContainers.put(R.string.system_icons_group_connectivity,
@@ -139,13 +145,189 @@ public class SystemIconsFragment extends Fragment {
continue; continue;
} }
for (SystemIconEntry entry : section.entries) { for (SystemIconEntry entry : section.entries) {
sectionContainer.addView(createRow(sectionContainer, prefs, blockedModes, entry)); sectionContainer.addView(createRow(
sectionContainer,
prefs,
blockedModes,
entry,
leadingColumnWidth));
} }
} }
applyLayoutAvailability(root, prefs); applyLayoutAvailability(root, prefs);
return root; 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) { private void applyLayoutAvailability(View root, SharedPreferences prefs) {
if (root == null || prefs == null) { if (root == null || prefs == null) {
return; return;
@@ -168,7 +350,8 @@ public class SystemIconsFragment extends Fragment {
private View createRow(ViewGroup parent, private View createRow(ViewGroup parent,
SharedPreferences prefs, SharedPreferences prefs,
Map<String, Integer> blockedModes, Map<String, Integer> blockedModes,
SystemIconEntry entry) { SystemIconEntry entry,
int leadingColumnWidth) {
Context context = parent.getContext(); Context context = parent.getContext();
LinearLayout row = new LinearLayout(context); LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL); row.setOrientation(LinearLayout.HORIZONTAL);
@@ -181,7 +364,7 @@ public class SystemIconsFragment extends Fragment {
LinearLayout modeStrip = new LinearLayout(context); LinearLayout modeStrip = new LinearLayout(context);
modeStrip.setOrientation(LinearLayout.HORIZONTAL); modeStrip.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout.LayoutParams modeStripParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams modeStripParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, leadingColumnWidth,
ViewGroup.LayoutParams.WRAP_CONTENT); ViewGroup.LayoutParams.WRAP_CONTENT);
modeStrip.setLayoutParams(modeStripParams); modeStrip.setLayoutParams(modeStripParams);
modeStrip.addView(createModeToggle(context, prefs, blockedModes, entry, modeStrip.addView(createModeToggle(context, prefs, blockedModes, entry,
@@ -202,6 +385,16 @@ public class SystemIconsFragment extends Fragment {
label.setText(context.getString(entry.labelRes)); label.setText(context.getString(entry.labelRes));
label.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1); label.setTextAppearance(com.google.android.material.R.style.TextAppearance_MaterialComponents_Body1);
row.addView(label); 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; return row;
} }
@@ -299,6 +299,45 @@
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </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 <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
+20
View File
@@ -53,6 +53,14 @@
<string name="system_icons_title">Hide status icons</string> <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_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_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_connectivity">Connectivity</string>
<string name="system_icons_group_notifications">Notifications</string> <string name="system_icons_group_notifications">Notifications</string>
<string name="system_icons_group_system">System</string> <string name="system_icons_group_system">System</string>
@@ -135,6 +143,18 @@
<string name="battery_bar_alignment_center">&#8596;</string> <string name="battery_bar_alignment_center">&#8596;</string>
<string name="battery_bar_thickness_title">Thickness (dp)</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_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_title">Battery mapping</string>
<string name="battery_bar_mapping_min_label">...0 at %1$d%%</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> <string name="battery_bar_mapping_max_label">...full at %1$d%%</string>