Complete shared AOD layout engine

This commit is contained in:
ajp_anton
2026-05-30 17:51:11 +00:00
parent 7d158cb0b1
commit 78b7d30838
40 changed files with 7250 additions and 568 deletions
@@ -17,6 +17,7 @@ import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.features.RuntimeContext;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport;
import se.ajpanton.statusbartweak.runtime.mode.SceneKey;
import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache;
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
@@ -46,27 +47,33 @@ final class StockUnlockedNotificationIconsController {
Class<?> containerClass = ReflectionSupport.requireClass(CONTAINER_CLASS_NAME, classLoader);
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "onAttachedToWindow", chain -> {
Object result = chain.proceed();
if (chain.getThisObject() instanceof View view && shouldManageContainer(view)) {
if (chain.getThisObject() instanceof View view) {
if (shouldManageContainer(view)) {
trackContainer(view);
view.post(() -> refreshContainer(view));
}
}
return result;
});
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "calculateIconXTranslations", chain -> {
if (chain.getThisObject() instanceof View view && shouldManageContainer(view)) {
if (chain.getThisObject() instanceof View view) {
if (shouldManageContainer(view)) {
trackContainer(view);
applyLimitIfNeeded(view);
}
}
return chain.proceed();
});
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "onLayout", chain -> {
Object result = chain.proceed();
if (chain.getThisObject() instanceof View view && shouldManageContainer(view)) {
if (chain.getThisObject() instanceof View view) {
if (shouldManageContainer(view)) {
trackContainer(view);
if (isFeatureEnabled(view.getContext())) {
enforceParentWidth(view);
}
}
}
return result;
});
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "shouldForceOverflow", chain -> {
@@ -236,6 +243,9 @@ final class StockUnlockedNotificationIconsController {
}
private boolean shouldManageContainer(View view) {
if (!isUnlockedScene()) {
return false;
}
if (isKeyguardContainer(view)) {
return false;
}
@@ -260,6 +270,11 @@ final class StockUnlockedNotificationIconsController {
return inUnlockedStatusBarIconArea;
}
private boolean isUnlockedScene() {
SceneKey scene = runtimeContext.getModeStateRepository().getActiveScene();
return scene != null && scene.isUnlocked();
}
private String resolveIdName(View view) {
if (view == null || view.getId() == View.NO_ID) {
return "";
@@ -265,15 +265,17 @@ public final class PackedStatusBarLayoutSolver {
neededForItem = Math.max(neededForItem, constraint.required - assumedAbsorberCapacity);
}
ArrayList<LayoutItem> groupItems = item.isIconItem()
? iconGroups.get(item.iconGroup)
: null;
double amount = Math.min(Math.max(0, neededForItem), contextualShrinkCapacity(item, item));
double directActual = item.isIconItem()
? truncateIconItemWithoutDot(item, amount)
? truncateIconItemWithoutDot(item, amount, groupItems != null && groupItems.size() > 1)
: item.truncateAtLeast(amount);
if (directActual != 0) {
adjustConstraintsForItemWidthDelta(constraints, item, directActual);
}
if (item.isIconItem()) {
ArrayList<LayoutItem> groupItems = iconGroups.get(item.iconGroup);
ArrayList<Integer> remaining = remainingIconWidths.get(item.iconGroup);
int remainingStart = remainingIconStarts.getOrDefault(item.iconGroup, 0);
ArrayList<Integer> remainingAfterItem = allocateCurrentIconItem(
@@ -336,7 +338,14 @@ public final class PackedStatusBarLayoutSolver {
return item.canShrinkBy();
}
private static double truncateIconItemWithoutDot(LayoutItem item, double amount) {
private static double truncateIconItemWithoutDot(
LayoutItem item,
double amount,
boolean allowZeroWithoutDot
) {
if (!allowZeroWithoutDot) {
return item.truncateAtLeast(amount);
}
boolean oldAllowZero = item.allowZero;
boolean oldAllowDot = item.allowDot;
item.allowZero = true;
@@ -403,8 +412,9 @@ public final class PackedStatusBarLayoutSolver {
}
ArrayList<Integer> remaining = new ArrayList<>(groupItems.get(0).iconWidths());
int start = 0;
boolean allowZeroWithoutDot = groupItems.size() > 1;
for (LayoutItem item : groupItems) {
setIconItemPool(item, remaining, start);
setIconItemPool(item, remaining, start, allowZeroWithoutDot);
int used = item.visibleIcons;
remaining = sliceFrom(remaining, used);
start += used;
@@ -420,12 +430,13 @@ public final class PackedStatusBarLayoutSolver {
ArrayList<FinalConstraint> constraints
) {
HashMap<LayoutItem, Double> oldWidths = widthsByItem(groupItems);
setIconItemPool(item, remainingWidths, remainingStart);
boolean allowZeroWithoutDot = groupItems != null && groupItems.size() > 1;
setIconItemPool(item, remainingWidths, remainingStart, allowZeroWithoutDot);
ArrayList<Integer> remainingAfterItem = sliceFrom(remainingWidths, item.visibleIcons);
int remainingAfterStart = remainingStart + item.visibleIcons;
int itemIndex = groupItems.indexOf(item);
for (int i = itemIndex + 1; i < groupItems.size(); i++) {
setIconItemPool(groupItems.get(i), remainingAfterItem, remainingAfterStart);
setIconItemPool(groupItems.get(i), remainingAfterItem, remainingAfterStart, true);
}
adjustGroupConstraintDeltas(groupItems, oldWidths, constraints);
return remainingAfterItem;
@@ -451,7 +462,17 @@ public final class PackedStatusBarLayoutSolver {
adjustGroupConstraintDeltas(groupItems, oldWidths, constraints);
}
private static void setIconItemPool(LayoutItem item, ArrayList<Integer> widths, int startIndex) {
private static void setIconItemPool(
LayoutItem item,
ArrayList<Integer> widths,
int startIndex,
boolean allowZeroWithoutDot
) {
if (!allowZeroWithoutDot) {
item.widthLimit = Math.min(item.currentWidthLimit(), item.width());
item.setPoolIcons(widths, startIndex);
return;
}
boolean oldAllowZero = item.allowZero;
boolean oldAllowDot = item.allowDot;
item.allowZero = true;
@@ -494,6 +515,12 @@ public final class PackedStatusBarLayoutSolver {
}
LayoutItem item = groupItems.get(lastIcons);
if (item.visibleIconWidths.isEmpty()) {
if (item.allowDot && item.poolRemaining > 0) {
item.dot = true;
item.visibleIcons = 0;
item.setPrimaryWidth(dotWidthForFinalPlacement(groupItems, item));
item.widthLimit = item.width();
}
return;
}
item.visibleIconWidths.remove(item.visibleIconWidths.size() - 1);
@@ -515,7 +542,7 @@ public final class PackedStatusBarLayoutSolver {
break;
}
}
if (allLaterZero) {
if (allLaterZero && item.noCollisionsOnSide(false)) {
return Math.min(dotWidth, item.dotWidth - item.dotWidth / 3.0);
}
} else {
@@ -526,7 +553,7 @@ public final class PackedStatusBarLayoutSolver {
break;
}
}
if (allEarlierZero) {
if (allEarlierZero && item.noCollisionsOnSide(true)) {
return Math.min(dotWidth, item.dotWidth - item.dotWidth / 3.0);
}
}
@@ -13,12 +13,12 @@ import java.util.Objects;
* runtime API.
*/
public final class ModeStateRepository {
private SceneKey activeScene = SceneKey.unlocked();
private boolean systemUiStatusBarStateKnown;
private int systemUiStatusBarState;
private boolean layoutEnabled;
private final List<LayoutEnabledListener> layoutEnabledListeners = new ArrayList<>();
private final List<SceneListener> sceneListeners = new ArrayList<>();
public synchronized SceneKey getActiveScene() {
return activeScene;
@@ -56,39 +56,71 @@ public final class ModeStateRepository {
}
}
public synchronized void setUnlocked() {
activeScene = SceneKey.unlocked();
public void setUnlocked() {
setActiveScene(SceneKey.unlocked());
}
public synchronized void setLockscreen(NotificationDisplayStyle style) {
activeScene = SceneKey.lockscreen(requireDisplayStyle(style));
public void setLockscreen(NotificationDisplayStyle style) {
setActiveScene(SceneKey.lockscreen(requireDisplayStyle(style)));
}
public synchronized void setAod(NotificationDisplayStyle style) {
activeScene = SceneKey.aod(requireDisplayStyle(style));
public void setAod(NotificationDisplayStyle style) {
setActiveScene(SceneKey.aod(requireDisplayStyle(style)));
}
public synchronized void setScene(SceneKey sceneKey) {
activeScene = Objects.requireNonNull(sceneKey, "sceneKey");
public void setScene(SceneKey sceneKey) {
setActiveScene(Objects.requireNonNull(sceneKey, "sceneKey"));
}
public synchronized void setSystemUiStatusBarState(
public void setSystemUiStatusBarState(
int state,
boolean dozing,
NotificationDisplayStyle notificationDisplayStyle
) {
systemUiStatusBarStateKnown = true;
systemUiStatusBarState = state;
NotificationDisplayStyle style = notificationDisplayStyle;
if (style == null || style == NotificationDisplayStyle.NONE) {
style = NotificationDisplayStyle.UNKNOWN;
}
if (dozing) {
activeScene = SceneKey.aod(style);
} else if (state == 0) {
activeScene = SceneKey.unlocked();
} else {
activeScene = SceneKey.lockscreen(style);
SceneKey scene = dozing
? SceneKey.aod(style)
: state == 0 ? SceneKey.unlocked() : SceneKey.lockscreen(style);
synchronized (this) {
systemUiStatusBarStateKnown = true;
systemUiStatusBarState = state;
}
setActiveScene(scene);
}
public synchronized void addSceneListener(SceneListener listener) {
if (listener != null && !sceneListeners.contains(listener)) {
sceneListeners.add(listener);
}
}
private void setActiveScene(SceneKey scene) {
SceneKey previous;
List<SceneListener> listeners;
synchronized (this) {
previous = activeScene;
if (previous.equals(scene)) {
return;
}
activeScene = scene;
listeners = new ArrayList<>(sceneListeners);
}
notifySceneChanged(previous, scene, listeners);
}
private void notifySceneChanged(
SceneKey previous,
SceneKey current,
List<SceneListener> listeners
) {
if (listeners == null || listeners.isEmpty()) {
return;
}
for (SceneListener listener : listeners) {
listener.onSceneChanged(previous, current);
}
}
@@ -102,4 +134,8 @@ public final class ModeStateRepository {
public interface LayoutEnabledListener {
void onLayoutEnabledChanged(boolean enabled);
}
public interface SceneListener {
void onSceneChanged(SceneKey previous, SceneKey current);
}
}
@@ -0,0 +1,151 @@
package se.ajpanton.statusbartweak.runtime.render;
import android.content.res.Resources;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayDeque;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
final class AodLiveStatusbarSource {
private AodLiveStatusbarSource() {
}
static Bounds movingBoundsForRoot(View root) {
View source = sourceForRoot(root, true);
return usableBounds(root, source);
}
static Bounds movingIconContentBoundsForRoot(View root) {
View source = sourceForRoot(root, true);
Bounds sourceBounds = usableBounds(root, source);
if (!(source instanceof ViewGroup group)) {
return sourceBounds;
}
Bounds union = null;
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (!isIconContentCandidate(child)) {
continue;
}
Bounds childBounds = usableBounds(root, child);
if (childBounds == null) {
continue;
}
union = union == null ? childBounds : union(union, childBounds);
}
return union != null ? union : sourceBounds;
}
static Bounds boundsForRoot(View root) {
View source = sourceForRoot(root, false);
return usableBounds(root, source);
}
private static View sourceForRoot(View root, boolean movingOnly) {
if (!(root instanceof ViewGroup rootGroup)) {
return null;
}
View best = null;
int bestPriority = Integer.MAX_VALUE;
ArrayDeque<View> queue = new ArrayDeque<>();
queue.add(rootGroup);
while (!queue.isEmpty()) {
View view = queue.removeFirst();
int priority = sourcePriority(view, movingOnly);
if (priority < bestPriority && usableBounds(root, view) != null) {
best = view;
bestPriority = priority;
}
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child != null) {
queue.addLast(child);
}
}
}
}
return best;
}
private static int sourcePriority(View view, boolean movingOnly) {
String idName = resolveIdName(view);
if ("common_statusbar_battery_container_internal".equals(idName)
&& view instanceof ViewGroup group
&& group.getChildCount() > 0
&& shortEnough(view)) {
return 0;
}
if (!movingOnly
&& "system_icons".equals(idName)
&& view instanceof ViewGroup group
&& group.getChildCount() > 0
&& hasAncestorId(view, "system_icons_container")
&& hasAncestorId(view, "status_icon_area")
&& shortEnough(view)) {
return 1;
}
return Integer.MAX_VALUE;
}
private static boolean shortEnough(View view) {
int height = view.getHeight() > 0 ? view.getHeight() : view.getMeasuredHeight();
return height > 0 && height <= 80;
}
private static boolean isIconContentCandidate(View view) {
if (view == null) {
return false;
}
String className = view.getClass().getName();
if (className.contains("BatteryMeterView")) {
return false;
}
return className.contains("ImageView") || className.contains("StatusBarIcon");
}
private static Bounds union(Bounds first, Bounds second) {
int left = Math.min(first.left, second.left);
int top = Math.min(first.top, second.top);
int right = Math.max(first.left + first.width, second.left + second.width);
int bottom = Math.max(first.top + first.height, second.top + second.height);
return new Bounds(left, top, right - left, bottom - top);
}
private static Bounds usableBounds(View root, View view) {
Bounds bounds = ViewGeometry.boundsRelativeTo(root, view);
if (root == null || bounds == null || bounds.width <= 0 || bounds.height <= 0) {
return null;
}
int right = bounds.left + bounds.width;
int bottom = bounds.top + bounds.height;
if (bounds.left < 0 || bounds.top < 0 || right > root.getWidth() || bottom > root.getHeight()) {
return null;
}
return bounds;
}
private static boolean hasAncestorId(View view, String idName) {
Object parent = view != null ? view.getParent() : null;
while (parent instanceof View parentView) {
if (idName.equals(resolveIdName(parentView))) {
return true;
}
parent = parentView.getParent();
}
return false;
}
private static String resolveIdName(View view) {
if (view == null || view.getId() == View.NO_ID) {
return "";
}
try {
return view.getResources().getResourceEntryName(view.getId());
} catch (Resources.NotFoundException ignored) {
return "";
}
}
}
@@ -0,0 +1,249 @@
package se.ajpanton.statusbartweak.runtime.render;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
public final class AodStatusbarSourceStore {
private static final WeakHashMap<View, StatusSourceRole> STATUS_SOURCES = new WeakHashMap<>();
public enum StatusSourceRole {
SYSTEM_ICONS,
STATUS_ICONS,
BATTERY,
UNKNOWN
}
private AodStatusbarSourceStore() {
}
public static void putStatusContainer(View view) {
putStatusSource(view);
}
public static void putStatusSource(View view) {
if (view != null) {
STATUS_SOURCES.put(view, StatusSourceRole.UNKNOWN);
}
}
public static void putStatusSource(View view, StatusSourceRole role) {
if (view != null) {
STATUS_SOURCES.put(view, role != null ? role : StatusSourceRole.UNKNOWN);
}
}
public static void replaceStatusSources(List<View> views) {
STATUS_SOURCES.clear();
if (views == null) {
return;
}
for (View view : views) {
putStatusSource(view);
}
}
public static void replaceStatusSources(
View systemIcons,
View statusIcons,
View battery
) {
STATUS_SOURCES.clear();
putStatusSource(systemIcons, StatusSourceRole.SYSTEM_ICONS);
putStatusSource(statusIcons, StatusSourceRole.STATUS_ICONS);
putStatusSource(battery, StatusSourceRole.BATTERY);
}
public static void replaceStatusContainer(View view) {
STATUS_SOURCES.clear();
putStatusSource(view);
}
public static void clear() {
STATUS_SOURCES.clear();
}
public static Bounds statusBoundsForRoot(View root, View excludedContainer) {
if (root == null) {
return null;
}
Bounds union = null;
for (View source : STATUS_SOURCES.keySet()) {
if (!isUsableSource(root, source, excludedContainer)) {
continue;
}
if (!isStatusAnchorSource(STATUS_SOURCES.get(source))) {
continue;
}
Bounds bounds = boundsForRoot(root, source);
if (!isUsableRightAnchor(root, bounds)) {
continue;
}
union = union == null ? bounds : union(union, bounds);
}
return union;
}
public static ArrayList<Bounds> statusBoundsListForRoot(View root, View excludedContainer) {
ArrayList<Bounds> result = new ArrayList<>();
if (root == null) {
return result;
}
for (View source : STATUS_SOURCES.keySet()) {
if (!isUsableSource(root, source, excludedContainer)) {
continue;
}
if (!isStatusAnchorSource(STATUS_SOURCES.get(source))) {
continue;
}
Bounds bounds = boundsForRoot(root, source);
if (isUsableRightAnchor(root, bounds)) {
result.add(bounds);
}
}
return result;
}
private static boolean isStatusAnchorSource(StatusSourceRole role) {
return role != StatusSourceRole.SYSTEM_ICONS;
}
private static Bounds union(Bounds first, Bounds second) {
if (first == null) {
return second;
}
if (second == null) {
return first;
}
int left = Math.min(first.left, second.left);
int top = Math.min(first.top, second.top);
int right = Math.max(right(first), right(second));
int bottom = Math.max(first.top + first.height, second.top + second.height);
return new Bounds(left, top, right - left, bottom - top);
}
private static boolean isUsableSource(View root, View source, View excludedContainer) {
return source != null
&& source != excludedContainer
&& !isRelated(source, excludedContainer)
&& !isNotificationContainer(source)
&& !isOwnedRuntimeView(source)
&& source.isAttachedToWindow();
}
private static Bounds boundsForRoot(View root, View view) {
Bounds localBounds = ViewGeometry.boundsRelativeTo(root, view);
if (localBounds != null) {
return clampToRoot(root, localBounds);
}
return screenBoundsRelativeTo(root, view);
}
private static Bounds screenBoundsRelativeTo(View root, View view) {
if (root == null || view == null || root.getWidth() <= 0 || root.getHeight() <= 0) {
return null;
}
int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth();
int height = view.getHeight() > 0 ? view.getHeight() : view.getMeasuredHeight();
if (width <= 0 || height <= 0) {
return null;
}
int[] rootLocation = new int[2];
int[] viewLocation = new int[2];
root.getLocationOnScreen(rootLocation);
view.getLocationOnScreen(viewLocation);
int left = viewLocation[0] - rootLocation[0];
int top = viewLocation[1] - rootLocation[1];
return clampToRoot(root, new Bounds(left, top, width, height));
}
private static Bounds clampToRoot(View root, Bounds bounds) {
if (root == null || bounds == null || root.getWidth() <= 0 || root.getHeight() <= 0) {
return null;
}
int left = Math.max(0, bounds.left);
int top = Math.max(0, bounds.top);
int right = Math.min(root.getWidth(), bounds.left + bounds.width);
int bottom = Math.min(root.getHeight(), bounds.top + bounds.height);
if (right <= left || bottom <= top) {
return null;
}
return new Bounds(left, top, right - left, bottom - top);
}
private static boolean isUsableRightAnchor(View root, Bounds bounds) {
if (root == null
|| bounds == null
|| bounds.width <= 0
|| bounds.height <= 0) {
return false;
}
int right = right(bounds);
int bottom = bounds.top + bounds.height;
return bounds.left >= 0
&& right <= root.getWidth()
&& bounds.top >= 0
&& bottom <= root.getHeight();
}
private static int right(Bounds bounds) {
return bounds.left + bounds.width;
}
static ArrayList<View> statusSourcesForRoot(View root, View excludedContainer) {
ArrayList<View> result = new ArrayList<>();
for (View source : STATUS_SOURCES.keySet()) {
if (!isUsableSource(root, source, excludedContainer)
|| !isDescendantOf(source, root)) {
continue;
}
if (!isStatusAnchorSource(STATUS_SOURCES.get(source))) {
continue;
}
result.add(source);
}
return result;
}
private static boolean isDescendantOf(View view, View root) {
View current = view;
while (current != null) {
if (current == root) {
return true;
}
Object parent = current.getParent();
current = parent instanceof View ? (View) parent : null;
}
return false;
}
private static boolean isRelated(View view, View other) {
return view != null
&& other != null
&& (isDescendantOf(view, other) || isDescendantOf(other, view));
}
private static boolean isNotificationContainer(View view) {
String className = view != null ? view.getClass().getName() : "";
return className.contains("NotificationIconsOnlyContainer")
|| className.contains("NotificationIconContainer");
}
private static boolean isOwnedRuntimeView(View view) {
View current = view;
while (current != null) {
Object tag = current.getTag();
if (tag instanceof String value && value.startsWith("statusbartweak.")) {
return true;
}
Object parent = current.getParent();
current = parent instanceof View ? (View) parent : null;
}
return false;
}
}
@@ -165,6 +165,21 @@ final class ClockLayout {
measured.height);
}
static int centeredTextBaseline(TextView source, int containerHeight) {
if (source == null || source.getText() == null || source.getText().length() == 0) {
return 0;
}
TextView probe = new TextView(source.getContext());
probe.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
ClockProxyRenderer.copyTextStyleIfChanged(source, probe);
MeasuredText measured = measureText(probe, source.getText());
int availableHeight = containerHeight > 0 ? containerHeight : measured.height;
int extraHeight = Math.max(0, availableHeight - measured.height);
return (extraHeight / 2) + measured.baseline;
}
private static ArrayList<MeasuredClockRow> positionRowsFromBaseline(
ArrayList<MeasuredClockRow> measuredRows,
int sourceBaselineY,
@@ -19,6 +19,8 @@ import java.util.Objects;
import se.ajpanton.statusbartweak.R;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver;
import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle;
import se.ajpanton.statusbartweak.runtime.mode.SceneKey;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
public final class DebugBoundsOverlayController {
@@ -31,8 +33,17 @@ public final class DebugBoundsOverlayController {
private static final int COLOR_CLOCK = Color.argb(96, 64, 220, 96);
private static final int COLOR_CHIP = Color.argb(112, 255, 0, 220);
private static final int COLOR_CUTOUT = Color.argb(112, 255, 180, 0);
private static final int COLOR_AOD_DERIVED_STATUSBAR = Color.argb(80, 255, 220, 0);
public boolean hasEnabledVisualizer(boolean layoutEnabled, SbtSettings settings) {
return hasEnabledVisualizer(layoutEnabled, settings, true);
}
public boolean hasEnabledVisualizer(
boolean layoutEnabled,
SbtSettings settings,
boolean aodStockVisualizersEnabled
) {
if (settings == null) {
return false;
}
@@ -41,21 +52,44 @@ public final class DebugBoundsOverlayController {
&& (settings.debugVisualizeNotificationContainer
|| settings.debugVisualizeStatusContainer
|| settings.debugVisualizeClockContainer
|| settings.debugVisualizeChipContainer));
|| settings.debugVisualizeChipContainer
|| (aodStockVisualizersEnabled && settings.debugVisualizeAodStockContainers)));
}
public void apply(View root, boolean layoutEnabled, SbtSettings settings) {
apply(root, layoutEnabled, settings, true);
}
public void apply(
View root,
boolean layoutEnabled,
SbtSettings settings,
boolean aodStockVisualizersEnabled
) {
apply(root, layoutEnabled, settings, aodStockVisualizersEnabled, null);
}
public void apply(
View root,
boolean layoutEnabled,
SbtSettings settings,
boolean aodStockVisualizersEnabled,
SceneKey scene
) {
if (!(root instanceof ViewGroup parent) || settings == null) {
return;
}
if (!hasEnabledVisualizer(layoutEnabled, settings)) {
removeOverlay(parent);
if (!hasEnabledVisualizer(layoutEnabled, settings, aodStockVisualizersEnabled)) {
removeOverlays(parent);
return;
}
ArrayList<OverlaySpec> specs = new ArrayList<>();
if (settings.debugVisualizeCameraCutout) {
addCutoutSpecs(root, specs);
}
if (layoutEnabled && aodStockVisualizersEnabled && settings.debugVisualizeAodStockContainers) {
addAodSourceSpecs(root, settings, specs, scene);
}
if (layoutEnabled && settings.debugVisualizeNotificationContainer) {
addHostContainerSpecs(
root,
@@ -84,10 +118,12 @@ public final class DebugBoundsOverlayController {
specs);
}
if (specs.isEmpty()) {
removeOverlay(parent);
removeOverlays(parent);
return;
}
OverlayView host = ensureOverlay(parent);
ViewGroup overlayParent = overlayParent(root, parent, layoutEnabled, aodStockVisualizersEnabled, settings);
removeOverlayFromOtherParent(parent, overlayParent);
OverlayView host = ensureOverlay(overlayParent);
if (host == null) {
return;
}
@@ -100,17 +136,183 @@ public final class DebugBoundsOverlayController {
host.layout(0, 0, width, height);
}
host.setSpecs(specs);
if (parent.getChildAt(parent.getChildCount() - 1) != host) {
if (overlayParent.getChildAt(overlayParent.getChildCount() - 1) != host) {
host.bringToFront();
}
}
public int sourceSignature(View root, boolean layoutEnabled, SbtSettings settings) {
return sourceSignature(root, layoutEnabled, settings, true);
}
public int sourceSignature(
View root,
boolean layoutEnabled,
SbtSettings settings,
boolean aodStockVisualizersEnabled
) {
return sourceSignature(root, layoutEnabled, settings, aodStockVisualizersEnabled, null);
}
public int sourceSignature(
View root,
boolean layoutEnabled,
SbtSettings settings,
boolean aodStockVisualizersEnabled,
SceneKey scene
) {
if (!layoutEnabled || root == null || settings == null
|| !aodStockVisualizersEnabled
|| !settings.debugVisualizeAodStockContainers) {
return 0;
}
int result = 17;
ViewGroup notificationContainer = aodNotificationContainer(root, scene);
result = 31 * result + boundsSignature(
aodNotificationBounds(root, scene));
result = 31 * result + boundsSignature(liveAodStatusIconsBounds(root));
result = 31 * result + boundsSignature(
derivedAodStatusbarBounds(root, notificationContainer, settings, scene));
return result;
}
public void remove(View root) {
if (root instanceof ViewGroup parent) {
removeOverlay(parent);
removeOverlays(parent);
}
}
public void bringToFront(View root) {
if (!(root instanceof ViewGroup parent)) {
return;
}
bringOverlayToFront(parent);
bringOverlayToFront(directTaggedViewGroup(parent, OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST));
bringOverlayToFront(directTaggedViewGroup(parent, OwnedIconHostManager.TAG_UNLOCKED_NOTIFICATION_HOST));
bringOverlayToFront(directTaggedViewGroup(parent, OwnedIconHostManager.TAG_UNLOCKED_CLOCK_HOST));
bringOverlayToFront(directTaggedViewGroup(parent, OwnedIconHostManager.TAG_UNLOCKED_CARRIER_HOST));
bringOverlayToFront(directTaggedViewGroup(parent, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST));
}
private boolean addAodSourceSpecs(
View root,
SbtSettings settings,
ArrayList<OverlaySpec> specs,
SceneKey scene
) {
if (root == null || settings == null || specs == null) {
return false;
}
boolean added = false;
ViewGroup notificationContainer = aodNotificationContainer(root, scene);
Bounds derivedStatusbar = derivedAodStatusbarBounds(root, notificationContainer, settings, scene);
if (derivedStatusbar != null) {
specs.add(new OverlaySpec(derivedStatusbar, COLOR_AOD_DERIVED_STATUSBAR, false));
added = true;
}
added |= addBoundsSpec(
aodNotificationBounds(root, scene),
COLOR_NOTIFICATION,
true,
specs);
added |= addBoundsSpec(liveAodStatusIconsBounds(root), COLOR_STATUS, true, specs);
return added;
}
private boolean addBoundsSpec(Bounds bounds, int color, boolean fill, ArrayList<OverlaySpec> specs) {
if (bounds == null || bounds.width <= 0 || bounds.height <= 0 || specs == null) {
return false;
}
specs.add(new OverlaySpec(bounds, color, fill));
return true;
}
private Bounds derivedAodStatusbarBounds(
View root,
View notificationContainer,
SbtSettings settings,
SceneKey scene
) {
Bounds notificationBounds = aodNotificationBounds(root, scene);
Bounds statusBounds = liveAodStatusIconsBounds(root);
if (!isUsableAodAnchor(root, statusBounds)) {
statusBounds = overlayBounds(AodStatusbarSourceStore.statusBoundsForRoot(
root,
notificationContainer));
}
boolean hasNotification = isUsableAodAnchor(root, notificationBounds);
boolean hasStatus = isUsableAodAnchor(root, statusBounds);
if (!hasNotification && !hasStatus) {
return null;
}
int left = hasNotification ? notificationBounds.left : 0;
int right = hasStatus ? statusBounds.left + statusBounds.width : root.getWidth();
left += settings != null ? settings.layoutPaddingLeftPx : 0;
right -= settings != null ? settings.layoutPaddingRightPx : 0;
int notificationBottom = hasNotification
? notificationBounds.top + notificationBounds.height
: 0;
int statusBottom = hasStatus ? statusBounds.top + statusBounds.height : 0;
int bottom = Math.max(notificationBottom, statusBottom);
if (right <= left || bottom <= 0) {
return null;
}
return new Bounds(left, 0, right - left, bottom);
}
private Bounds aodNotificationBounds(View root, SceneKey scene) {
if (scene != null
&& scene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS) {
return null;
}
return overlayBounds(
LockscreenCardsNotificationIconSourceStore.sourceContainerBoundsForAodIconsRoot(root));
}
private ViewGroup aodNotificationContainer(View root, SceneKey scene) {
if (scene != null
&& scene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS) {
return LockscreenCardsNotificationIconSourceStore.sourceContainerForRoot(root);
}
return LockscreenCardsNotificationIconSourceStore.sourceContainerForAodIconsRoot(root);
}
private boolean isUsableAodAnchor(View root, Bounds bounds) {
if (root == null || bounds == null || bounds.width <= 0 || bounds.height <= 0) {
return false;
}
int right = bounds.left + bounds.width;
int bottom = bounds.top + bounds.height;
return bounds.left >= 0
&& bounds.top >= 0
&& right <= root.getWidth()
&& bottom <= root.getHeight();
}
private int boundsSignature(Bounds bounds) {
if (bounds == null) {
return 0;
}
int result = 17;
result = 31 * result + bounds.left;
result = 31 * result + bounds.top;
result = 31 * result + bounds.width;
result = 31 * result + bounds.height;
return result;
}
private Bounds overlayBounds(StatusBarLayoutSolver.Bounds bounds) {
if (bounds == null) {
return null;
}
return new Bounds(bounds.left, bounds.top, bounds.width, bounds.height);
}
private Bounds liveAodStatusIconsBounds(View root) {
StatusBarLayoutSolver.Bounds bounds = AodLiveStatusbarSource.boundsForRoot(root);
return bounds != null ? new Bounds(bounds.left, bounds.top, bounds.width, bounds.height) : null;
}
private void addCutoutSpecs(View root, ArrayList<OverlaySpec> specs) {
WindowInsets insets = root.getRootWindowInsets();
DisplayCutout cutout = insets != null ? insets.getDisplayCutout() : null;
@@ -252,13 +454,94 @@ public final class DebugBoundsOverlayController {
return host;
}
private ViewGroup overlayParent(
View root,
ViewGroup rootParent,
boolean layoutEnabled,
boolean aodStockVisualizersEnabled,
SbtSettings settings
) {
if (layoutEnabled
&& aodStockVisualizersEnabled
&& settings != null
&& settings.debugVisualizeAodStockContainers) {
View chipHost = findDirectTaggedChild(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST);
if (chipHost instanceof ViewGroup chipHostGroup) {
return chipHostGroup;
}
View statusHost = findDirectTaggedChild(rootParent, OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST);
if (statusHost instanceof ViewGroup statusHostGroup) {
return statusHostGroup;
}
}
return rootParent;
}
private void removeOverlayFromOtherParent(ViewGroup rootParent, ViewGroup overlayParent) {
if (rootParent == null) {
return;
}
removeOverlayIfParentDiffers(rootParent, overlayParent);
removeOverlayIfParentDiffers(
directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST),
overlayParent);
removeOverlayIfParentDiffers(
directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_NOTIFICATION_HOST),
overlayParent);
removeOverlayIfParentDiffers(
directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CLOCK_HOST),
overlayParent);
removeOverlayIfParentDiffers(
directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CARRIER_HOST),
overlayParent);
removeOverlayIfParentDiffers(
directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST),
overlayParent);
}
private void removeOverlays(ViewGroup rootParent) {
if (rootParent == null) {
return;
}
removeOverlay(rootParent);
removeOverlay(directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST));
removeOverlay(directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_NOTIFICATION_HOST));
removeOverlay(directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CLOCK_HOST));
removeOverlay(directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CARRIER_HOST));
removeOverlay(directTaggedViewGroup(rootParent, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST));
}
private void removeOverlayIfParentDiffers(ViewGroup parent, ViewGroup expectedParent) {
if (parent != null && parent != expectedParent) {
removeOverlay(parent);
}
}
private void removeOverlay(ViewGroup parent) {
if (parent == null) {
return;
}
View existing = findDirectTaggedChild(parent, TAG_OVERLAY_HOST);
if (existing != null) {
parent.removeView(existing);
}
}
private void bringOverlayToFront(ViewGroup parent) {
if (parent == null || parent.getChildCount() == 0) {
return;
}
View existing = findDirectTaggedChild(parent, TAG_OVERLAY_HOST);
if (existing != null && parent.getChildAt(parent.getChildCount() - 1) != existing) {
existing.bringToFront();
}
}
private ViewGroup directTaggedViewGroup(ViewGroup parent, String tag) {
View view = findDirectTaggedChild(parent, tag);
return view instanceof ViewGroup group ? group : null;
}
private View findDirectTaggedChild(ViewGroup parent, String tag) {
if (parent == null || tag == null) {
return null;
@@ -352,7 +635,10 @@ public final class DebugBoundsOverlayController {
return Math.max(min, Math.min(max, value));
}
private record OverlaySpec(Bounds bounds, int color) {
private record OverlaySpec(Bounds bounds, int color, boolean fill) {
OverlaySpec(Bounds bounds, int color) {
this(bounds, color, true);
}
}
private record Bounds(int left, int top, int width, int height) {
@@ -389,6 +675,8 @@ public final class DebugBoundsOverlayController {
for (OverlaySpec spec : specs) {
Bounds bounds = spec.bounds;
paint.setColor(spec.color);
paint.setStyle(spec.fill ? Paint.Style.FILL : Paint.Style.STROKE);
paint.setStrokeWidth(Math.max(1f, getResources().getDisplayMetrics().density));
canvas.drawRect(
bounds.left,
bounds.top,
@@ -2,6 +2,7 @@ package se.ajpanton.statusbartweak.runtime.render;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.Collections;
@@ -11,10 +12,17 @@ import java.util.Set;
import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
import se.ajpanton.statusbartweak.shell.settings.AppIconRules;
public final class LockscreenCardsNotificationIconSourceStore {
private static final WeakHashMap<ViewGroup, ArrayList<SourceEntry>> SOURCES_BY_CONTAINER =
new WeakHashMap<>();
private static final WeakHashMap<ViewGroup, ArrayList<String>> PACKAGES_BY_CONTAINER =
new WeakHashMap<>();
private static final WeakHashMap<ViewGroup, ArrayList<View>> AOD_DRAWABLES_BY_CONTAINER =
new WeakHashMap<>();
private static final int VIRTUAL_ICON_SIZE_PX = 39;
private LockscreenCardsNotificationIconSourceStore() {
}
@@ -41,12 +49,114 @@ public final class LockscreenCardsNotificationIconSourceStore {
}
}
public static boolean putPackages(ViewGroup container, ArrayList<String> packages) {
if (container == null || packages == null || packages.isEmpty()) {
if (container != null) {
return PACKAGES_BY_CONTAINER.remove(container) != null;
}
return false;
}
ArrayList<String> valid = new ArrayList<>();
boolean hasValid = false;
for (String pkg : packages) {
if (AppIconRules.isValidPackageName(pkg)) {
valid.add(pkg);
hasValid = true;
} else {
valid.add(null);
}
}
if (!hasValid) {
return PACKAGES_BY_CONTAINER.remove(container) != null;
} else {
ArrayList<String> previous = PACKAGES_BY_CONTAINER.get(container);
if (previous != null && previous.equals(valid)) {
return false;
}
PACKAGES_BY_CONTAINER.put(container, valid);
return true;
}
}
public static boolean putAodDrawables(ViewGroup container, ArrayList<? extends View> views) {
if (container == null || views == null || views.isEmpty()) {
if (container != null) {
return AOD_DRAWABLES_BY_CONTAINER.remove(container) != null;
}
return false;
}
ArrayList<View> valid = new ArrayList<>();
Set<View> seen = Collections.newSetFromMap(new IdentityHashMap<>());
for (View view : views) {
if (!(view instanceof ImageView imageView)
|| imageView.getDrawable() == null
|| !seen.add(view)) {
continue;
}
valid.add(view);
}
if (valid.isEmpty()) {
return AOD_DRAWABLES_BY_CONTAINER.remove(container) != null;
} else {
ArrayList<View> previous = AOD_DRAWABLES_BY_CONTAINER.get(container);
if (sameViews(previous, valid)) {
return false;
}
AOD_DRAWABLES_BY_CONTAINER.put(container, valid);
return true;
}
}
public static void clearAodSources() {
PACKAGES_BY_CONTAINER.clear();
AOD_DRAWABLES_BY_CONTAINER.clear();
}
public static void clearAodSources(ViewGroup container) {
if (container == null) {
return;
}
PACKAGES_BY_CONTAINER.remove(container);
AOD_DRAWABLES_BY_CONTAINER.remove(container);
}
private static boolean sameViews(ArrayList<View> previous, ArrayList<View> current) {
if (previous == current) {
return true;
}
if (previous == null || current == null || previous.size() != current.size()) {
return false;
}
for (int i = 0; i < previous.size(); i++) {
if (previous.get(i) != current.get(i)) {
return false;
}
}
return true;
}
static ArrayList<SnapshotSource> snapshotSources(ViewGroup container, View root) {
ArrayList<SourceEntry> views = SOURCES_BY_CONTAINER.get(container);
ArrayList<SnapshotSource> result = new ArrayList<>();
if (views == null || views.isEmpty()) {
ArrayList<View> aodDrawables = AOD_DRAWABLES_BY_CONTAINER.get(container);
if (aodDrawables != null && !aodDrawables.isEmpty()) {
ArrayList<String> packages = PACKAGES_BY_CONTAINER.get(container);
Bounds containerBounds = sourceBoundsRelativeTo(root, container);
int top = containerBounds != null ? containerBounds.top : 0;
int size = VIRTUAL_ICON_SIZE_PX;
for (int i = 0; i < aodDrawables.size(); i++) {
View view = aodDrawables.get(i);
result.add(new SnapshotSource(
view,
SnapshotMode.NOTIFICATION_DRAWABLE,
aodDrawableKey(packages, i, view),
new Bounds(0, top, size, size)));
}
if (!result.isEmpty()) {
return result;
}
}
ArrayList<SourceEntry> views = SOURCES_BY_CONTAINER.get(container);
if (views != null && !views.isEmpty()) {
int skipCards = visibleShelfStartIndex(container, views);
HashSet<String> visibleCardKeys = visibleCardKeys(root, container);
for (int i = 0; i < views.size(); i++) {
@@ -68,8 +178,202 @@ public final class LockscreenCardsNotificationIconSourceStore {
}
result.add(new SnapshotSource(view, SnapshotMode.NOTIFICATION_DRAWABLE, entry.key));
}
}
if (!result.isEmpty()) {
return result;
}
ArrayList<String> packages = PACKAGES_BY_CONTAINER.get(container);
if (packages != null) {
Bounds containerBounds = sourceBoundsRelativeTo(root, container);
int top = containerBounds != null ? containerBounds.top : 0;
int size = VIRTUAL_ICON_SIZE_PX;
for (int i = 0; i < packages.size(); i++) {
String pkg = packages.get(i);
if (!AppIconRules.isValidPackageName(pkg)) {
continue;
}
result.add(new SnapshotSource(
null,
SnapshotMode.APP_ICON,
pkg + "@aod" + i,
new Bounds(0, top, size, size)));
}
}
return result;
}
private static String aodDrawableKey(ArrayList<String> packages, int index, View view) {
if (packages != null && index >= 0 && index < packages.size()) {
String pkg = packages.get(index);
if (AppIconRules.isValidPackageName(pkg)) {
return pkg + "@aod" + index;
}
}
return "statusbartweak:aod_drawable@" + index + "@0x"
+ Integer.toHexString(System.identityHashCode(view));
}
static ArrayList<SnapshotSource> snapshotSourcesForRoot(View root) {
ArrayList<SnapshotSource> result = new ArrayList<>();
if (!(root instanceof ViewGroup rootGroup)) {
return result;
}
ArrayList<View> stack = new ArrayList<>();
stack.add(rootGroup);
for (int i = 0; i < stack.size(); i++) {
View view = stack.get(i);
if (view instanceof ViewGroup group) {
if (hasSources(group)) {
result = snapshotSources(group, root);
if (!result.isEmpty()) {
return result;
}
}
for (int child = 0; child < group.getChildCount(); child++) {
View childView = group.getChildAt(child);
if (childView != null) {
stack.add(childView);
}
}
}
}
return result;
}
static ArrayList<SnapshotSource> snapshotSourcesForAodIconsRoot(View root) {
ArrayList<SnapshotSource> result = new ArrayList<>();
ViewGroup container = sourceContainerForAodIconsRoot(root);
if (container != null) {
result = snapshotSources(container, root);
}
return result;
}
static ViewGroup sourceContainerForRoot(View root) {
ViewGroup container = sourceContainerForRoot(root, AOD_DRAWABLES_BY_CONTAINER.keySet());
if (container != null) {
return container;
}
container = sourceContainerForRoot(root, SOURCES_BY_CONTAINER.keySet());
if (container != null) {
return container;
}
return sourceContainerForRoot(root, PACKAGES_BY_CONTAINER.keySet());
}
static ViewGroup sourceContainerForAodIconsRoot(View root) {
ViewGroup container = sourceContainerForRoot(
root,
AOD_DRAWABLES_BY_CONTAINER.keySet(),
true);
if (container != null) {
return container;
}
return sourceContainerForRoot(root, PACKAGES_BY_CONTAINER.keySet(), true);
}
public static Bounds sourceContainerBoundsForRoot(View root) {
ViewGroup container = sourceContainerForRoot(root);
Bounds bounds = sourceBoundsRelativeTo(root, container);
if (isZeroSizeDotContainer(container, bounds)) {
return new Bounds(bounds.left, bounds.top, VIRTUAL_ICON_SIZE_PX, VIRTUAL_ICON_SIZE_PX);
}
return bounds;
}
public static Bounds sourceContainerBoundsForAodIconsRoot(View root) {
ViewGroup container = sourceContainerForAodIconsRoot(root);
Bounds bounds = sourceBoundsRelativeTo(root, container);
if (isZeroSizeDotContainer(container, bounds)) {
return new Bounds(bounds.left, bounds.top, VIRTUAL_ICON_SIZE_PX, VIRTUAL_ICON_SIZE_PX);
}
return bounds;
}
private static Bounds sourceBoundsRelativeTo(View root, ViewGroup container) {
Bounds bounds = container != null ? ViewGeometry.boundsRelativeTo(root, container) : null;
if (bounds == null || isZeroSizeDotContainer(container, bounds)) {
return bounds;
}
if (!hasAodSources(container) || bounds.height <= 0) {
return bounds;
}
// Samsung's AOD icon-only container can temporarily expand vertically;
// downstream layout cares about the icon row, not that outer transition box.
int centerY = bounds.top + bounds.height / 2;
int top = centerY - VIRTUAL_ICON_SIZE_PX / 2;
return new Bounds(bounds.left, top, bounds.width, VIRTUAL_ICON_SIZE_PX);
}
private static boolean hasAodSources(ViewGroup container) {
return container != null
&& (AOD_DRAWABLES_BY_CONTAINER.containsKey(container)
|| PACKAGES_BY_CONTAINER.containsKey(container));
}
private static boolean isZeroSizeDotContainer(ViewGroup container, Bounds bounds) {
if (container == null
|| bounds == null
|| bounds.width > 0
|| bounds.height > 0) {
return false;
}
return "notification_dot_container".equals(resolveIdName(container));
}
private static ViewGroup sourceContainerForRoot(
View root,
Set<ViewGroup> containers
) {
return sourceContainerForRoot(root, containers, false);
}
private static ViewGroup sourceContainerForRoot(
View root,
Set<ViewGroup> containers,
boolean iconOnlyContainerRequired
) {
if (root == null || containers == null || containers.isEmpty()) {
return null;
}
for (ViewGroup container : containers) {
if (container != null
&& container.isAttachedToWindow()
&& isDescendantOf(container, root)
&& (!iconOnlyContainerRequired || isAodIconOnlyContainer(container))) {
return container;
}
}
return null;
}
private static boolean isAodIconOnlyContainer(View view) {
if (view == null) {
return false;
}
String idName = resolveIdName(view);
return "keyguard_icononly_container_view".equals(idName)
|| "notification_icononly_container".equals(idName)
|| "notification_icononly_area".equals(idName);
}
private static boolean isDescendantOf(View view, View root) {
View current = view;
while (current != null) {
if (current == root) {
return true;
}
Object parent = current.getParent();
current = parent instanceof View ? (View) parent : null;
}
return false;
}
private static boolean hasSources(ViewGroup container) {
return SOURCES_BY_CONTAINER.containsKey(container)
|| AOD_DRAWABLES_BY_CONTAINER.containsKey(container)
|| PACKAGES_BY_CONTAINER.containsKey(container);
}
private static HashSet<String> visibleCardKeys(View root, View shelf) {
HashSet<String> result = new HashSet<>();
@@ -30,6 +30,7 @@ public final class LockscreenCardsNotificationStripRenderer {
private final SnapshotRenderer renderer = new SnapshotRenderer(false, false, false);
private final WeakHashMap<ViewGroup, ArrayList<View>> pillViewsByHost = new WeakHashMap<>();
private final WeakHashMap<View, Integer> aodInitialStatusbarHeightByRoot = new WeakHashMap<>();
private final NotificationSeenAppReporter seenAppReporter = new NotificationSeenAppReporter();
public RenderResult render(
@@ -38,28 +39,43 @@ public final class LockscreenCardsNotificationStripRenderer {
SbtSettings settings,
SceneKey scene
) {
if (root != null && (scene == null || !scene.isAod())) {
aodInitialStatusbarHeightByRoot.remove(root);
}
if (root == null
|| host == null
|| settings == null
|| scene == null
|| !scene.isLockscreen()
|| scene.notificationDisplayStyle() != NotificationDisplayStyle.CARDS
|| settings.cardsLockMaxRows <= 0) {
|| !(scene.isLockscreen() || scene.isAod())
|| maxRows(settings, scene) <= 0) {
clear(host);
return RenderResult.empty();
}
ViewGroup sourceRoot = findSourceRoot(root);
if (sourceRoot == null) {
ViewGroup sourceRoot = scene.isAod()
? LockscreenCardsNotificationIconSourceStore.sourceContainerForRoot(root)
: findSourceRoot(root);
if (sourceRoot == null && scene.isAod()) {
sourceRoot = findSourceRoot(root);
}
if (sourceRoot == null && !scene.isAod()) {
clear(host);
return RenderResult.empty();
}
if (hasHiddenShelfAncestor(sourceRoot, root)) {
if (!scene.isAod() && hasHiddenShelfAncestor(sourceRoot, root)) {
clear(host);
return RenderResult.empty();
}
if (sourceRoot != null) {
syncHostAnimation(host, sourceRoot);
ArrayList<SnapshotSource> sources = LockscreenCardsNotificationIconSourceStore.snapshotSources(sourceRoot, root);
if (sources.isEmpty()) {
}
ArrayList<SnapshotSource> sources = scene.isAod()
? LockscreenCardsNotificationIconSourceStore.snapshotSourcesForRoot(root)
: new ArrayList<>();
if (sources.isEmpty() && sourceRoot != null) {
sources = LockscreenCardsNotificationIconSourceStore.snapshotSources(sourceRoot, root);
}
if (sources.isEmpty() && sourceRoot != null) {
sources = collectSources(sourceRoot);
}
if (sources.isEmpty()) {
@@ -72,8 +88,24 @@ public final class LockscreenCardsNotificationStripRenderer {
unfilteredPlacements,
settings,
scene);
StripLayout layout = layoutPlacements(root, sourceRoot, placements, settings);
renderPills(host, layout.rowBounds, resolvePillColor(sourceRoot));
Bounds sourceBounds = scene.isAod()
? LockscreenCardsNotificationIconSourceStore.sourceContainerBoundsForRoot(root)
: null;
if (sourceBounds == null && sourceRoot != null) {
sourceBounds = ViewGeometry.boundsRelativeTo(root, sourceRoot);
}
int statusbarHeight = statusbarHeight(root, sourceBounds, scene);
int iconHeight = iconHeightPx(
statusbarHeight,
cardsIconHeightSteps(settings, scene),
representativeHeight(placements));
StripLayout layout = layoutPlacements(root, sourceBounds, placements, settings, scene, iconHeight);
if (scene.isAod()) {
clearPills(host);
} else {
renderPills(host, layout.rowBounds,
sourceRoot != null ? resolvePillColor(sourceRoot) : DEFAULT_PILL_COLOR);
}
Bounds renderedBounds = renderer.renderPlacements(host, layout.placements);
return new RenderResult(sourceRoot, renderedBounds);
}
@@ -85,6 +117,12 @@ public final class LockscreenCardsNotificationStripRenderer {
public void clearAll() {
renderer.clearAll();
ArrayList<ViewGroup> hosts = new ArrayList<>(pillViewsByHost.keySet());
for (ViewGroup host : hosts) {
clearPills(host);
}
pillViewsByHost.clear();
aodInitialStatusbarHeightByRoot.clear();
}
private ViewGroup findSourceRoot(View root) {
@@ -172,17 +210,19 @@ public final class LockscreenCardsNotificationStripRenderer {
private StripLayout layoutPlacements(
View root,
View sourceRoot,
Bounds sourceBounds,
ArrayList<SnapshotPlacement> placements,
SbtSettings settings
SbtSettings settings,
SceneKey scene,
int iconHeight
) {
ArrayList<SnapshotPlacement> result = new ArrayList<>();
ArrayList<Bounds> rowBounds = new ArrayList<>();
if (placements == null || placements.isEmpty()) {
return new StripLayout(result, rowBounds);
}
int maxPerRow = Math.max(1, settings.cardsLockMaxIconsPerRow);
int maxRows = Math.max(0, settings.cardsLockMaxRows);
int maxPerRow = Math.max(1, maxIconsPerRow(settings, scene));
int maxRows = Math.max(0, maxRows(settings, scene));
int capacity = maxPerRow * maxRows;
if (capacity <= 0) {
return new StripLayout(result, rowBounds);
@@ -190,18 +230,16 @@ public final class LockscreenCardsNotificationStripRenderer {
boolean showDot = placements.size() > capacity;
int iconSlots = showDot ? Math.max(0, capacity - 1) : Math.min(capacity, placements.size());
int visible = iconSlots + (showDot ? 1 : 0);
int iconHeight = representativeHeight(placements);
int iconWidth = representativeWidth(placements, iconHeight);
Bounds sourceBounds = ViewGeometry.boundsRelativeTo(root, sourceRoot);
int iconWidth = iconHeight;
int top = sourceBounds != null ? sourceBounds.top : placements.get(0).bounds.top;
int centerX = sourceBounds != null
? sourceBounds.left + sourceBounds.width / 2
: root.getWidth() / 2;
int availableWidth = settings.cardsLockLimitToWidth
int availableWidth = limitToWidth(settings, scene)
? Math.max(0, root.getWidth())
: Integer.MAX_VALUE;
int index = 0;
int[] rowCounts = rowCounts(visible, maxPerRow, maxRows, settings.cardsLockEvenDistribution);
int[] rowCounts = rowCounts(visible, maxPerRow, maxRows, evenDistribution(settings, scene));
int dotColor = dotColor(placements);
int rowGap = dp(root, 8);
for (int row = 0; row < rowCounts.length && index < visible; row++) {
@@ -236,6 +274,30 @@ public final class LockscreenCardsNotificationStripRenderer {
return new StripLayout(result, rowBounds);
}
private int maxIconsPerRow(SbtSettings settings, SceneKey scene) {
return scene != null && scene.isAod()
? settings.cardsAodMaxIconsPerRow
: settings.cardsLockMaxIconsPerRow;
}
private int maxRows(SbtSettings settings, SceneKey scene) {
return scene != null && scene.isAod()
? settings.cardsAodMaxRows
: settings.cardsLockMaxRows;
}
private boolean evenDistribution(SbtSettings settings, SceneKey scene) {
return scene != null && scene.isAod()
? settings.cardsAodEvenDistribution
: settings.cardsLockEvenDistribution;
}
private boolean limitToWidth(SbtSettings settings, SceneKey scene) {
return scene != null && scene.isAod()
? settings.cardsAodLimitToWidth
: settings.cardsLockLimitToWidth;
}
private int[] rowCounts(
int slots,
int maxPerRow,
@@ -459,15 +521,6 @@ public final class LockscreenCardsNotificationStripRenderer {
return params;
}
private int representativeWidth(ArrayList<SnapshotPlacement> placements, int fallback) {
for (SnapshotPlacement placement : placements) {
if (placement != null && placement.bounds != null && placement.bounds.width > 0) {
return placement.bounds.width;
}
}
return Math.max(1, fallback);
}
private int representativeHeight(ArrayList<SnapshotPlacement> placements) {
for (SnapshotPlacement placement : placements) {
if (placement != null && placement.bounds != null && placement.bounds.height > 0) {
@@ -477,6 +530,50 @@ public final class LockscreenCardsNotificationStripRenderer {
return 1;
}
private int cardsIconHeightSteps(SbtSettings settings, SceneKey scene) {
return scene != null && scene.isAod()
? settings.cardsAodIconHeightSteps
: settings.cardsLockIconHeightSteps;
}
private int iconHeightPx(int statusbarHeight, int steps, int fallbackHeight) {
if (statusbarHeight <= 0) {
return Math.max(1, fallbackHeight);
}
return Math.max(1, Math.round((float) statusbarHeight * Math.max(1, steps) / 100f));
}
private int statusbarHeight(View root, Bounds sourceBounds, SceneKey scene) {
int sharedHeight = StatusbarHeightStore.get(root);
if (sharedHeight > 0) {
return sharedHeight;
}
Bounds statusBounds = StatusbarHeightSource.bounds(root, scene, null);
if (statusBounds != null && statusBounds.top >= 0 && statusBounds.height > 0) {
int height = Math.max(1, statusBounds.top + statusBounds.height);
if (scene != null && scene.isAod() && root != null) {
Integer stored = aodInitialStatusbarHeightByRoot.get(root);
if (stored != null && stored > 0) {
return stored;
}
aodInitialStatusbarHeightByRoot.put(root, height);
} else {
StatusbarHeightStore.put(root, height);
}
return height;
}
if (scene != null && scene.isAod() && root != null) {
Integer stored = aodInitialStatusbarHeightByRoot.get(root);
if (stored != null && stored > 0) {
return stored;
}
}
if (sourceBounds != null && sourceBounds.top >= 0 && sourceBounds.height > 0) {
return Math.max(1, sourceBounds.top + sourceBounds.height);
}
return Math.max(1, root != null ? root.getHeight() : 1);
}
private boolean isShelfIconContainer(View view) {
String className = view.getClass().getName();
return className.contains("SecShelfNotificationIconContainer");
@@ -6,24 +6,34 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class NotificationSeenAppReporter {
private static final long MIN_REPORT_INTERVAL_MS = 500L;
private static final long MIN_REPORT_INTERVAL_MS = 60_000L;
private final Map<String, Long> lastReportedMs = new HashMap<>();
private final Map<String, HashSet<String>> activeKeysByPackage = new HashMap<>();
void report(Context context, ArrayList<SnapshotPlacement> placements) {
if (context == null || placements == null || placements.isEmpty()) {
activeKeysByPackage.clear();
return;
}
long now = System.currentTimeMillis();
HashMap<String, HashSet<String>> currentKeysByPackage = new HashMap<>();
HashSet<String> reportedThisPass = new HashSet<>();
for (SnapshotPlacement placement : placements) {
String packageName = SnapshotIconFilter.notificationPackage(
placement != null ? placement.key : null);
if (packageName == null || !reportedThisPass.add(packageName)) {
String key = placement != null ? placement.key : null;
String packageName = SnapshotIconFilter.notificationPackage(key);
if (packageName == null || key == null) {
continue;
}
currentKeysByPackage
.computeIfAbsent(packageName, ignored -> new HashSet<>())
.add(key);
if (wasAlreadyActive(packageName, key) || !reportedThisPass.add(packageName)) {
continue;
}
Long lastReported = lastReportedMs.get(packageName);
@@ -33,5 +43,12 @@ final class NotificationSeenAppReporter {
lastReportedMs.put(packageName, now);
SbtSettings.noteSeenIconApp(context, packageName, now);
}
activeKeysByPackage.clear();
activeKeysByPackage.putAll(currentKeysByPackage);
}
private boolean wasAlreadyActive(String packageName, String key) {
Set<String> activeKeys = activeKeysByPackage.get(packageName);
return activeKeys != null && activeKeys.contains(key);
}
}
@@ -28,6 +28,8 @@ public final class OwnedIconHostManager {
"statusbartweak.unlocked.chip.host";
public static final String TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST =
"statusbartweak.lockscreen.cards.notification.host";
public static final String TAG_AOD_CARDS_NOTIFICATION_HOST =
"statusbartweak.aod.cards.notification.host";
private static final String[] UNLOCKED_HOST_ORDER = {
TAG_UNLOCKED_STATUS_HOST,
TAG_UNLOCKED_NOTIFICATION_HOST,
@@ -60,6 +62,10 @@ public final class OwnedIconHostManager {
return ensureHost(root, TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST);
}
public FrameLayout ensureAodCardsNotificationHost(View root) {
return ensureHost(root, TAG_AOD_CARDS_NOTIFICATION_HOST);
}
public FrameLayout ensureHost(View root, String tag) {
Objects.requireNonNull(tag, "tag");
if (!(root instanceof ViewGroup parent)) {
@@ -99,6 +105,7 @@ public final class OwnedIconHostManager {
removeHost(root, TAG_UNLOCKED_CARRIER_HOST);
removeHost(root, TAG_UNLOCKED_CHIP_HOST);
removeHost(root, TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST);
removeHost(root, TAG_AOD_CARDS_NOTIFICATION_HOST);
}
public void setUnlockedHostsVisible(View root, boolean visible) {
@@ -130,7 +137,8 @@ public final class OwnedIconHostManager {
|| TAG_UNLOCKED_CLOCK_HOST.equals(tag)
|| TAG_UNLOCKED_CARRIER_HOST.equals(tag)
|| TAG_UNLOCKED_CHIP_HOST.equals(tag)
|| TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST.equals(tag);
|| TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST.equals(tag)
|| TAG_AOD_CARDS_NOTIFICATION_HOST.equals(tag);
}
private FrameLayout findDirectHost(ViewGroup parent, String tag) {
@@ -156,6 +156,7 @@ final class SnapshotIconFilter {
append(sb, placement.key);
View view = placement.source != null ? placement.source.view() : null;
appendViewText(sb, view);
appendAncestorViewText(sb, view);
return sb.toString().toLowerCase(Locale.ROOT);
}
@@ -169,6 +170,15 @@ final class SnapshotIconFilter {
append(sb, contentDescription != null ? contentDescription.toString() : null);
}
private static void appendAncestorViewText(StringBuilder sb, View view) {
Object parent = view != null ? view.getParent() : null;
int depth = 0;
while (parent instanceof View parentView && depth++ < 4) {
appendViewText(sb, parentView);
parent = parentView.getParent();
}
}
private static ArrayList<String> statusSlots(String matchText) {
ArrayList<String> slots = new ArrayList<>();
add(slots, normalizedSlot(matchText));
@@ -12,16 +12,23 @@ import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bou
record SnapshotSource(
View view,
SnapshotMode mode,
String keyHint
String keyHint,
Bounds boundsOverride
) {
SnapshotSource(View view, SnapshotMode mode, String keyHint) {
this(view, mode, keyHint, null);
}
SnapshotSource(View view, SnapshotMode mode) {
this(view, mode, null);
this(view, mode, null, null);
}
}
enum SnapshotMode {
VIEW,
NOTIFICATION_DRAWABLE
STATUS_CHIP,
NOTIFICATION_DRAWABLE,
APP_ICON
}
final class SnapshotKeys {
@@ -43,6 +50,7 @@ final class SnapshotPlacement {
final HeldSignal heldSignal;
final Bounds contentBounds;
final int sourceOffsetX;
final Bounds sourceRenderBounds;
SnapshotPlacement(
SnapshotSource source,
@@ -63,7 +71,8 @@ final class SnapshotPlacement {
Color.WHITE,
heldSignal,
contentBounds,
sourceOffsetX);
sourceOffsetX,
null);
}
SnapshotPlacement(
@@ -76,6 +85,31 @@ final class SnapshotPlacement {
HeldSignal heldSignal,
Bounds contentBounds,
int sourceOffsetX
) {
this(
source,
bounds,
key,
signal,
overflowDot,
overflowDotColor,
heldSignal,
contentBounds,
sourceOffsetX,
null);
}
SnapshotPlacement(
SnapshotSource source,
Bounds bounds,
String key,
boolean signal,
boolean overflowDot,
int overflowDotColor,
HeldSignal heldSignal,
Bounds contentBounds,
int sourceOffsetX,
Bounds sourceRenderBounds
) {
this.source = source;
this.bounds = bounds;
@@ -86,6 +120,7 @@ final class SnapshotPlacement {
this.heldSignal = heldSignal;
this.contentBounds = contentBounds;
this.sourceOffsetX = sourceOffsetX;
this.sourceRenderBounds = sourceRenderBounds;
}
SnapshotSource source() {
@@ -123,6 +158,10 @@ final class SnapshotPlacement {
int sourceOffsetX() {
return sourceOffsetX;
}
Bounds sourceRenderBounds() {
return sourceRenderBounds;
}
}
record BitmapResult(Bitmap bitmap, boolean created) {
@@ -130,14 +169,13 @@ record BitmapResult(Bitmap bitmap, boolean created) {
record SnapshotRenderState(
String key,
int left,
int top,
int width,
int height,
boolean signal,
boolean overflowDot,
int overflowDotColor,
int sourceOffsetX,
int sourceRenderBoundsSignature,
String heldSignalKey,
int heldBitmapId,
SnapshotMode sourceMode,
@@ -1,5 +1,7 @@
package se.ajpanton.statusbartweak.runtime.render;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -20,6 +22,7 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.IdentityHashMap;
import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
@@ -35,6 +38,8 @@ final class SnapshotRenderer {
new WeakHashMap<>();
private final WeakHashMap<ImageView, SnapshotRenderState> statesByProxy =
new WeakHashMap<>();
private final WeakHashMap<View, SourceSignatureCache> sourceSignaturesByView =
new WeakHashMap<>();
private final LinkedHashMap<String, HeldSignal> heldSignals = new LinkedHashMap<>();
private long holdSignalsUntilElapsedMs;
@@ -58,11 +63,21 @@ final class SnapshotRenderer {
ArrayList<SnapshotPlacement> rawPlacements(
View root,
ArrayList<SnapshotSource> sources
) {
return rawPlacements(root, sources, sortPlacementsByX);
}
ArrayList<SnapshotPlacement> rawPlacements(
View root,
ArrayList<SnapshotSource> sources,
boolean sortByX
) {
ArrayList<SnapshotPlacement> placements = new ArrayList<>();
int order = 0;
for (SnapshotSource source : sources) {
Bounds bounds = visualBoundsRelativeTo(root, source.view());
Bounds bounds = source.boundsOverride() != null
? source.boundsOverride()
: visualBoundsRelativeTo(root, source.view());
if (bounds != null && bounds.width > 0 && bounds.height > 0) {
if (source.mode() == SnapshotMode.NOTIFICATION_DRAWABLE) {
bounds = notificationDrawableBounds(bounds);
@@ -112,7 +127,7 @@ final class SnapshotRenderer {
}
order++;
}
if (sortPlacementsByX) {
if (sortByX) {
placements.sort(Comparator.comparingInt(placement -> placement.bounds().left));
}
return placements;
@@ -176,11 +191,14 @@ final class SnapshotRenderer {
|| !desiredState.equals(statesByProxy.get(proxy));
if (bitmapChanged) {
if (placement.overflowDot()) {
drawOverflowDot(bitmap, placement.overflowDotColor());
drawOverflowDot(bitmap, placement.overflowDotColor(), placement.sourceOffsetX());
} else if (placement.heldSignal() != null) {
drawHeldSignal(placement.heldSignal(), bitmap);
} else if (placement.source() != null
&& placement.source().mode() == SnapshotMode.APP_ICON) {
drawAppIcon(host.getContext(), placement.source(), bitmap);
} else if (placement.source() != null) {
drawSnapshot(placement.source(), bitmap, placement.sourceOffsetX());
drawSnapshot(placement, bitmap);
if (placement.signal()) {
saveHeldSignal(key, bounds, bitmap);
}
@@ -265,7 +283,9 @@ final class SnapshotRenderer {
}
private boolean shouldTrimContent(SnapshotSource source) {
return trimViewContentForCollision && source != null && source.mode() == SnapshotMode.VIEW;
return trimViewContentForCollision
&& source != null
&& (source.mode() == SnapshotMode.VIEW || source.mode() == SnapshotMode.STATUS_CHIP);
}
private Bounds bitmapContentBounds(Bitmap bitmap) {
@@ -351,6 +371,9 @@ final class SnapshotRenderer {
if (keyHint != null && !keyHint.isEmpty()) {
return keyHint;
}
if (view == null) {
return source.mode().name().toLowerCase(java.util.Locale.ROOT) + "#" + order;
}
String slot = reflectString(view, "getSlot", "mSlot", "slot");
if (!slot.isEmpty()) {
return slot;
@@ -363,6 +386,9 @@ final class SnapshotRenderer {
}
private String signalKind(View view, String key) {
if (view == null || key == null) {
return null;
}
String lowerKey = key.toLowerCase(java.util.Locale.ROOT);
if (lowerKey.contains("wifi")) {
return "wifi";
@@ -475,24 +501,60 @@ final class SnapshotRenderer {
Bounds bounds = placement.bounds();
return new SnapshotRenderState(
placement.key(),
bounds.left,
bounds.top,
bounds.width,
bounds.height,
placement.signal(),
placement.overflowDot(),
placement.overflowDotColor(),
placement.sourceOffsetX(),
boundsSignature(placement.sourceRenderBounds()),
placement.heldSignal() != null ? placement.heldSignal().key : "",
placement.heldSignal() != null ? System.identityHashCode(placement.heldSignal().bitmap) : 0,
placement.source() != null ? placement.source().mode() : null,
placement.source() != null ? sourceTreeSignature(placement.source().view()) : 0);
placement.source() != null ? sourceSignature(placement.source()) : 0);
}
private int sourceSignature(SnapshotSource source) {
if (source == null) {
return 0;
}
int result = 17;
result = 31 * result + sourceTreeSignature(source.view());
result = 31 * result + boundsSignature(source.boundsOverride());
return result;
}
private int boundsSignature(Bounds bounds) {
if (bounds == null) {
return 0;
}
int result = 17;
result = 31 * result + bounds.left;
result = 31 * result + bounds.top;
result = 31 * result + bounds.width;
result = 31 * result + bounds.height;
return result;
}
private boolean isStatusBarIconView(View view) {
return view != null && view.getClass().getName().contains("StatusBarIconView");
}
private int sourceTreeSignature(View view) {
if (view == null) {
return 0;
}
int stamp = sourceSignatureStamp(view);
SourceSignatureCache cached = sourceSignaturesByView.get(view);
if (cached != null && cached.stamp == stamp) {
return cached.signature;
}
int signature = computeSourceTreeSignature(view);
sourceSignaturesByView.put(view, new SourceSignatureCache(stamp, signature));
return signature;
}
private int computeSourceTreeSignature(View view) {
int result = 17;
result = 31 * result + System.identityHashCode(view);
result = 31 * result + view.getVisibility();
@@ -523,6 +585,53 @@ final class SnapshotRenderer {
return result;
}
private int sourceSignatureStamp(View view) {
if (view == null) {
return 0;
}
int result = 17;
result = 31 * result + System.identityHashCode(view);
result = 31 * result + view.getVisibility();
result = 31 * result + Float.floatToIntBits(view.getAlpha());
result = 31 * result + view.getWidth();
result = 31 * result + view.getHeight();
result = 31 * result + view.getScrollX();
result = 31 * result + view.getScrollY();
result = 31 * result + java.util.Arrays.hashCode(view.getDrawableState());
result = 31 * result + SnapshotAppearanceSignature.view(view);
if (view instanceof TextView textView) {
CharSequence text = textView.getText();
result = 31 * result + (text != null ? text.toString().hashCode() : 0);
result = 31 * result + textView.getCurrentTextColor();
result = 31 * result + Float.floatToIntBits(textView.getTextSize());
}
if (view instanceof ImageView imageView) {
Drawable drawable = imageView.getDrawable();
result = 31 * result + System.identityHashCode(drawable);
if (drawable != null) {
result = 31 * result + drawable.getLevel();
result = 31 * result + drawable.getAlpha();
result = 31 * result + java.util.Arrays.hashCode(drawable.getState());
}
result = 31 * result + (imageView.getImageTintList() != null
? imageView.getImageTintList().hashCode()
: 0);
result = 31 * result + (imageView.getColorFilter() != null
? System.identityHashCode(imageView.getColorFilter())
: 0);
}
if (view instanceof ViewGroup group) {
result = 31 * result + group.getChildCount();
for (int i = 0; i < group.getChildCount(); i++) {
result = 31 * result + sourceSignatureStamp(group.getChildAt(i));
}
}
return result;
}
private record SourceSignatureCache(int stamp, int signature) {
}
private int drawableSignature(Drawable drawable) {
if (drawable == null) {
return 0;
@@ -548,9 +657,22 @@ final class SnapshotRenderer {
canvas.translate(-offsetX, 0);
}
View sourceView = source.view();
if (sourceView == null) {
return;
}
int oldVisibility = sourceView.getVisibility();
float oldAlpha = sourceView.getAlpha();
try {
// StatusBarIconView may be mid-transition while its drawable is valid.
if (source.mode() == SnapshotMode.VIEW
&& isStatusBarIconView(sourceView)
&& drawNotificationDrawable(
sourceView,
canvas,
bitmap.getWidth(),
bitmap.getHeight())) {
return;
}
if (source.mode() == SnapshotMode.NOTIFICATION_DRAWABLE
&& drawNotificationDrawable(
sourceView,
@@ -561,13 +683,287 @@ final class SnapshotRenderer {
}
sourceView.setVisibility(View.VISIBLE);
sourceView.setAlpha(1f);
if (source.mode() == SnapshotMode.STATUS_CHIP) {
drawTemporarilySizedView(sourceView, canvas, bitmap.getWidth(), bitmap.getHeight());
} else {
sourceView.draw(canvas);
}
} finally {
sourceView.setAlpha(oldAlpha);
sourceView.setVisibility(oldVisibility);
}
}
private void drawSnapshot(SnapshotPlacement placement, Bitmap bitmap) {
SnapshotSource source = placement.source();
if (source == null) {
bitmap.eraseColor(Color.TRANSPARENT);
return;
}
Bounds sourceRenderBounds = placement.sourceRenderBounds();
if ((source.mode() != SnapshotMode.STATUS_CHIP && source.mode() != SnapshotMode.VIEW)
|| sourceRenderBounds == null
|| sourceRenderBounds.width <= 0
|| sourceRenderBounds.height <= 0
|| bitmap.getWidth() <= 0
|| bitmap.getHeight() <= 0) {
drawSnapshot(source, bitmap, placement.sourceOffsetX());
return;
}
sourceRenderBounds = effectiveSourceRenderBounds(
placement,
sourceRenderBounds,
bitmap.getWidth(),
bitmap.getHeight());
if (sourceRenderBounds.width == bitmap.getWidth()
&& sourceRenderBounds.height == bitmap.getHeight()) {
drawSnapshot(source, bitmap, placement.sourceOffsetX());
return;
}
Bitmap sourceBitmap = Bitmap.createBitmap(
sourceRenderBounds.width,
sourceRenderBounds.height,
Bitmap.Config.ARGB_8888);
try {
drawSnapshot(source, sourceBitmap, 0);
bitmap.eraseColor(Color.TRANSPARENT);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG
| Paint.DITHER_FLAG);
float scale = (float) bitmap.getHeight() / sourceRenderBounds.height;
int scaledWidth = Math.max(1, Math.round(sourceRenderBounds.width * scale));
canvas.translate(-placement.sourceOffsetX(), 0);
canvas.drawBitmap(
sourceBitmap,
null,
new Rect(0, 0, scaledWidth, bitmap.getHeight()),
paint);
} finally {
sourceBitmap.recycle();
}
}
private Bounds effectiveSourceRenderBounds(
SnapshotPlacement placement,
Bounds requestedBounds,
int outputWidth,
int outputHeight
) {
if (placement == null
|| requestedBounds == null
|| placement.source() == null
|| placement.source().mode() != SnapshotMode.STATUS_CHIP
|| placement.contentBounds() == null
|| placement.contentBounds().width <= 0
|| outputWidth <= 0
|| outputHeight <= 0
|| requestedBounds.width <= 0
|| requestedBounds.height <= 0) {
return requestedBounds;
}
float scale = (float) outputHeight / requestedBounds.height;
if (scale <= 0f) {
return requestedBounds;
}
int targetContentWidth = Math.max(1, Math.round(placement.contentBounds().width / scale));
int maxWidth = Math.max(1, Math.round(outputWidth / scale));
int width = Math.max(1, Math.min(requestedBounds.width, maxWidth));
for (int i = 0; i < 3; i++) {
Bounds content = sourceContentBoundsAtWidth(placement.source(), width, requestedBounds.height);
if (content == null || content.width <= 0) {
break;
}
int delta = targetContentWidth - content.width;
if (Math.abs(delta) <= 1) {
break;
}
int nextWidth = Math.max(1, Math.min(maxWidth, width + delta));
if (nextWidth == width) {
break;
}
width = nextWidth;
}
return new Bounds(
requestedBounds.left,
requestedBounds.top,
width,
requestedBounds.height);
}
private Bounds sourceContentBoundsAtWidth(SnapshotSource source, int width, int height) {
if (source == null || width <= 0 || height <= 0) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
try {
drawSnapshot(source, bitmap, 0);
return bitmapContentBounds(bitmap);
} finally {
bitmap.recycle();
}
}
private void drawTemporarilySizedView(View view, Canvas canvas, int width, int height) {
int oldLeft = view.getLeft();
int oldTop = view.getTop();
int oldRight = view.getRight();
int oldBottom = view.getBottom();
int oldMeasuredWidth = view.getMeasuredWidth();
int oldMeasuredHeight = view.getMeasuredHeight();
ViewFrameState frameState = captureFrameState(view);
IdentityHashMap<TextView, Integer> oldMaxWidths = clearTextMaxWidths(view);
try {
int widthSpec = View.MeasureSpec.makeMeasureSpec(Math.max(1, width), View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(Math.max(1, height), View.MeasureSpec.EXACTLY);
view.measure(widthSpec, heightSpec);
view.layout(oldLeft, oldTop, oldLeft + Math.max(1, width), oldTop + Math.max(1, height));
clampDescendantsToParent(view);
view.draw(canvas);
} finally {
restoreTextMaxWidths(oldMaxWidths);
restoreFrameState(frameState);
if (oldMeasuredWidth > 0 && oldMeasuredHeight > 0) {
view.measure(
View.MeasureSpec.makeMeasureSpec(oldMeasuredWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(oldMeasuredHeight, View.MeasureSpec.EXACTLY));
}
view.layout(oldLeft, oldTop, oldRight, oldBottom);
}
}
private void clampDescendantsToParent(View view) {
if (!(view instanceof ViewGroup group)) {
return;
}
int parentRight = Math.max(0, group.getWidth() - group.getPaddingRight());
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
int availableWidth = Math.max(0, parentRight - child.getLeft());
int currentWidth = child.getWidth() > 0 ? child.getWidth() : child.getMeasuredWidth();
int currentHeight = child.getHeight() > 0 ? child.getHeight() : child.getMeasuredHeight();
if (availableWidth > 0 && currentWidth > availableWidth && currentHeight > 0) {
child.measure(
View.MeasureSpec.makeMeasureSpec(availableWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(currentHeight, View.MeasureSpec.EXACTLY));
child.layout(
child.getLeft(),
child.getTop(),
child.getLeft() + availableWidth,
child.getTop() + currentHeight);
}
clampDescendantsToParent(child);
}
}
private ViewFrameState captureFrameState(View root) {
ViewFrameState state = new ViewFrameState();
captureFrameState(root, state);
return state;
}
private void captureFrameState(View view, ViewFrameState state) {
if (view == null) {
return;
}
state.views.add(view);
state.frames.put(view, new ViewFrame(
view.getLeft(),
view.getTop(),
view.getRight(),
view.getBottom(),
view.getMeasuredWidth(),
view.getMeasuredHeight()));
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
captureFrameState(group.getChildAt(i), state);
}
}
}
private void restoreFrameState(ViewFrameState state) {
if (state == null) {
return;
}
for (int i = state.views.size() - 1; i >= 0; i--) {
View view = state.views.get(i);
ViewFrame frame = state.frames.get(view);
if (view == null || frame == null) {
continue;
}
if (frame.measuredWidth > 0 && frame.measuredHeight > 0) {
view.measure(
View.MeasureSpec.makeMeasureSpec(frame.measuredWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(frame.measuredHeight, View.MeasureSpec.EXACTLY));
}
view.layout(frame.left, frame.top, frame.right, frame.bottom);
}
}
private static final class ViewFrameState {
final ArrayList<View> views = new ArrayList<>();
final IdentityHashMap<View, ViewFrame> frames = new IdentityHashMap<>();
}
private record ViewFrame(
int left,
int top,
int right,
int bottom,
int measuredWidth,
int measuredHeight
) {
}
private IdentityHashMap<TextView, Integer> clearTextMaxWidths(View view) {
IdentityHashMap<TextView, Integer> oldMaxWidths = new IdentityHashMap<>();
collectTextMaxWidths(view, oldMaxWidths);
for (Map.Entry<TextView, Integer> entry : oldMaxWidths.entrySet()) {
entry.getKey().setMaxWidth(Integer.MAX_VALUE);
}
return oldMaxWidths;
}
private void collectTextMaxWidths(View view, IdentityHashMap<TextView, Integer> out) {
if (view instanceof TextView textView) {
out.put(textView, textView.getMaxWidth());
}
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
collectTextMaxWidths(group.getChildAt(i), out);
}
}
}
private void restoreTextMaxWidths(IdentityHashMap<TextView, Integer> oldMaxWidths) {
for (Map.Entry<TextView, Integer> entry : oldMaxWidths.entrySet()) {
entry.getKey().setMaxWidth(entry.getValue());
}
}
private void drawAppIcon(Context context, SnapshotSource source, Bitmap bitmap) {
bitmap.eraseColor(Color.TRANSPARENT);
if (context == null || source == null || source.keyHint() == null) {
return;
}
try {
String packageName = source.keyHint();
int suffixIndex = packageName.indexOf('@');
if (suffixIndex > 0) {
packageName = packageName.substring(0, suffixIndex);
}
Drawable drawable = context.getPackageManager().getApplicationIcon(packageName);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight());
drawable.draw(canvas);
} catch (PackageManager.NameNotFoundException ignored) {
// Unknown packages fail closed: keep the icon transparent rather than guessing.
}
}
private void drawHeldSignal(HeldSignal heldSignal, Bitmap bitmap) {
bitmap.eraseColor(Color.TRANSPARENT);
if (heldSignal.bitmap == null || heldSignal.bitmap.isRecycled()) {
@@ -579,16 +975,18 @@ final class SnapshotRenderer {
canvas.drawBitmap(heldSignal.bitmap, src, dst, null);
}
private void drawOverflowDot(Bitmap bitmap, int color) {
private void drawOverflowDot(Bitmap bitmap, int color, int centerOffsetX) {
bitmap.eraseColor(Color.TRANSPARENT);
if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
return;
}
float radius = Math.max(1f, Math.min(bitmap.getWidth() / 2f, bitmap.getHeight() * 0.16f));
float radius = Math.max(1f, Math.min(bitmap.getWidth() / 2f, bitmap.getHeight() * 0.125f));
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
Canvas canvas = new Canvas(bitmap);
canvas.drawCircle(bitmap.getWidth() / 2f, bitmap.getHeight() / 2f, radius, paint);
float centerX = bitmap.getWidth() / 2f + centerOffsetX;
centerX = Math.max(radius, Math.min(bitmap.getWidth() - radius, centerX));
canvas.drawCircle(centerX, bitmap.getHeight() / 2f, radius, paint);
}
private boolean drawNotificationDrawable(View source, Canvas canvas, int width, int height) {
@@ -0,0 +1,42 @@
package se.ajpanton.statusbartweak.runtime.render;
import java.util.ArrayList;
public final class StatusIconSlotVisibilityStore {
private static final Object LOCK = new Object();
private static ArrayList<String> activeSlots = new ArrayList<>();
private StatusIconSlotVisibilityStore() {
}
public static boolean setActiveSlots(ArrayList<String> slots) {
ArrayList<String> normalized = normalize(slots);
synchronized (LOCK) {
if (activeSlots.equals(normalized)) {
return false;
}
activeSlots = normalized;
return true;
}
}
static ArrayList<String> activeSlotList() {
synchronized (LOCK) {
return new ArrayList<>(activeSlots);
}
}
private static ArrayList<String> normalize(ArrayList<String> slots) {
ArrayList<String> normalized = new ArrayList<>();
if (slots == null) {
return normalized;
}
for (String slot : slots) {
if (slot == null || slot.isEmpty() || normalized.contains(slot)) {
continue;
}
normalized.add(slot);
}
return normalized;
}
}
@@ -0,0 +1,105 @@
package se.ajpanton.statusbartweak.runtime.render;
import android.content.res.Resources;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayDeque;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
import se.ajpanton.statusbartweak.runtime.mode.SceneKey;
final class StatusbarHeightSource {
private StatusbarHeightSource() {
}
static Bounds bounds(View root, SceneKey scene, RootAnchors anchors) {
if (root == null) {
return null;
}
if (scene != null && scene.isAod()) {
Bounds aodBounds = firstBoundsForIds(root,
"common_battery_statusbar_container",
"keyguard_status_bar_container",
"keyguard_header");
if (isUsable(root, aodBounds)) {
return aodBounds;
}
return null;
}
Bounds anchorBounds = ViewGeometry.boundsRelativeTo(root, anchors != null ? anchors.statusRoot : null);
if (isUsable(root, anchorBounds)) {
return anchorBounds;
}
Bounds scannedBounds = firstBoundsForIds(root,
"system_icon_area",
"status_bar_contents",
"status_bar");
if (isUsable(root, scannedBounds)) {
return scannedBounds;
}
return null;
}
static int height(View root, SceneKey scene, RootAnchors anchors, Bounds fallbackBounds) {
Bounds bounds = bounds(root, scene, anchors);
if (isUsable(root, bounds)) {
return bounds.top + bounds.height;
}
if (isUsable(root, fallbackBounds)) {
return fallbackBounds.top + fallbackBounds.height;
}
return Math.max(1, root != null ? root.getHeight() : 1);
}
private static Bounds firstBoundsForIds(View root, String... idNames) {
if (!(root instanceof ViewGroup rootGroup) || idNames == null || idNames.length == 0) {
return null;
}
for (String candidateId : idNames) {
ArrayDeque<View> queue = new ArrayDeque<>();
queue.add(rootGroup);
while (!queue.isEmpty()) {
View view = queue.removeFirst();
if (candidateId.equals(resolveIdName(view))) {
Bounds bounds = ViewGeometry.boundsRelativeTo(root, view);
if (isUsable(root, bounds)) {
return bounds;
}
}
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child != null) {
queue.addLast(child);
}
}
}
}
}
return null;
}
private static boolean isUsable(View root, Bounds bounds) {
if (root == null || bounds == null || bounds.width <= 0 || bounds.height <= 0) {
return false;
}
int right = bounds.left + bounds.width;
int bottom = bounds.top + bounds.height;
return bounds.left >= 0
&& bounds.top >= 0
&& right <= root.getWidth()
&& bottom <= root.getHeight();
}
private static String resolveIdName(View view) {
if (view == null || view.getId() == View.NO_ID) {
return "";
}
try {
return view.getResources().getResourceEntryName(view.getId());
} catch (Resources.NotFoundException ignored) {
return "";
}
}
}
@@ -0,0 +1,33 @@
package se.ajpanton.statusbartweak.runtime.render;
import android.view.View;
import java.util.LinkedHashMap;
import java.util.Map;
final class StatusbarHeightStore {
private static final int MAX_ENTRIES = 4;
private static final Map<Integer, Integer> HEIGHT_BY_ROOT_WIDTH = new LinkedHashMap<>();
private StatusbarHeightStore() {
}
static void put(View root, int height) {
if (root == null || root.getWidth() <= 0 || height <= 0) {
return;
}
HEIGHT_BY_ROOT_WIDTH.put(root.getWidth(), height);
while (HEIGHT_BY_ROOT_WIDTH.size() > MAX_ENTRIES) {
Integer oldest = HEIGHT_BY_ROOT_WIDTH.keySet().iterator().next();
HEIGHT_BY_ROOT_WIDTH.remove(oldest);
}
}
static int get(View root) {
if (root == null || root.getWidth() <= 0) {
return 0;
}
Integer height = HEIGHT_BY_ROOT_WIDTH.get(root.getWidth());
return height != null && height > 0 ? height : 0;
}
}
@@ -1,6 +1,8 @@
package se.ajpanton.statusbartweak.runtime.render;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
@@ -9,15 +11,13 @@ import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class UnlockedChipPlacementController {
private final SnapshotRenderer renderer;
private final UnlockedVerticalOffsetScaler offsetScaler;
private ChipHoldState heldPlacements;
private Bounds lastCollisionBounds;
private int lastRootWidth;
private int lastRootHeight;
UnlockedChipPlacementController(SnapshotRenderer renderer, UnlockedVerticalOffsetScaler offsetScaler) {
UnlockedChipPlacementController(SnapshotRenderer renderer) {
this.renderer = renderer;
this.offsetScaler = offsetScaler;
}
void clear() {
@@ -33,31 +33,169 @@ final class UnlockedChipPlacementController {
ArrayList<SnapshotSource> metricSources,
SbtSettings settings
) {
ArrayList<SnapshotPlacement> rawPlacements = renderer.rawPlacements(root, visibleSources);
ArrayList<SnapshotPlacement> rawPlacements = renderer.rawPlacements(
root,
withNaturalChipBounds(root, visibleSources));
rawPlacements = withVisualChipCollision(rawPlacements);
if (!rawPlacements.isEmpty()) {
rememberMetrics(root, rawPlacements);
ArrayList<SnapshotPlacement> placements = offsetPlacements(
rawPlacements,
offsetScaler.chip(
settings.layoutChipVerticalOffsetPx,
representativeChipHeight(rawPlacements)));
heldPlacements = new ChipHoldState(
copyAsHeldPlacements(placements),
copyAsHeldPlacements(rawPlacements),
root.getWidth(),
root.getHeight(),
0);
return placements;
return rawPlacements;
}
ArrayList<SnapshotPlacement> placements = isAnyChipHidingEnabled(settings)
? new ArrayList<>()
: heldPlacements(root);
if (placements.isEmpty()) {
placements = emptyPlacement(root, metricSources, settings);
placements = emptyPlacement(root, metricSources);
}
return placements;
}
private ArrayList<SnapshotPlacement> withVisualChipCollision(ArrayList<SnapshotPlacement> placements) {
if (placements == null || placements.isEmpty()) {
return placements;
}
ArrayList<SnapshotPlacement> result = null;
for (int i = 0; i < placements.size(); i++) {
SnapshotPlacement placement = placements.get(i);
if (placement == null
|| placement.source == null
|| placement.source.mode() != SnapshotMode.STATUS_CHIP
|| placement.contentBounds == null
|| placement.bounds == null) {
continue;
}
Bounds visualCollision = new Bounds(
placement.contentBounds.left,
placement.contentBounds.top,
placement.contentBounds.width,
placement.contentBounds.height);
if (visualCollision.equals(placement.contentBounds)) {
continue;
}
if (result == null) {
result = new ArrayList<>(placements);
}
result.set(i, new SnapshotPlacement(
placement.source,
placement.bounds,
placement.key,
placement.signal,
placement.overflowDot,
placement.overflowDotColor,
placement.heldSignal,
visualCollision,
placement.sourceOffsetX));
}
return result != null ? result : placements;
}
private ArrayList<SnapshotSource> withNaturalChipBounds(
View root,
ArrayList<SnapshotSource> sources
) {
if (root == null || sources == null || sources.isEmpty()) {
return sources;
}
ArrayList<SnapshotSource> result = null;
for (int i = 0; i < sources.size(); i++) {
SnapshotSource source = sources.get(i);
SnapshotSource normalized = withNaturalChipBounds(root, source);
if (normalized != source && result == null) {
result = new ArrayList<>(sources);
}
if (result != null) {
result.set(i, normalized);
}
}
return result != null ? result : sources;
}
private SnapshotSource withNaturalChipBounds(View root, SnapshotSource source) {
if (source == null || source.mode() != SnapshotMode.STATUS_CHIP) {
return source;
}
Bounds bounds = source.boundsOverride() != null
? source.boundsOverride()
: ViewGeometry.boundsRelativeTo(root, source.view());
if (bounds == null || bounds.width <= 0 || bounds.height <= 0) {
return source;
}
int naturalWidth = naturalChipWidth(source.view(), bounds.width, root.getWidth());
if (naturalWidth <= bounds.width) {
return source;
}
return new SnapshotSource(
source.view(),
source.mode(),
source.keyHint(),
new Bounds(bounds.left, bounds.top, naturalWidth, bounds.height));
}
private int naturalChipWidth(View view, int currentWidth, int maxWidth) {
if (view == null) {
return currentWidth;
}
int width = naturalContentWidth(view);
if (maxWidth > 0) {
width = Math.min(width, maxWidth);
}
return Math.max(currentWidth, width);
}
private int naturalContentWidth(View view) {
if (view == null) {
return 0;
}
int width = measuredWidth(view);
if (view instanceof TextView textView) {
width = Math.max(width, naturalTextWidth(textView));
}
if (view instanceof ViewGroup group) {
int childRight = group.getPaddingLeft() + group.getPaddingRight();
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
ViewGroup.LayoutParams lp = child.getLayoutParams();
int margins = 0;
if (lp instanceof ViewGroup.MarginLayoutParams marginParams) {
margins = marginParams.leftMargin + marginParams.rightMargin;
}
childRight = Math.max(
childRight,
child.getLeft() + margins + naturalContentWidth(child));
}
width = Math.max(width, childRight + group.getPaddingRight());
}
return width;
}
private int measuredWidth(View view) {
int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth();
ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp != null && lp.width > 0) {
width = Math.max(width, lp.width);
}
return Math.max(0, width);
}
private int naturalTextWidth(TextView textView) {
CharSequence text = textView.getText();
int textWidth = text != null && text.length() > 0
? (int) Math.ceil(textView.getPaint().measureText(text, 0, text.length()))
: 0;
return textWidth
+ textView.getCompoundPaddingLeft()
+ textView.getCompoundPaddingRight();
}
private ArrayList<SnapshotPlacement> heldPlacements(View root) {
ChipHoldState state = heldPlacements;
if (state == null || state.placements.isEmpty()) {
@@ -81,20 +219,16 @@ final class UnlockedChipPlacementController {
private ArrayList<SnapshotPlacement> emptyPlacement(
View root,
ArrayList<SnapshotSource> sources,
SbtSettings settings
ArrayList<SnapshotSource> sources
) {
ArrayList<SnapshotPlacement> result = new ArrayList<>();
Bounds bounds = emptyBounds(root, sources);
if (bounds == null || bounds.height <= 0) {
return result;
}
int verticalOffset = offsetScaler.chip(
settings != null ? settings.layoutChipVerticalOffsetPx : 0,
bounds.height);
result.add(new SnapshotPlacement(
null,
new Bounds(0, bounds.top - verticalOffset, 1, bounds.height),
new Bounds(0, bounds.top, 1, bounds.height),
SnapshotKeys.EMPTY_CHIP,
false,
false,
@@ -119,7 +253,9 @@ final class UnlockedChipPlacementController {
return null;
}
for (SnapshotSource source : sources) {
Bounds bounds = ViewGeometry.boundsRelativeTo(root, source.view());
Bounds bounds = source.boundsOverride() != null
? source.boundsOverride()
: ViewGeometry.boundsRelativeTo(root, source.view());
if (bounds == null || bounds.width <= 0 || bounds.height <= 0) {
continue;
}
@@ -198,40 +334,6 @@ final class UnlockedChipPlacementController {
return held;
}
private ArrayList<SnapshotPlacement> offsetPlacements(ArrayList<SnapshotPlacement> placements, int verticalOffsetPx) {
if (placements == null || placements.isEmpty() || verticalOffsetPx == 0) {
return placements;
}
ArrayList<SnapshotPlacement> result = new ArrayList<>();
int dy = -verticalOffsetPx;
for (SnapshotPlacement placement : placements) {
Bounds bounds = placement.bounds;
result.add(new SnapshotPlacement(
placement.source,
new Bounds(bounds.left, bounds.top + dy, bounds.width, bounds.height),
placement.key,
placement.signal,
placement.overflowDot,
placement.overflowDotColor,
placement.heldSignal,
placement.contentBounds,
placement.sourceOffsetX));
}
return result;
}
private int representativeChipHeight(ArrayList<SnapshotPlacement> placements) {
Bounds bounds = collisionUnion(placements);
return bounds != null ? Math.max(0, bounds.height) : representativeHeight(placements);
}
private int representativeHeight(ArrayList<SnapshotPlacement> placements) {
if (placements == null || placements.isEmpty()) {
return 0;
}
return Math.max(0, placements.get(0).bounds.height);
}
private record ChipHoldState(
ArrayList<SnapshotPlacement> placements,
int rootWidth,
@@ -5,10 +5,14 @@ import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.features.RuntimeContext;
import se.ajpanton.statusbartweak.runtime.features.clock.ClockLayoutTextFactory;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle;
import se.ajpanton.statusbartweak.runtime.mode.SceneKey;
import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
@@ -20,34 +24,44 @@ import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
* into owned unlocked rows instead of copying each stock X position.
*/
public final class UnlockedIconSnapshotRenderController {
private final RuntimeContext runtimeContext;
private final SnapshotRenderer statusRenderer;
private final SnapshotRenderer notificationRenderer;
private final SnapshotRenderer chipRenderer;
private final ClockProxyRenderer clockRenderer = new ClockProxyRenderer();
private final ClockProxyRenderer carrierRenderer = new ClockProxyRenderer();
private final UnlockedStockSourceCollector sourceCollector = new UnlockedStockSourceCollector();
private final UnlockedStockSourceCollector sourceCollector;
private final UnlockedVerticalOffsetScaler offsetScaler = new UnlockedVerticalOffsetScaler();
private final UnlockedChipPlacementController chipPlacementController;
private final UnlockedLayoutPlanner layoutPlanner;
private final WeakHashMap<View, Integer> clockBaselineDeltaByRoot = new WeakHashMap<>();
private final LinkedHashMap<Integer, Integer> clockSourceBaselineByRootKey = new LinkedHashMap<>();
private final WeakHashMap<View, LayoutInputSignature> lastLayoutSignatureByRoot = new WeakHashMap<>();
private final WeakHashMap<View, CollectedIcons> lastCollectedIconsByRoot = new WeakHashMap<>();
private final WeakHashMap<View, LayoutPlan> lastLayoutPlanByRoot = new WeakHashMap<>();
private final WeakHashMap<View, Integer> lastClockGeometryByRoot = new WeakHashMap<>();
private final WeakHashMap<View, Integer> lastClockSourceGeometryByRoot = new WeakHashMap<>();
private final WeakHashMap<View, Integer> lastClockAppearanceByRoot = new WeakHashMap<>();
private final WeakHashMap<View, Boolean> forcedChipRefreshByRoot = new WeakHashMap<>();
public UnlockedIconSnapshotRenderController() {
this(null);
}
public UnlockedIconSnapshotRenderController(RuntimeContext runtimeContext) {
this.runtimeContext = runtimeContext;
sourceCollector = new UnlockedStockSourceCollector(runtimeContext);
statusRenderer = new SnapshotRenderer(true, true, false);
notificationRenderer = new SnapshotRenderer(false, false, false);
chipRenderer = new SnapshotRenderer(false, false, true);
chipPlacementController = new UnlockedChipPlacementController(chipRenderer, offsetScaler);
chipPlacementController = new UnlockedChipPlacementController(chipRenderer);
layoutPlanner = new UnlockedLayoutPlanner(
statusRenderer,
notificationRenderer,
chipRenderer,
chipPlacementController,
offsetScaler);
offsetScaler,
runtimeContext);
}
public RenderResult renderUnlocked(
@@ -60,7 +74,9 @@ public final class UnlockedIconSnapshotRenderController {
SceneKey scene,
TextView externalCarrierView
) {
if (scene == null || (!scene.isUnlocked() && !scene.isLockscreen()) || !root.isAttachedToWindow()) {
if (scene == null
|| (!scene.isUnlocked() && !scene.isLockscreen() && !scene.isAod())
|| !root.isAttachedToWindow()) {
clearAll();
return new RenderResult(true, true);
}
@@ -92,11 +108,14 @@ public final class UnlockedIconSnapshotRenderController {
anchors,
previousIcons);
LayoutInputSignature previousSignature = lastLayoutSignatureByRoot.get(root);
boolean forceChipRefresh = Boolean.TRUE.equals(forcedChipRefreshByRoot.remove(root));
boolean sceneChanged = previousSignature != null
&& !java.util.Objects.equals(signature.scene, previousSignature.scene);
boolean stockSourcesChanged = previousSignature == null
|| signature.stockSurfaceSignature() != previousSignature.stockSurfaceSignature();
boolean chipSourcesChanged = previousSignature == null
|| signature.chipSources != previousSignature.chipSources;
if (signature.equals(previousSignature)) {
if (signature.equals(previousSignature) && !forceChipRefresh) {
return new RenderResult(
false,
false,
@@ -109,6 +128,7 @@ public final class UnlockedIconSnapshotRenderController {
ClockLayout currentClockLayout = null;
Integer currentClockGeometry = null;
if (isClockOnlySignatureChange(previousSignature, signature)
&& !forceChipRefresh
&& scene.isUnlocked()
&& settings.clockEnabledUnlocked
&& previousIcons != null) {
@@ -158,8 +178,11 @@ public final class UnlockedIconSnapshotRenderController {
}
}
CollectedIcons collectedIcons = previousIcons;
if (stockSourcesChanged || collectedIcons == null) {
collectedIcons = sourceCollector.collect(root, anchors);
if (forceChipRefresh && collectedIcons != null) {
collectedIcons = sourceCollector.collectChips(root, anchors, collectedIcons);
lastCollectedIconsByRoot.put(root, collectedIcons);
} else if (sceneChanged || stockSourcesChanged || collectedIcons == null) {
collectedIcons = sourceCollector.collect(root, anchors, scene);
lastCollectedIconsByRoot.put(root, collectedIcons);
} else if (chipSourcesChanged) {
collectedIcons = sourceCollector.collectChips(root, anchors, collectedIcons);
@@ -190,7 +213,6 @@ public final class UnlockedIconSnapshotRenderController {
settings,
currentClockLayout,
scene);
lastLayoutPlanByRoot.put(root, layoutPlan);
lastClockGeometryByRoot.put(root, currentClockGeometry != null ? currentClockGeometry : 0);
lastClockSourceGeometryByRoot.put(root, currentClockSourceGeometry);
layoutPlan = withCurrentDotColors(layoutPlan, collectedIcons);
@@ -200,7 +222,9 @@ public final class UnlockedIconSnapshotRenderController {
} else {
clockRenderer.clear(clockHost);
}
if (scene.isLockscreen() && settings.layoutCarrierEnabledLockscreen && layoutPlan.carrierPlacement != null) {
if (scene.isLockscreen()
&& settings.layoutCarrierEnabledLockscreen
&& layoutPlan.carrierPlacement != null) {
carrierRenderer.render(carrierHost, layoutPlan.carrierPlacement);
} else {
carrierRenderer.clear(carrierHost);
@@ -265,6 +289,32 @@ public final class UnlockedIconSnapshotRenderController {
}
}
public boolean refreshUnlockedChipContent(
View root,
ViewGroup chipHost,
SceneKey scene
) {
if (root == null
|| chipHost == null
|| scene == null
|| !scene.isUnlocked()
|| !root.isAttachedToWindow()) {
return false;
}
LayoutPlan layoutPlan = lastLayoutPlanByRoot.get(root);
if (layoutPlan == null
|| layoutPlan.chipPlacements == null
|| layoutPlan.chipPlacements.isEmpty()) {
return false;
}
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
if (settings == null || !settings.layoutChipEnabledUnlocked) {
return false;
}
chipRenderer.renderPlacements(chipHost, layoutPlan.chipPlacements);
return true;
}
public boolean refreshUnlockedClockText(
View root,
ViewGroup clockHost,
@@ -273,7 +323,7 @@ public final class UnlockedIconSnapshotRenderController {
if (root == null
|| clockHost == null
|| scene == null
|| !scene.isUnlocked()
|| (!scene.isUnlocked() && !scene.isLockscreen())
|| !root.isAttachedToWindow()) {
return false;
}
@@ -282,7 +332,7 @@ public final class UnlockedIconSnapshotRenderController {
return false;
}
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
if (settings == null || !settings.clockEnabledUnlocked) {
if (settings == null || !clockEnabledForScene(settings, scene)) {
return false;
}
CollectedIcons collectedIcons = lastCollectedIconsByRoot.get(root);
@@ -355,9 +405,13 @@ public final class UnlockedIconSnapshotRenderController {
if (settings == null || scene == null) {
return false;
}
return scene.isLockscreen()
? settings.layoutStatusEnabledLockscreen
: settings.layoutStatusEnabledUnlocked;
if (scene.isLockscreen()) {
return settings.layoutStatusEnabledLockscreen;
}
if (scene.isAod()) {
return settings.layoutStatusEnabledAod;
}
return settings.layoutStatusEnabledUnlocked;
}
private boolean notificationsEnabledForScene(SbtSettings settings, SceneKey scene) {
@@ -366,8 +420,14 @@ public final class UnlockedIconSnapshotRenderController {
}
if (scene.isLockscreen()) {
return settings.layoutNotifEnabledLockscreen
&& scene.notificationDisplayStyle() != se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle.NONE
&& scene.notificationDisplayStyle() != se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle.CARDS;
&& scene.notificationDisplayStyle() != NotificationDisplayStyle.NONE
&& scene.notificationDisplayStyle() != NotificationDisplayStyle.CARDS;
}
if (scene.isAod()) {
NotificationDisplayStyle style = scene.notificationDisplayStyle();
return settings.layoutNotifEnabledAod
&& (style == NotificationDisplayStyle.ICONS
|| style == NotificationDisplayStyle.DOT);
}
return settings.layoutNotifEnabledUnlocked;
}
@@ -556,6 +616,8 @@ public final class UnlockedIconSnapshotRenderController {
chipRenderer.clearAll();
sourceCollector.clear();
clockBaselineDeltaByRoot.clear();
clockSourceBaselineByRootKey.clear();
forcedChipRefreshByRoot.clear();
lastLayoutSignatureByRoot.clear();
lastCollectedIconsByRoot.clear();
lastLayoutPlanByRoot.clear();
@@ -565,9 +627,31 @@ public final class UnlockedIconSnapshotRenderController {
chipPlacementController.clear();
}
public void clearRoot(View root) {
if (root == null) {
return;
}
forcedChipRefreshByRoot.remove(root);
sourceCollector.clearRoot(root);
clockBaselineDeltaByRoot.remove(root);
lastLayoutSignatureByRoot.remove(root);
lastCollectedIconsByRoot.remove(root);
lastLayoutPlanByRoot.remove(root);
lastClockGeometryByRoot.remove(root);
lastClockSourceGeometryByRoot.remove(root);
lastClockAppearanceByRoot.remove(root);
}
public void forceChipRefresh(View root) {
if (root != null) {
forcedChipRefreshByRoot.put(root, true);
}
}
public void beginRootTransition() {
statusRenderer.beginTransitionHold();
sourceCollector.clear();
forcedChipRefreshByRoot.clear();
lastLayoutSignatureByRoot.clear();
lastCollectedIconsByRoot.clear();
lastLayoutPlanByRoot.clear();
@@ -615,12 +699,12 @@ public final class UnlockedIconSnapshotRenderController {
if (model == null || model.isEmpty()) {
return null;
}
int baselineY = stableClockBaselineY(root, source, clockContainer);
return ClockLayout.measure(
source,
model,
UnlockedLayoutPlanner.positionFor(settings.clockPosition),
stableClockBaselineY(root, source, clockContainer)
- offsetScaler.clock(settings.clockVerticalOffsetPx, source),
baselineY - offsetScaler.clock(settings.clockVerticalOffsetPx, source),
root.getWidth(),
cutoutBounds,
settings.clockMiddleAutoNearestSide,
@@ -872,6 +956,7 @@ public final class UnlockedIconSnapshotRenderController {
int sourceBaseline = source.getBaseline() > 0
? source.getBaseline()
: (sourceBounds != null ? Math.max(0, (sourceBounds.height * 3) / 4) : 0);
sourceBaseline = stableClockSourceBaseline(root, source, sourceBaseline);
Bounds containerBounds = ViewGeometry.boundsRelativeTo(root, clockContainer);
if (containerBounds != null
&& containerBounds.width > 0
@@ -890,6 +975,28 @@ public final class UnlockedIconSnapshotRenderController {
return sourceBounds != null ? sourceBounds.top + sourceBaseline : 0;
}
private int stableClockSourceBaseline(View root, TextView source, int sourceBaseline) {
if (root == null || source == null || root.getWidth() <= 0 || sourceBaseline <= 0) {
return sourceBaseline;
}
int key = 31 * root.getWidth() + Math.round(source.getTextSize());
Integer previous = clockSourceBaselineByRootKey.get(key);
int stable = previous == null ? sourceBaseline : Math.min(previous, sourceBaseline);
clockSourceBaselineByRootKey.put(key, stable);
while (clockSourceBaselineByRootKey.size() > 8) {
Integer eldest = null;
for (Map.Entry<Integer, Integer> entry : clockSourceBaselineByRootKey.entrySet()) {
eldest = entry.getKey();
break;
}
if (eldest == null) {
break;
}
clockSourceBaselineByRootKey.remove(eldest);
}
return stable;
}
public record RenderResult(
boolean layoutChanged,
boolean stockSourcesChanged,
@@ -18,6 +18,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
final SnapshotRenderer renderer;
final ArrayList<SnapshotPlacement> rawPlacements;
final boolean showDotIfTruncated;
final boolean forceOverflowDotOnly;
final Bounds reservedBounds;
final ClockLayout clockLayout;
@@ -26,6 +27,8 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
Bounds placedBounds;
int clipStartPx;
int clipEndPx;
private int solverCollisionTop = Integer.MIN_VALUE;
private int solverCollisionHeight = Integer.MIN_VALUE;
private UnlockedLayoutBand(
LayoutItemKind kind,
@@ -36,6 +39,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
boolean middleAutoNearestSide,
AnchorSide middleSide,
boolean showDotIfTruncated,
boolean forceOverflowDotOnly,
Bounds reservedBounds,
ClockLayout clockLayout,
SplitRegion splitRegion
@@ -45,6 +49,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
this.renderer = renderer;
this.rawPlacements = rawPlacements;
this.showDotIfTruncated = showDotIfTruncated;
this.forceOverflowDotOnly = forceOverflowDotOnly;
this.reservedBounds = reservedBounds;
this.clockLayout = clockLayout;
}
@@ -64,6 +69,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
settings.clockMiddleAutoNearestSide,
sideFor(settings.clockMiddleCutoutSide),
false,
false,
null,
clockLayout,
SplitRegion.NONE);
@@ -84,6 +90,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
settings.clockMiddleAutoNearestSide,
sideFor(settings.clockMiddleCutoutSide),
false,
false,
null,
clockLayout,
splitRegion);
@@ -105,6 +112,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
middleAutoNearestSide,
middleSide,
false,
false,
null,
carrierLayout,
SplitRegion.NONE);
@@ -129,6 +137,37 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
middleAutoNearestSide,
middleSide,
showDotIfTruncated,
false,
null,
null,
SplitRegion.NONE);
}
UnlockedLayoutBand withSolverCollision(int top, int height) {
solverCollisionTop = top;
solverCollisionHeight = Math.max(0, height);
return this;
}
static UnlockedLayoutBand dotIcons(
LayoutItemKind kind,
ViewGroup host,
SnapshotRenderer renderer,
ArrayList<SnapshotPlacement> rawPlacements,
LayoutPosition position,
boolean middleAutoNearestSide,
AnchorSide middleSide
) {
return new UnlockedLayoutBand(
kind,
host,
renderer,
rawPlacements,
position,
middleAutoNearestSide,
middleSide,
true,
true,
null,
null,
SplitRegion.NONE);
@@ -201,6 +240,16 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
if (kind != LayoutItemKind.NOTIFICATIONS && kind != LayoutItemKind.STATUS) {
return;
}
if (forceOverflowDotOnly) {
int height = dotHeight(rawPlacements);
SnapshotPlacement dotPlacement = dotPlacement(
Math.max(1, Math.round(Math.max(1, height) * 0.75f)),
height,
rowTop(rawPlacements));
placements = new ArrayList<>();
placements.add(dotPlacement);
return;
}
int targetVisible = Math.max(0, visibleIcons);
placements = iconSlice(rawPlacements, firstIconIndex, targetVisible);
if (dot) {
@@ -295,7 +344,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
withoutDot.add(placement);
}
}
SnapshotPlacement dotPlacement = dotPlacement(width, height);
SnapshotPlacement dotPlacement = dotPlacement(width, height, rowTop(withoutDot));
if (kind == LayoutItemKind.STATUS) {
withoutDot.add(0, dotPlacement);
} else {
@@ -417,6 +466,25 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
return boxes;
}
ArrayList<Bounds> solverCollisionBoxes() {
ArrayList<Bounds> boxes = collisionBoxes();
if (isTextKind()
|| solverCollisionTop == Integer.MIN_VALUE
|| solverCollisionHeight <= 0
|| boxes.isEmpty()) {
return boxes;
}
ArrayList<Bounds> solverBoxes = new ArrayList<>();
for (Bounds box : boxes) {
solverBoxes.add(new Bounds(
box.left,
solverCollisionTop,
box.width,
solverCollisionHeight));
}
return solverBoxes;
}
@Override
protected ArrayList<Bounds> placedCollisionBounds() {
ArrayList<Bounds> boxes = new ArrayList<>();
@@ -454,8 +522,8 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
private int overflowDotVisualWidth(SnapshotPlacement placement) {
Bounds bounds = placement.bounds;
int base = Math.max(1, Math.min(bounds.width, bounds.height));
int radius = Math.max(1, Math.round(base * 0.16f));
return Math.min(bounds.width, radius * 2 + 2);
int radius = Math.max(1, Math.round(base * 0.125f));
return Math.min(bounds.width, radius * 2);
}
private Bounds collisionBounds(SnapshotPlacement placement) {
@@ -487,7 +555,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
boolean hadDot = hasOverflowDot(icons);
icons.remove(removeIndex);
if (showDotIfTruncated && !hadDot) {
SnapshotPlacement dot = dotPlacement(dotWidth(icons), dotHeight(icons));
SnapshotPlacement dot = dotPlacement(dotWidth(icons), dotHeight(icons), rowTop(icons));
if (fromStart) {
icons.add(0, dot);
} else {
@@ -604,17 +672,61 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
return 0;
}
private SnapshotPlacement dotPlacement(int width, int height) {
private int rowTop(ArrayList<SnapshotPlacement> preferred) {
int top = rowTop(preferred, true);
if (top != Integer.MAX_VALUE) {
return top;
}
top = rowTop(rawPlacements, true);
if (top != Integer.MAX_VALUE) {
return top;
}
top = rowTop(preferred, false);
if (top != Integer.MAX_VALUE) {
return top;
}
top = rowTop(rawPlacements, false);
return top != Integer.MAX_VALUE ? top : top();
}
private int rowTop(ArrayList<SnapshotPlacement> placements, boolean shrinkableOnly) {
if (placements == null || placements.isEmpty()) {
return Integer.MAX_VALUE;
}
int top = Integer.MAX_VALUE;
for (SnapshotPlacement placement : placements) {
if (placement == null || (shrinkableOnly && !isShrinkableIcon(placement))) {
continue;
}
top = Math.min(top, collisionBounds(placement).top);
}
return top;
}
private SnapshotPlacement dotPlacement(int width, int height, int rowTop) {
int safeWidth = Math.max(1, width);
int safeHeight = Math.max(1, height);
return new SnapshotPlacement(
null,
new Bounds(0, top(), Math.max(1, width), Math.max(1, height)),
new Bounds(0, rowTop, safeWidth, safeHeight),
SnapshotKeys.OVERFLOW_DOT + ":" + kind.name().toLowerCase(java.util.Locale.ROOT),
false,
true,
dotColor(),
null,
new Bounds(0, 0, Math.max(1, width), Math.max(1, height)),
0);
new Bounds(0, 0, safeWidth, safeHeight),
dotCenterOffsetX(safeWidth, safeHeight));
}
private int dotCenterOffsetX(int width, int height) {
int fullWidth = Math.max(1, Math.round(Math.max(1, height) * 0.75f));
if (width >= fullWidth) {
return 0;
}
float radius = Math.max(1f, Math.min(width / 2f, height * 0.125f));
float centered = width / 2f;
float target = kind == LayoutItemKind.STATUS ? radius : width - radius;
return Math.round(target - centered);
}
private int dotColor() {
@@ -649,7 +761,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
int height = Math.max(1, dotHeight(activePlacements()));
return new SnapshotPlacement(
null,
new Bounds(0, top(), 1, height),
new Bounds(0, rowTop(activePlacements()), 1, height),
SnapshotKeys.EMPTY_ICON_CONTAINER + ":" + kind.name().toLowerCase(java.util.Locale.ROOT),
false,
false,
@@ -689,6 +801,14 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
collisionWidth,
placement.contentBounds.height)
: null;
Bounds sourceRenderBounds = placement.sourceRenderBounds;
int sourceOffsetX = placement.sourceOffsetX + removeLeft;
if (kind == LayoutItemKind.CHIP && sourceRenderBounds != null && clippedContent != null) {
sourceRenderBounds = chipSourceRenderBounds(
sourceRenderBounds,
clippedContent);
sourceOffsetX = 0;
}
return new SnapshotPlacement(
placement.source,
clippedBounds,
@@ -698,7 +818,20 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
placement.overflowDotColor,
placement.heldSignal,
clippedContent,
placement.sourceOffsetX + removeLeft);
sourceOffsetX,
sourceRenderBounds);
}
private Bounds chipSourceRenderBounds(Bounds sourceRenderBounds, Bounds clippedContent) {
if (sourceRenderBounds == null || clippedContent == null) {
return sourceRenderBounds;
}
int width = Math.max(1, clippedContent.left + clippedContent.width);
return new Bounds(
sourceRenderBounds.left,
sourceRenderBounds.top,
width,
sourceRenderBounds.height);
}
private int sumWidths(ArrayList<SnapshotPlacement> snapshotPlacements) {
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,7 @@ package se.ajpanton.statusbartweak.runtime.render;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@@ -9,20 +10,36 @@ import android.widget.TextView;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.features.RuntimeContext;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle;
import se.ajpanton.statusbartweak.runtime.mode.SceneKey;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class UnlockedStockSourceCollector {
private final WeakHashMap<View, RootAnchors> anchorsByRoot = new WeakHashMap<>();
UnlockedStockSourceCollector() {
this(null);
}
UnlockedStockSourceCollector(RuntimeContext runtimeContext) {
}
void clear() {
anchorsByRoot.clear();
}
void clearRoot(View root) {
if (root != null) {
anchorsByRoot.remove(root);
}
}
RootAnchors anchorsFor(View root) {
RootAnchors cached = anchorsByRoot.get(root);
if (cached != null && cached.isUsable()) {
@@ -37,21 +54,40 @@ final class UnlockedStockSourceCollector {
return anchors;
}
CollectedIcons collect(View root, RootAnchors anchors) {
CollectedIcons collect(View root, RootAnchors anchors, SceneKey scene) {
ArrayList<SnapshotSource> statusIcons = new ArrayList<>();
ArrayList<SnapshotSource> notificationIcons = new ArrayList<>();
ArrayList<SnapshotSource> statusChips = new ArrayList<>();
ArrayList<SnapshotSource> statusChipMetrics = new ArrayList<>();
if (scene != null && scene.isAod()) {
collectAodStatusSnapshotViews(root, statusIcons, scene);
}
if (statusIcons.isEmpty()) {
if (anchors.statusRoot != null) {
collectStatusSnapshotViews(anchors.statusRoot, statusIcons);
} else if (scene != null && scene.isAod()) {
collectStatusSnapshotViews(root, statusIcons);
}
}
if (scene == null || !scene.isAod()) {
mergeActiveHiddenStatusSources(root, anchors.statusIcons, statusIcons);
}
if (anchors.battery != null && !isDescendantOf(anchors.battery, anchors.statusRoot)) {
collectStatusSnapshotViews(anchors.battery, statusIcons);
}
if (anchors.notificationRoot != null) {
normalizeStatusIconSourceBounds(root, statusIcons);
if (scene != null && scene.isAod()) {
if (scene.notificationDisplayStyle() == NotificationDisplayStyle.ICONS
|| scene.notificationDisplayStyle() == NotificationDisplayStyle.DOT) {
notificationIcons.addAll(
LockscreenCardsNotificationIconSourceStore.snapshotSourcesForAodIconsRoot(root));
}
} else if (anchors.notificationRoot != null) {
collectNotificationSnapshotViews(anchors.notificationRoot, notificationIcons);
}
if (scene == null || scene.isUnlocked()) {
collectStatusChipSnapshotViews(root, statusChips, statusChipMetrics);
}
return new CollectedIcons(
anchors.clockView,
statusIcons,
@@ -61,6 +97,53 @@ final class UnlockedStockSourceCollector {
anchors.carrierView);
}
private void collectAodStatusSnapshotViews(
View root,
ArrayList<SnapshotSource> out,
SceneKey scene
) {
if (root == null || out == null) {
return;
}
boolean iconMode = scene != null
&& (scene.notificationDisplayStyle() == NotificationDisplayStyle.ICONS
|| scene.notificationDisplayStyle() == NotificationDisplayStyle.DOT);
View excludedNotificationContainer = iconMode
? LockscreenCardsNotificationIconSourceStore.sourceContainerForAodIconsRoot(root)
: LockscreenCardsNotificationIconSourceStore.sourceContainerForRoot(root);
for (View source : AodStatusbarSourceStore.statusSourcesForRoot(
root,
excludedNotificationContainer)) {
collectStatusSnapshotViews(source, out, true);
}
}
private String reflectString(View view, String methodName, String... fieldNames) {
Object methodValue = ReflectionSupport.invokeMethod(view, methodName, new Class<?>[0]);
if (methodValue != null) {
return safeText(String.valueOf(methodValue));
}
if (fieldNames != null) {
for (String fieldName : fieldNames) {
Object fieldValue = ReflectionSupport.getFieldValue(view, fieldName);
if (fieldValue != null) {
return safeText(String.valueOf(fieldValue));
}
}
}
return "";
}
private String safeText(Object value) {
if (value == null) {
return "";
}
return String.valueOf(value)
.replace('\n', ' ')
.replace('\r', ' ')
.replace(';', ',');
}
CollectedIcons collectChips(
View root,
RootAnchors anchors,
@@ -89,9 +172,17 @@ final class UnlockedStockSourceCollector {
RootAnchors anchors,
CollectedIcons previousIcons
) {
int chipSourceSignature = settings.layoutChipEnabledUnlocked
int chipSourceSignature = scene != null
&& scene.isUnlocked()
&& settings.layoutChipEnabledUnlocked
? chipSourceSignature(root, previousIcons)
: 0;
int statusSourceSignature = sourceLayoutListSignature(
previousIcons != null ? previousIcons.statusIcons : null);
if (scene != null && scene.isAod()) {
statusSourceSignature = 31 * statusSourceSignature
+ aodLayoutAnchorSignature(root, anchors, scene);
}
return new LayoutInputSignature(
scene != null ? scene.toString() : "",
root.getWidth(),
@@ -102,7 +193,7 @@ final class UnlockedStockSourceCollector {
cutout != null ? cutout.top : Integer.MIN_VALUE,
cutout != null ? cutout.right : Integer.MIN_VALUE,
cutout != null ? cutout.bottom : Integer.MIN_VALUE,
layoutSettingsSignature(settings),
loggingAwareSettingsSignature(root, settings),
clockTimeSignature(settings),
viewSignatureOrZero(anchors != null ? anchors.clockView : null),
shallowTreeSignature(anchors != null ? anchors.clockContainer : null),
@@ -120,11 +211,36 @@ final class UnlockedStockSourceCollector {
anchors != null ? anchors.clockContainer : null),
notificationRootSignature(anchors != null ? anchors.notificationRoot : null),
viewSignatureOrZero(anchors != null ? anchors.carrierView : null),
sourceLayoutListSignature(previousIcons != null ? previousIcons.statusIcons : null),
statusSourceSignature,
sourceLayoutListSignature(previousIcons != null ? previousIcons.notificationIcons : null),
chipSourceSignature);
}
private int aodLayoutAnchorSignature(View root, RootAnchors anchors, SceneKey scene) {
if (root == null) {
return 0;
}
boolean iconMode = scene != null
&& (scene.notificationDisplayStyle() == NotificationDisplayStyle.ICONS
|| scene.notificationDisplayStyle() == NotificationDisplayStyle.DOT);
View notificationContainer = iconMode
? LockscreenCardsNotificationIconSourceStore.sourceContainerForAodIconsRoot(root)
: LockscreenCardsNotificationIconSourceStore.sourceContainerForRoot(root);
Bounds notificationBounds = iconMode
? LockscreenCardsNotificationIconSourceStore.sourceContainerBoundsForAodIconsRoot(root)
: LockscreenCardsNotificationIconSourceStore.sourceContainerBoundsForRoot(root);
if (!iconMode && notificationBounds == null) {
notificationBounds = ViewGeometry.boundsRelativeTo(
root,
anchors != null ? anchors.notificationRoot : null);
}
Bounds statusBounds = AodStatusbarSourceStore.statusBoundsForRoot(root, notificationContainer);
int result = 17;
result = 31 * result + boundsSignature(notificationBounds);
result = 31 * result + boundsSignature(statusBounds);
return result;
}
private void collectKnownStatusChipSnapshotViews(
View root,
CollectedIcons previousIcons,
@@ -136,8 +252,7 @@ final class UnlockedStockSourceCollector {
}
if (previousIcons.statusChips != null) {
for (SnapshotSource source : previousIcons.statusChips) {
View view = source.view();
if (isStatusChipSnapshotCandidate(root, view)) {
if (isKnownStatusChipSnapshotSource(root, source)) {
visibleOut.add(source);
metricOut.add(source);
return;
@@ -146,7 +261,13 @@ final class UnlockedStockSourceCollector {
}
if (previousIcons.statusChipMetrics != null) {
for (SnapshotSource source : previousIcons.statusChipMetrics) {
if (isStatusChipMetricCandidate(root, source.view())) {
if (isKnownStatusChipSnapshotSource(root, source)) {
visibleOut.add(statusChipSource(root, source.view()));
metricOut.add(statusChipSource(root, source.view()));
return;
}
if (isStatusChipMetricCandidate(root, source.view(), true)
&& hasVisibleStatusChipContent(source.view())) {
metricOut.add(source);
return;
}
@@ -191,7 +312,7 @@ final class UnlockedStockSourceCollector {
if (battery == null && idName.equals("battery")) {
battery = view;
}
if (notificationRoot == null && idName.equals("notificationIcons")) {
if (notificationRoot == null && isNotificationRootCandidate(view, idName)) {
notificationRoot = view;
}
if (carrierView == null && view instanceof TextView textView && isCarrierTextView(textView)) {
@@ -252,8 +373,35 @@ final class UnlockedStockSourceCollector {
return Integer.MAX_VALUE;
}
private boolean isNotificationRootCandidate(View view, String idName) {
if (idName.equals("notificationIcons")
|| idName.equals("notification_icononly_container")
|| idName.equals("notification_icononly_area")
|| idName.equals("keyguard_icononly_container_view")
|| idName.equals("common_notification_widget")) {
return true;
}
String className = view != null ? view.getClass().getName() : "";
return className.contains("NotificationIconsOnlyContainer")
|| className.contains("SecShelfNotificationIconContainer");
}
private void collectStatusSnapshotViews(View source, ArrayList<SnapshotSource> out) {
collectStatusSnapshotViews(source, out, false);
}
private void collectStatusSnapshotViews(
View source,
ArrayList<SnapshotSource> out,
boolean includeHidden
) {
if (isOwnedRuntimeView(source)) {
return;
}
boolean candidate = isStatusSnapshotCandidate(source);
if (includeHidden) {
candidate = isStatusSnapshotCandidate(source, true);
}
if (isModernSignalContainer(source)) {
if (candidate) {
out.add(new SnapshotSource(source, SnapshotMode.VIEW));
@@ -268,11 +416,269 @@ final class UnlockedStockSourceCollector {
}
if (source instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
collectStatusSnapshotViews(group.getChildAt(i), out);
collectStatusSnapshotViews(group.getChildAt(i), out, includeHidden);
}
}
}
private void mergeActiveHiddenStatusSources(
View root,
View statusIcons,
ArrayList<SnapshotSource> out
) {
if (root == null || statusIcons == null || out == null) {
return;
}
ArrayList<String> activeSlots = StatusIconSlotVisibilityStore.activeSlotList();
if (activeSlots.isEmpty()) {
return;
}
LinkedHashMap<String, SnapshotSource> existingBySlot = new LinkedHashMap<>();
for (SnapshotSource source : out) {
if (source != null && isDescendantOf(source.view(), statusIcons)) {
String slot = reflectString(source.view(), "getSlot", "mSlot", "slot");
if (!slot.isEmpty()) {
existingBySlot.put(slot, source);
}
}
}
LinkedHashMap<String, View> childBySlot = new LinkedHashMap<>();
for (View child : directChildren(statusIcons)) {
String slot = reflectString(child, "getSlot", "mSlot", "slot");
if (!slot.isEmpty() && isDrawableStatusIconView(child)) {
childBySlot.put(slot, child);
}
}
ArrayList<SnapshotSource> mergedStatusIcons = new ArrayList<>();
int syntheticIndex = 0;
Bounds metricBounds = representativeStatusIconBounds(root, statusIcons, out);
int size = Math.max(1, metricBounds.height);
for (String slot : activeSlots) {
SnapshotSource existing = existingBySlot.get(slot);
if (existing != null) {
SnapshotSource ordered = orderedStatusSource(
root,
existing,
metricBounds,
syntheticIndex,
size);
mergedStatusIcons.add(ordered);
syntheticIndex++;
continue;
}
View child = childBySlot.get(slot);
if (child == null) {
continue;
}
int width = syntheticStatusIconWidth(child, size);
int height = syntheticStatusIconHeight(child, size);
Bounds bounds = orderedStatusBounds(metricBounds, syntheticIndex, width, height);
mergedStatusIcons.add(new SnapshotSource(
child,
SnapshotMode.VIEW,
slot,
bounds));
syntheticIndex++;
}
if (mergedStatusIcons.isEmpty()) {
return;
}
ArrayList<SnapshotSource> merged = new ArrayList<>();
boolean inserted = false;
for (SnapshotSource source : out) {
if (source != null && isDescendantOf(source.view(), statusIcons)) {
if (!inserted) {
merged.addAll(mergedStatusIcons);
inserted = true;
}
continue;
}
merged.add(source);
}
if (!inserted) {
merged.addAll(mergedStatusIcons);
}
out.clear();
out.addAll(merged);
}
private SnapshotSource orderedStatusSource(
View root,
SnapshotSource source,
Bounds metricBounds,
int index,
int fallbackSize
) {
Bounds sourceBounds = ViewGeometry.boundsRelativeTo(root, source.view());
int width = sourceBounds != null && sourceBounds.width > 0
? sourceBounds.width
: fallbackSize;
int height = sourceBounds != null && sourceBounds.height > 0
? sourceBounds.height
: fallbackSize;
return new SnapshotSource(
source.view(),
source.mode(),
source.keyHint(),
orderedStatusBounds(metricBounds, index, width, height));
}
private Bounds orderedStatusBounds(
Bounds metricBounds,
int index,
int width,
int height
) {
int fallbackHeight = Math.max(1, metricBounds.height);
return new Bounds(
metricBounds.left + Math.max(0, index) * fallbackHeight,
metricBounds.top,
Math.max(1, width),
Math.max(1, height));
}
private int syntheticStatusIconWidth(View view, int fallbackSize) {
int width = measuredWidth(view);
if (width > 0) {
return width;
}
int drawableWidth = drawableWidth(view);
if (drawableWidth > 0) {
return normalizedStatusIconWidth(drawableWidth, fallbackSize);
}
return Math.max(1, fallbackSize);
}
private void normalizeStatusIconSourceBounds(View root, ArrayList<SnapshotSource> sources) {
if (root == null || sources == null || sources.isEmpty()) {
return;
}
for (int i = 0; i < sources.size(); i++) {
SnapshotSource source = sources.get(i);
if (source == null || source.mode() != SnapshotMode.VIEW || !isDrawableStatusIconView(source.view())) {
continue;
}
Bounds bounds = source.boundsOverride() != null
? source.boundsOverride()
: ViewGeometry.boundsRelativeTo(root, source.view());
if (bounds == null || bounds.height <= 0) {
continue;
}
int drawableWidth = drawableWidth(source.view());
if (drawableWidth <= 0) {
continue;
}
int width = normalizedStatusIconWidth(drawableWidth, bounds.height);
if (width == bounds.width) {
continue;
}
sources.set(i, new SnapshotSource(
source.view(),
source.mode(),
source.keyHint(),
new Bounds(bounds.left, bounds.top, width, bounds.height)));
}
}
private int normalizedStatusIconWidth(int drawableWidth, int iconHeight) {
return Math.max(1, drawableWidth + statusIconHorizontalPadding(iconHeight));
}
private int statusIconHorizontalPadding(int iconHeight) {
return Math.max(1, Math.round(Math.max(1, iconHeight) * 0.09f));
}
private int drawableWidth(View view) {
Drawable drawable = view instanceof ImageView imageView ? imageView.getDrawable() : null;
if (drawable == null) {
return 0;
}
int width = drawable.getBounds().width();
if (width > 0) {
return width;
}
return Math.max(0, drawable.getIntrinsicWidth());
}
private int syntheticStatusIconHeight(View view, int fallbackSize) {
int height = measuredHeight(view);
return height > 0 ? height : Math.max(1, fallbackSize);
}
private int measuredWidth(View view) {
if (view == null) {
return 0;
}
if (view.getWidth() > 0) {
return view.getWidth();
}
if (view.getMeasuredWidth() > 0) {
return view.getMeasuredWidth();
}
ViewGroup.LayoutParams lp = view.getLayoutParams();
return lp != null && lp.width > 0 ? lp.width : 0;
}
private int measuredHeight(View view) {
if (view == null) {
return 0;
}
if (view.getHeight() > 0) {
return view.getHeight();
}
if (view.getMeasuredHeight() > 0) {
return view.getMeasuredHeight();
}
ViewGroup.LayoutParams lp = view.getLayoutParams();
return lp != null && lp.height > 0 ? lp.height : 0;
}
private ArrayList<View> directChildren(View view) {
ArrayList<View> children = new ArrayList<>();
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child != null) {
children.add(child);
}
}
}
return children;
}
private Bounds representativeStatusIconBounds(
View root,
View statusIcons,
ArrayList<SnapshotSource> sources
) {
if (sources != null) {
for (SnapshotSource source : sources) {
if (source == null || !isDescendantOf(source.view(), statusIcons)) {
continue;
}
Bounds bounds = ViewGeometry.boundsRelativeTo(root, source.view());
if (bounds != null && bounds.width > 0 && bounds.height > 0) {
int size = Math.max(1, bounds.height);
return new Bounds(bounds.left, bounds.top, size, size);
}
}
}
Bounds containerBounds = ViewGeometry.boundsRelativeTo(root, statusIcons);
if (containerBounds != null && containerBounds.height > 0) {
int size = Math.max(1, containerBounds.height);
return new Bounds(containerBounds.left, containerBounds.top, size, size);
}
return new Bounds(0, 0, 32, 32);
}
private boolean isDrawableStatusIconView(View view) {
if (view == null || !view.getClass().getName().contains("StatusBarIconView")) {
return false;
}
Drawable drawable = view instanceof ImageView imageView ? imageView.getDrawable() : null;
return drawable != null;
}
private boolean isModernSignalContainer(View view) {
String className = view.getClass().getName();
return className.contains("ModernStatusBarWifiView")
@@ -304,12 +710,13 @@ final class UnlockedStockSourceCollector {
while (!queue.isEmpty()) {
View view = queue.removeFirst();
if (isStatusChipSnapshotCandidate(root, view)) {
visibleOut.add(new SnapshotSource(view, SnapshotMode.VIEW));
metricOut.add(new SnapshotSource(view, SnapshotMode.VIEW));
SnapshotSource source = statusChipSource(root, view);
visibleOut.add(source);
metricOut.add(source);
return;
}
if (metricOut.isEmpty() && isStatusChipMetricCandidate(root, view)) {
metricOut.add(new SnapshotSource(view, SnapshotMode.VIEW));
metricOut.add(statusChipSource(root, view));
}
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
@@ -320,9 +727,58 @@ final class UnlockedStockSourceCollector {
}
private boolean isStatusChipSnapshotCandidate(View root, View view) {
return isStatusChipSnapshotCandidate(root, view, false);
}
private SnapshotSource statusChipSource(View root, View view) {
Bounds bounds = ViewGeometry.boundsRelativeTo(root, view);
if (bounds == null || bounds.width <= 0 || bounds.height <= 0) {
return new SnapshotSource(view, SnapshotMode.STATUS_CHIP);
}
int measuredWidth = view.getMeasuredWidth();
int measuredHeight = view.getMeasuredHeight();
if (measuredWidth > bounds.width || measuredHeight > bounds.height) {
bounds = new Bounds(
bounds.left,
bounds.top,
Math.max(bounds.width, measuredWidth),
Math.max(bounds.height, measuredHeight));
}
return new SnapshotSource(view, SnapshotMode.STATUS_CHIP, null, bounds);
}
private boolean isKnownStatusChipSnapshotSource(View root, SnapshotSource source) {
if (source == null || source.mode() != SnapshotMode.STATUS_CHIP || root == null) {
return false;
}
View view = source.view();
if (view == null
|| view == root
|| !view.isAttachedToWindow()
|| !isDescendantOf(view, root)
|| ReflectionSupport.getBooleanField(view, "sbtStatusChipForcedHidden", false)
|| !isStatusChipCandidate(view)
|| !hasVisibleStatusChipContent(view)) {
return false;
}
Bounds bounds = source.boundsOverride() != null
? source.boundsOverride()
: ViewGeometry.boundsRelativeTo(root, view);
return bounds != null
&& bounds.width > 0
&& bounds.height > 0
&& bounds.top >= 0
&& bounds.top <= root.getHeight()
&& bounds.width < root.getWidth() / 2;
}
private boolean isStatusChipSnapshotCandidate(View root, View view, boolean allowHidden) {
if (view == null || view == root || view.getVisibility() != View.VISIBLE) {
return false;
}
if (!allowHidden && view.getAlpha() <= 0f && !hasVisibleStatusChipContent(view)) {
return false;
}
if (ReflectionSupport.getBooleanField(view, "sbtStatusChipForcedHidden", false)) {
return false;
}
@@ -348,10 +804,46 @@ final class UnlockedStockSourceCollector {
return true;
}
private boolean hasVisibleStatusChipContent(View source) {
if (!(source instanceof ViewGroup group)) {
return false;
}
ArrayDeque<View> queue = new ArrayDeque<>();
for (int i = 0; i < group.getChildCount(); i++) {
queue.addLast(group.getChildAt(i));
}
while (!queue.isEmpty()) {
View view = queue.removeFirst();
if (view == null) {
continue;
}
if (view.getVisibility() == View.VISIBLE && view.getAlpha() > 0f) {
int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth();
int height = view.getHeight() > 0 ? view.getHeight() : view.getMeasuredHeight();
if (width > 0 && height > 0) {
return true;
}
}
if (view instanceof ViewGroup childGroup) {
for (int i = 0; i < childGroup.getChildCount(); i++) {
queue.addLast(childGroup.getChildAt(i));
}
}
}
return false;
}
private boolean isStatusChipMetricCandidate(View root, View view) {
return isStatusChipMetricCandidate(root, view, false);
}
private boolean isStatusChipMetricCandidate(View root, View view, boolean allowHidden) {
if (view == null || view == root || root == null) {
return false;
}
if (!allowHidden && view.getAlpha() <= 0f && !hasVisibleStatusChipContent(view)) {
return false;
}
int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth();
int height = view.getHeight() > 0 ? view.getHeight() : view.getMeasuredHeight();
if (width <= 0 || height <= 0 || width >= root.getWidth() / 2) {
@@ -386,7 +878,14 @@ final class UnlockedStockSourceCollector {
}
private boolean isStatusSnapshotCandidate(View view) {
if (view.getVisibility() != View.VISIBLE) {
return isStatusSnapshotCandidate(view, false);
}
private boolean isStatusSnapshotCandidate(View view, boolean includeHidden) {
if (isOwnedRuntimeView(view)) {
return false;
}
if (!includeHidden && view.getVisibility() != View.VISIBLE) {
return false;
}
int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth();
@@ -424,6 +923,9 @@ final class UnlockedStockSourceCollector {
}
private boolean isNotificationSnapshotCandidate(View view) {
if (isOwnedRuntimeView(view)) {
return false;
}
if (view instanceof TextView) {
return false;
}
@@ -466,6 +968,19 @@ final class UnlockedStockSourceCollector {
return false;
}
private boolean isOwnedRuntimeView(View view) {
View current = view;
while (current != null) {
Object tag = current.getTag();
if (tag instanceof String value && value.startsWith("statusbartweak.")) {
return true;
}
Object parent = current.getParent();
current = parent instanceof View ? (View) parent : null;
}
return false;
}
private static int viewSignatureOrZero(View view) {
return view != null ? viewSignature(view) : 0;
}
@@ -571,9 +1086,6 @@ final class UnlockedStockSourceCollector {
hasKnownChipSource = true;
result = 31 * result + chipSourceListSignature(previousIcons.statusChipMetrics);
}
if (!hasKnownChipSource) {
return 0;
}
}
if (!hasKnownChipSource) {
result = 31 * result + statusChipCandidateSignature(root);
@@ -590,7 +1102,7 @@ final class UnlockedStockSourceCollector {
queue.add(root);
while (!queue.isEmpty()) {
View view = queue.removeFirst();
if (view != root && isStatusChipCandidate(view)) {
if (view != root && isStatusChipCandidate(view) && hasVisibleStatusChipContent(view)) {
result = 31 * result + chipViewLayoutSignature(view);
}
if (view instanceof ViewGroup group) {
@@ -602,7 +1114,7 @@ final class UnlockedStockSourceCollector {
return result;
}
private static int chipSourceListSignature(ArrayList<SnapshotSource> sources) {
private int chipSourceListSignature(ArrayList<SnapshotSource> sources) {
if (sources == null || sources.isEmpty()) {
return 0;
}
@@ -610,6 +1122,7 @@ final class UnlockedStockSourceCollector {
result = 31 * result + sources.size();
for (SnapshotSource source : sources) {
result = 31 * result + source.mode().ordinal();
result = 31 * result + (hasVisibleStatusChipContent(source.view()) ? 1 : 0);
result = 31 * result + chipViewLayoutSignature(source.view());
}
return result;
@@ -679,6 +1192,18 @@ final class UnlockedStockSourceCollector {
return result;
}
private static int boundsSignature(Bounds bounds) {
if (bounds == null) {
return 0;
}
int result = 17;
result = 31 * result + bounds.left;
result = 31 * result + bounds.top;
result = 31 * result + bounds.width;
result = 31 * result + bounds.height;
return result;
}
private static int drawableLayoutSignature(android.graphics.drawable.Drawable drawable) {
if (drawable == null) {
return 0;
@@ -761,6 +1286,7 @@ final class UnlockedStockSourceCollector {
settings.layoutChipMiddleSide,
settings.layoutChipMiddleAutoNearestSide,
settings.layoutChipVerticalOffsetPx,
settings.layoutChipHeightSteps,
settings.layoutNotifPosition,
settings.layoutNotifSortOrder,
settings.layoutNotifEnabledUnlocked,
@@ -775,6 +1301,12 @@ final class UnlockedStockSourceCollector {
java.util.Arrays.hashCode(settings.layoutNotifLockMiddleSides),
java.util.Arrays.hashCode(settings.layoutNotifLockMiddleAutoNearestSides),
java.util.Arrays.hashCode(settings.layoutNotifLockVerticalOffsetsPx),
settings.layoutNotifEnabledAod,
settings.layoutNotifAodCount,
java.util.Arrays.hashCode(settings.layoutNotifAodPositions),
java.util.Arrays.hashCode(settings.layoutNotifAodMiddleSides),
java.util.Arrays.hashCode(settings.layoutNotifAodMiddleAutoNearestSides),
java.util.Arrays.hashCode(settings.layoutNotifAodVerticalOffsetsPx),
settings.layoutNotifMiddleSide,
settings.layoutNotifMiddleAutoNearestSide,
settings.layoutNotifVerticalOffsetPx,
@@ -791,6 +1323,12 @@ final class UnlockedStockSourceCollector {
java.util.Arrays.hashCode(settings.layoutStatusLockMiddleSides),
java.util.Arrays.hashCode(settings.layoutStatusLockMiddleAutoNearestSides),
java.util.Arrays.hashCode(settings.layoutStatusLockVerticalOffsetsPx),
settings.layoutStatusEnabledAod,
settings.layoutStatusAodCount,
java.util.Arrays.hashCode(settings.layoutStatusAodPositions),
java.util.Arrays.hashCode(settings.layoutStatusAodMiddleSides),
java.util.Arrays.hashCode(settings.layoutStatusAodMiddleAutoNearestSides),
java.util.Arrays.hashCode(settings.layoutStatusAodVerticalOffsetsPx),
settings.layoutStatusMiddleSide,
settings.layoutStatusMiddleAutoNearestSide,
settings.layoutStatusVerticalOffsetPx,
@@ -802,4 +1340,12 @@ final class UnlockedStockSourceCollector {
settings.statusChipsHideNavUnlocked,
settings.statusChipsHideCallUnlocked);
}
private int loggingAwareSettingsSignature(View root, SbtSettings settings) {
int result = layoutSettingsSignature(settings);
boolean logsEnabled = root != null
&& root.getContext() != null
&& SbtSettings.isDebugLogWritingEnabled(root.getContext());
return 31 * result + (logsEnabled ? 1 : 0);
}
}
@@ -5,7 +5,6 @@ import android.widget.TextView;
final class UnlockedVerticalOffsetScaler {
private int baselineNotificationIconSizePx;
private int baselineStatusIconHeightPx;
private int baselineChipHeightPx;
private float baselineClockTextSizePx;
int notification(int offsetPx, int currentIconHeightPx) {
@@ -22,11 +21,6 @@ final class UnlockedVerticalOffsetScaler {
return scaledOffset(offsetPx, currentIconHeightPx, baselineStatusIconHeightPx);
}
int chip(int offsetPx, int currentChipHeightPx) {
baselineChipHeightPx = updateBaseline(baselineChipHeightPx, currentChipHeightPx);
return scaledOffset(offsetPx, currentChipHeightPx, baselineChipHeightPx);
}
int clock(int offsetPx, TextView source) {
if (offsetPx == 0 || source == null || source.getTextSize() <= 0f) {
return offsetPx;
@@ -12,6 +12,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Pattern;
public final class AppIconRules {
@@ -24,6 +25,9 @@ public final class AppIconRules {
public static final String EXTRA_PACKAGE = "package";
public static final String EXTRA_FIRST_SEEN_MS = "first_seen_ms";
public static final String EXTRA_LAST_SEEN_MS = "last_seen_ms";
public static final String EXTRA_LAST_SEEN_TIMEZONE_OFFSET_MINUTES =
"last_seen_timezone_offset_minutes";
public static final int MODE_AOD = 1;
public static final int MODE_LOCK = 1 << 1;
@@ -73,7 +77,7 @@ public final class AppIconRules {
prefs.edit().putStringSet(PREF_KEY_BLOCKED_MODES, encoded).apply();
}
public static Map<String, Long> loadSeenSession(SharedPreferences prefs) {
public static Map<String, SeenApp> loadSeenSession(SharedPreferences prefs) {
if (prefs == null) {
return new HashMap<>();
}
@@ -97,7 +101,7 @@ public final class AppIconRules {
prefs.edit().putStringSet(PREF_KEY_EXCEPTION_RULES, encoded).apply();
}
public static void saveSeenSession(SharedPreferences prefs, Map<String, Long> map) {
public static void saveSeenSession(SharedPreferences prefs, Map<String, SeenApp> map) {
if (prefs == null) {
return;
}
@@ -112,18 +116,37 @@ public final class AppIconRules {
prefs.edit().remove(PREF_KEY_SEEN_SESSION).apply();
}
public static void noteSeen(Map<String, Long> seen, String pkg, long firstSeenMs) {
public static boolean noteSeen(Map<String, SeenApp> seen,
String pkg,
long seenMs,
int timezoneOffsetMinutes) {
if (seen == null || !isValidPackageName(pkg)) {
return;
return false;
}
Long existing = seen.get(pkg);
if (seenMs <= 0L) {
seenMs = System.currentTimeMillis();
}
SeenApp existing = seen.get(pkg);
if (existing == null) {
seen.put(pkg, firstSeenMs);
return;
seen.put(pkg, new SeenApp(pkg, seenMs, seenMs, timezoneOffsetMinutes));
return true;
}
if (firstSeenMs > 0 && firstSeenMs < existing) {
seen.put(pkg, firstSeenMs);
long firstSeenMs = existing.firstSeenMs > 0L
? Math.min(existing.firstSeenMs, seenMs)
: seenMs;
long lastSeenMs = existing.lastSeenMs;
int lastSeenOffsetMinutes = existing.lastSeenTimezoneOffsetMinutes;
if (lastSeenMs <= 0L || seenMs >= lastSeenMs) {
lastSeenMs = seenMs;
lastSeenOffsetMinutes = timezoneOffsetMinutes;
}
if (firstSeenMs == existing.firstSeenMs
&& lastSeenMs == existing.lastSeenMs
&& lastSeenOffsetMinutes == existing.lastSeenTimezoneOffsetMinutes) {
return false;
}
seen.put(pkg, new SeenApp(pkg, firstSeenMs, lastSeenMs, lastSeenOffsetMinutes));
return true;
}
public static boolean isBlocked(Map<String, Integer> masks, String pkg, String mode) {
@@ -367,8 +390,8 @@ public final class AppIconRules {
return MODE_NAME_UNKNOWN;
}
public static Map<String, Long> parseSeenSession(Set<String> rawSet) {
Map<String, Long> out = new HashMap<>();
public static Map<String, SeenApp> parseSeenSession(Set<String> rawSet) {
Map<String, SeenApp> out = new HashMap<>();
if (rawSet == null || rawSet.isEmpty()) {
return out;
}
@@ -377,7 +400,7 @@ public final class AppIconRules {
continue;
}
String[] parts = entry.split("\\|", -1);
if (parts.length != 2) {
if (parts.length != 2 && parts.length != 4) {
continue;
}
String pkg = parts[0];
@@ -385,38 +408,61 @@ public final class AppIconRules {
continue;
}
long firstSeen;
long lastSeen;
int lastSeenOffsetMinutes;
try {
firstSeen = Long.parseLong(parts[1]);
if (parts.length == 4) {
lastSeen = Long.parseLong(parts[2]);
lastSeenOffsetMinutes = Integer.parseInt(parts[3]);
} else {
lastSeen = firstSeen;
lastSeenOffsetMinutes = currentTimezoneOffsetMinutes();
}
} catch (NumberFormatException ignored) {
continue;
}
if (firstSeen <= 0L) {
if (firstSeen <= 0L || lastSeen <= 0L) {
continue;
}
Long existing = out.get(pkg);
if (existing == null || firstSeen < existing) {
out.put(pkg, firstSeen);
SeenApp existing = out.get(pkg);
if (existing == null) {
out.put(pkg, new SeenApp(pkg, firstSeen, lastSeen, lastSeenOffsetMinutes));
} else {
long mergedFirstSeen = Math.min(existing.firstSeenMs, firstSeen);
long mergedLastSeen = existing.lastSeenMs;
int mergedOffset = existing.lastSeenTimezoneOffsetMinutes;
if (lastSeen >= mergedLastSeen) {
mergedLastSeen = lastSeen;
mergedOffset = lastSeenOffsetMinutes;
}
out.put(pkg, new SeenApp(pkg, mergedFirstSeen, mergedLastSeen, mergedOffset));
}
}
return out;
}
public static Set<String> encodeSeenSession(Map<String, Long> map) {
public static Set<String> encodeSeenSession(Map<String, SeenApp> map) {
Set<String> out = new HashSet<>();
if (map == null || map.isEmpty()) {
return out;
}
for (Map.Entry<String, Long> e : map.entrySet()) {
for (Map.Entry<String, SeenApp> e : map.entrySet()) {
String pkg = e.getKey();
Long firstSeenObj = e.getValue();
if (!isValidPackageName(pkg) || firstSeenObj == null) {
SeenApp app = e.getValue();
if (!isValidPackageName(pkg) || app == null) {
continue;
}
long firstSeen = firstSeenObj;
long firstSeen = app.firstSeenMs;
long lastSeen = app.lastSeenMs;
if (firstSeen <= 0L) {
continue;
}
out.add(pkg + "|" + firstSeen);
if (lastSeen <= 0L) {
lastSeen = firstSeen;
}
out.add(pkg + "|" + firstSeen + "|" + lastSeen + "|"
+ app.lastSeenTimezoneOffsetMinutes);
}
return out;
}
@@ -424,10 +470,17 @@ public final class AppIconRules {
public static final class SeenApp {
public final String packageName;
public final long firstSeenMs;
public final long lastSeenMs;
public final int lastSeenTimezoneOffsetMinutes;
public SeenApp(String packageName, long firstSeenMs) {
public SeenApp(String packageName,
long firstSeenMs,
long lastSeenMs,
int lastSeenTimezoneOffsetMinutes) {
this.packageName = packageName;
this.firstSeenMs = firstSeenMs;
this.lastSeenMs = lastSeenMs;
this.lastSeenTimezoneOffsetMinutes = lastSeenTimezoneOffsetMinutes;
}
}
@@ -459,18 +512,23 @@ public final class AppIconRules {
}
}
public static List<SeenApp> sortSeenOldestFirst(Map<String, Long> seen) {
public static int currentTimezoneOffsetMinutes() {
long now = System.currentTimeMillis();
return TimeZone.getDefault().getOffset(now) / 60000;
}
public static List<SeenApp> sortSeenOldestFirst(Map<String, SeenApp> seen) {
List<SeenApp> out = new ArrayList<>();
if (seen == null || seen.isEmpty()) {
return out;
}
for (Map.Entry<String, Long> e : seen.entrySet()) {
for (Map.Entry<String, SeenApp> e : seen.entrySet()) {
String pkg = e.getKey();
Long firstSeen = e.getValue();
if (!isValidPackageName(pkg) || firstSeen == null || firstSeen <= 0L) {
SeenApp app = e.getValue();
if (!isValidPackageName(pkg) || app == null || app.firstSeenMs <= 0L) {
continue;
}
out.add(new SeenApp(pkg, firstSeen));
out.add(app);
}
out.sort((a, b) -> Long.compare(a.firstSeenMs, b.firstSeenMs));
return out;
@@ -92,6 +92,7 @@ public final class SbtDefaults {
public static final boolean DEBUG_VISUALIZE_CLOCK_CONTAINER_DEFAULT = false;
public static final boolean DEBUG_VISUALIZE_CHIP_CONTAINER_DEFAULT = false;
public static final boolean DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT = false;
public static final boolean DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS_DEFAULT = false;
public static final int LAYOUT_PADDING_CUTOUT_PX_DEFAULT = 0;
public static final int LAYOUT_PADDING_LEFT_PX_DEFAULT = 0;
@@ -114,6 +115,7 @@ public final class SbtDefaults {
public static final String LAYOUT_CHIP_MIDDLE_SIDE_DEFAULT = "left";
public static final boolean LAYOUT_CHIP_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT = true;
public static final int LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT = 0;
public static final int LAYOUT_CHIP_HEIGHT_STEPS_DEFAULT = 52;
public static final String LAYOUT_NOTIF_POSITION_DEFAULT = "left";
public static final String LAYOUT_NOTIF_SORT_ORDER_DEFAULT = "automatic";
public static final boolean LAYOUT_NOTIF_SHOW_DOT_IF_TRUNCATED_DEFAULT = true;
@@ -124,6 +126,11 @@ public final class SbtDefaults {
public static final String LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT = "left";
public static final boolean LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT = true;
public static final int LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT = 0;
public static final int LAYOUT_NOTIF_ICON_HEIGHT_STEPS_DEFAULT = 46;
public static final int LAYOUT_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT = 46;
public static final int LAYOUT_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT = 46;
public static final int LAYOUT_CARDS_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT = 59;
public static final int LAYOUT_CARDS_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT = 65;
public static final String LAYOUT_STATUS_POSITION_DEFAULT = "right";
public static final boolean LAYOUT_STATUS_ENABLED_AOD_DEFAULT = true;
public static final boolean LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT = true;
@@ -133,6 +140,9 @@ public final class SbtDefaults {
public static final String LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT = "left";
public static final boolean LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT = true;
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_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;
public static final int LAYOUT_ICON_CONTAINER_COUNT_MAX = 3;
@@ -35,6 +35,8 @@ public final class SbtSettings {
"debug_visualize_chip_container";
public static final String KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT =
"debug_visualize_camera_cutout";
public static final String KEY_DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS =
"debug_visualize_aod_stock_containers";
public static final String KEY_LOCKED_NOTIFICATION_MODE =
"locked_notification_mode";
public static final String KEY_UNLOCKED_MAX_ICONS_PER_ROW = "unlocked_max_icons_per_row";
@@ -50,10 +52,12 @@ public final class SbtSettings {
public static final String KEY_CARDS_AOD_MAX_ROWS = "cards_aod_max_rows";
public static final String KEY_CARDS_AOD_EVEN_DISTRIBUTION = "cards_aod_even_distribution";
public static final String KEY_CARDS_AOD_LIMIT_TO_WIDTH = "cards_aod_limit_to_width";
public static final String KEY_CARDS_AOD_ICON_HEIGHT_STEPS = "cards_aod_icon_height_steps";
public static final String KEY_CARDS_LOCK_MAX_ICONS_PER_ROW = "cards_lock_max_icons_per_row";
public static final String KEY_CARDS_LOCK_MAX_ROWS = "cards_lock_max_rows";
public static final String KEY_CARDS_LOCK_EVEN_DISTRIBUTION = "cards_lock_even_distribution";
public static final String KEY_CARDS_LOCK_LIMIT_TO_WIDTH = "cards_lock_limit_to_width";
public static final String KEY_CARDS_LOCK_ICON_HEIGHT_STEPS = "cards_lock_icon_height_steps";
public static final String KEY_STATUS_CHIPS_HIDE_ALL = "status_chips_hide_all";
public static final String KEY_STATUS_CHIPS_HIDE_MEDIA_UNLOCKED = "status_chips_hide_media_unlocked";
public static final String KEY_STATUS_CHIPS_HIDE_NAV_UNLOCKED = "status_chips_hide_nav_unlocked";
@@ -105,6 +109,7 @@ public final class SbtSettings {
public static final String KEY_LAYOUT_CHIP_MIDDLE_SIDE = "layout_chip_middle_side";
public static final String KEY_LAYOUT_CHIP_MIDDLE_AUTO_NEAREST_SIDE = "layout_chip_middle_auto_nearest_side";
public static final String KEY_LAYOUT_CHIP_VERTICAL_OFFSET_PX = "layout_chip_vertical_offset_px";
public static final String KEY_LAYOUT_CHIP_HEIGHT_STEPS = "layout_chip_height_steps";
public static final String KEY_LAYOUT_NOTIF_POSITION = "layout_notif_position";
public static final String KEY_LAYOUT_NOTIF_SORT_ORDER = "layout_notif_sort_order";
public static final String KEY_LAYOUT_NOTIF_SHOW_DOT_IF_TRUNCATED = "layout_notif_show_dot_if_truncated";
@@ -115,6 +120,7 @@ public final class SbtSettings {
public static final String KEY_LAYOUT_NOTIF_MIDDLE_SIDE = "layout_notif_middle_side";
public static final String KEY_LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE = "layout_notif_middle_auto_nearest_side";
public static final String KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX = "layout_notif_vertical_offset_px";
public static final String KEY_LAYOUT_NOTIF_ICON_HEIGHT_STEPS = "layout_notif_icon_height_steps";
public static final String KEY_LAYOUT_STATUS_POSITION = "layout_status_position";
public static final String KEY_LAYOUT_STATUS_ENABLED_AOD = "layout_status_enabled_aod";
public static final String KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN = "layout_status_enabled_lockscreen";
@@ -124,6 +130,7 @@ public final class SbtSettings {
public static final String KEY_LAYOUT_STATUS_MIDDLE_SIDE = "layout_status_middle_side";
public static final String KEY_LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE = "layout_status_middle_auto_nearest_side";
public static final String KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX = "layout_status_vertical_offset_px";
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_APP_ICON_BLOCKED_MODES = AppIconRules.PREF_KEY_BLOCKED_MODES;
@@ -177,6 +184,30 @@ public final class SbtSettings {
return suffixedKey("layout_lock_notifications_vertical_offset_px", index);
}
public static String layoutLockNotifIconHeightKey() {
return "layout_lock_notifications_icon_height_steps";
}
public static String layoutAodNotifPositionKey(int index) {
return suffixedKey("layout_aod_notifications_position", index);
}
public static String layoutAodNotifMiddleSideKey(int index) {
return suffixedKey("layout_aod_notifications_middle_side", index);
}
public static String layoutAodNotifMiddleAutoNearestSideKey(int index) {
return suffixedKey("layout_aod_notifications_middle_auto_nearest_side", index);
}
public static String layoutAodNotifVerticalOffsetKey(int index) {
return suffixedKey("layout_aod_notifications_vertical_offset_px", index);
}
public static String layoutAodNotifIconHeightKey() {
return "layout_aod_notifications_icon_height_steps";
}
public static String layoutLockStatusPositionKey(int index) {
return suffixedKey("layout_lock_status_position", index);
}
@@ -193,6 +224,30 @@ public final class SbtSettings {
return suffixedKey("layout_lock_status_vertical_offset_px", index);
}
public static String layoutLockStatusIconHeightKey() {
return "layout_lock_status_icon_height_steps";
}
public static String layoutAodStatusPositionKey(int index) {
return suffixedKey("layout_aod_status_position", index);
}
public static String layoutAodStatusMiddleSideKey(int index) {
return suffixedKey("layout_aod_status_middle_side", index);
}
public static String layoutAodStatusMiddleAutoNearestSideKey(int index) {
return suffixedKey("layout_aod_status_middle_auto_nearest_side", index);
}
public static String layoutAodStatusVerticalOffsetKey(int index) {
return suffixedKey("layout_aod_status_vertical_offset_px", index);
}
public static String layoutAodStatusIconHeightKey() {
return "layout_aod_status_icon_height_steps";
}
private static String suffixedKey(String base, int index) {
return index <= 0 ? base : base + "_" + (index + 1);
}
@@ -209,10 +264,12 @@ public final class SbtSettings {
public final int cardsAodMaxRows;
public final boolean cardsAodEvenDistribution;
public final boolean cardsAodLimitToWidth;
public final int cardsAodIconHeightSteps;
public final int cardsLockMaxIconsPerRow;
public final int cardsLockMaxRows;
public final boolean cardsLockEvenDistribution;
public final boolean cardsLockLimitToWidth;
public final int cardsLockIconHeightSteps;
public final boolean keepAodVisibleDuringCall;
public final boolean batteryLockscreenUnpluggedPercent;
public final boolean batteryAodUnpluggedPercent;
@@ -259,6 +316,7 @@ public final class SbtSettings {
public final String layoutChipMiddleSide;
public final boolean layoutChipMiddleAutoNearestSide;
public final int layoutChipVerticalOffsetPx;
public final int layoutChipHeightSteps;
public final String layoutNotifPosition;
public final String layoutNotifSortOrder;
public final boolean layoutNotifShowDotIfTruncated;
@@ -275,9 +333,17 @@ public final class SbtSettings {
public final String[] layoutNotifLockMiddleSides;
public final boolean[] layoutNotifLockMiddleAutoNearestSides;
public final int[] layoutNotifLockVerticalOffsetsPx;
public final int layoutNotifAodCount;
public final String[] layoutNotifAodPositions;
public final String[] layoutNotifAodMiddleSides;
public final boolean[] layoutNotifAodMiddleAutoNearestSides;
public final int[] layoutNotifAodVerticalOffsetsPx;
public final String layoutNotifMiddleSide;
public final boolean layoutNotifMiddleAutoNearestSide;
public final int layoutNotifVerticalOffsetPx;
public final int layoutNotifIconHeightSteps;
public final int layoutNotifLockIconHeightSteps;
public final int layoutNotifAodIconHeightSteps;
public final String layoutStatusPosition;
public final boolean layoutStatusEnabledAod;
public final boolean layoutStatusEnabledLockscreen;
@@ -292,10 +358,18 @@ public final class SbtSettings {
public final String[] layoutStatusLockMiddleSides;
public final boolean[] layoutStatusLockMiddleAutoNearestSides;
public final int[] layoutStatusLockVerticalOffsetsPx;
public final int layoutStatusAodCount;
public final String[] layoutStatusAodPositions;
public final String[] layoutStatusAodMiddleSides;
public final boolean[] layoutStatusAodMiddleAutoNearestSides;
public final int[] layoutStatusAodVerticalOffsetsPx;
public final boolean layoutStatusShowDotIfTruncated;
public final String layoutStatusMiddleSide;
public final boolean layoutStatusMiddleAutoNearestSide;
public final int layoutStatusVerticalOffsetPx;
public final int layoutStatusIconHeightSteps;
public final int layoutStatusLockIconHeightSteps;
public final int layoutStatusAodIconHeightSteps;
public final Map<String, String> clockCameraTypeOverrides;
public final Map<String, Integer> systemIconBlockedModes;
public final boolean statusChipsHideAll;
@@ -310,6 +384,7 @@ public final class SbtSettings {
public final boolean debugVisualizeClockContainer;
public final boolean debugVisualizeChipContainer;
public final boolean debugVisualizeCameraCutout;
public final boolean debugVisualizeAodStockContainers;
private SbtSettings(boolean enableAodCutoutLimit,
boolean enableLockCutoutLimit,
@@ -324,10 +399,12 @@ public final class SbtSettings {
int cardsAodMaxRows,
boolean cardsAodEvenDistribution,
boolean cardsAodLimitToWidth,
int cardsAodIconHeightSteps,
int cardsLockMaxIconsPerRow,
int cardsLockMaxRows,
boolean cardsLockEvenDistribution,
boolean cardsLockLimitToWidth,
int cardsLockIconHeightSteps,
boolean keepAodVisibleDuringCall,
boolean batteryLockscreenUnpluggedPercent,
boolean batteryAodUnpluggedPercent,
@@ -374,6 +451,7 @@ public final class SbtSettings {
String layoutChipMiddleSide,
boolean layoutChipMiddleAutoNearestSide,
int layoutChipVerticalOffsetPx,
int layoutChipHeightSteps,
String layoutNotifPosition,
String layoutNotifSortOrder,
boolean layoutNotifShowDotIfTruncated,
@@ -383,6 +461,9 @@ public final class SbtSettings {
String layoutNotifMiddleSide,
boolean layoutNotifMiddleAutoNearestSide,
int layoutNotifVerticalOffsetPx,
int layoutNotifIconHeightSteps,
int layoutNotifLockIconHeightSteps,
int layoutNotifAodIconHeightSteps,
int layoutNotifUnlockedCount,
String[] layoutNotifPositions,
String[] layoutNotifMiddleSides,
@@ -393,6 +474,11 @@ public final class SbtSettings {
String[] layoutNotifLockMiddleSides,
boolean[] layoutNotifLockMiddleAutoNearestSides,
int[] layoutNotifLockVerticalOffsetsPx,
int layoutNotifAodCount,
String[] layoutNotifAodPositions,
String[] layoutNotifAodMiddleSides,
boolean[] layoutNotifAodMiddleAutoNearestSides,
int[] layoutNotifAodVerticalOffsetsPx,
String layoutStatusPosition,
boolean layoutStatusEnabledAod,
boolean layoutStatusEnabledLockscreen,
@@ -401,6 +487,9 @@ public final class SbtSettings {
String layoutStatusMiddleSide,
boolean layoutStatusMiddleAutoNearestSide,
int layoutStatusVerticalOffsetPx,
int layoutStatusIconHeightSteps,
int layoutStatusLockIconHeightSteps,
int layoutStatusAodIconHeightSteps,
int layoutStatusUnlockedCount,
String[] layoutStatusPositions,
String[] layoutStatusMiddleSides,
@@ -411,6 +500,11 @@ public final class SbtSettings {
String[] layoutStatusLockMiddleSides,
boolean[] layoutStatusLockMiddleAutoNearestSides,
int[] layoutStatusLockVerticalOffsetsPx,
int layoutStatusAodCount,
String[] layoutStatusAodPositions,
String[] layoutStatusAodMiddleSides,
boolean[] layoutStatusAodMiddleAutoNearestSides,
int[] layoutStatusAodVerticalOffsetsPx,
Map<String, String> clockCameraTypeOverrides,
Map<String, Integer> systemIconBlockedModes,
boolean statusChipsHideAll,
@@ -424,7 +518,8 @@ public final class SbtSettings {
boolean debugVisualizeStatusContainer,
boolean debugVisualizeClockContainer,
boolean debugVisualizeChipContainer,
boolean debugVisualizeCameraCutout) {
boolean debugVisualizeCameraCutout,
boolean debugVisualizeAodStockContainers) {
this.enableAodCutoutLimit = enableAodCutoutLimit;
this.enableLockCutoutLimit = enableLockCutoutLimit;
this.unlockedMaxIconsPerRow = unlockedMaxIconsPerRow;
@@ -438,10 +533,12 @@ public final class SbtSettings {
this.cardsAodMaxRows = cardsAodMaxRows;
this.cardsAodEvenDistribution = cardsAodEvenDistribution;
this.cardsAodLimitToWidth = cardsAodLimitToWidth;
this.cardsAodIconHeightSteps = clampIconHeightSteps(cardsAodIconHeightSteps);
this.cardsLockMaxIconsPerRow = cardsLockMaxIconsPerRow;
this.cardsLockMaxRows = cardsLockMaxRows;
this.cardsLockEvenDistribution = cardsLockEvenDistribution;
this.cardsLockLimitToWidth = cardsLockLimitToWidth;
this.cardsLockIconHeightSteps = clampIconHeightSteps(cardsLockIconHeightSteps);
this.keepAodVisibleDuringCall = keepAodVisibleDuringCall;
this.batteryLockscreenUnpluggedPercent = batteryLockscreenUnpluggedPercent;
this.batteryAodUnpluggedPercent = batteryAodUnpluggedPercent;
@@ -550,6 +647,7 @@ public final class SbtSettings {
layoutChipVerticalOffsetPx,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutChipHeightSteps = clampIconHeightSteps(layoutChipHeightSteps);
this.layoutNotifPosition = layoutNotifPosition != null
? layoutNotifPosition
: SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT;
@@ -568,6 +666,9 @@ public final class SbtSettings {
layoutNotifVerticalOffsetPx,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutNotifIconHeightSteps = clampIconHeightSteps(layoutNotifIconHeightSteps);
this.layoutNotifLockIconHeightSteps = clampIconHeightSteps(layoutNotifLockIconHeightSteps);
this.layoutNotifAodIconHeightSteps = clampIconHeightSteps(layoutNotifAodIconHeightSteps);
this.layoutNotifUnlockedCount = Math.max(
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN,
Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, layoutNotifUnlockedCount));
@@ -608,6 +709,25 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutNotifAodCount = clampIconContainerCount(layoutNotifAodCount);
this.layoutNotifAodPositions = sanitizeStringArray(
layoutNotifAodPositions,
this.layoutNotifPosition,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutNotifAodMiddleSides = sanitizeStringArray(
layoutNotifAodMiddleSides,
this.layoutNotifMiddleSide,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutNotifAodMiddleAutoNearestSides = sanitizeBooleanArray(
layoutNotifAodMiddleAutoNearestSides,
this.layoutNotifMiddleAutoNearestSide,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutNotifAodVerticalOffsetsPx = sanitizeIntArray(
layoutNotifAodVerticalOffsetsPx,
this.layoutNotifVerticalOffsetPx,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutStatusPosition = layoutStatusPosition != null
? layoutStatusPosition
: SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT;
@@ -623,6 +743,9 @@ public final class SbtSettings {
layoutStatusVerticalOffsetPx,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutStatusIconHeightSteps = clampIconHeightSteps(layoutStatusIconHeightSteps);
this.layoutStatusLockIconHeightSteps = clampIconHeightSteps(layoutStatusLockIconHeightSteps);
this.layoutStatusAodIconHeightSteps = clampIconHeightSteps(layoutStatusAodIconHeightSteps);
this.layoutStatusUnlockedCount = Math.max(
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN,
Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, layoutStatusUnlockedCount));
@@ -663,6 +786,25 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutStatusAodCount = clampIconContainerCount(layoutStatusAodCount);
this.layoutStatusAodPositions = sanitizeStringArray(
layoutStatusAodPositions,
this.layoutStatusPosition,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutStatusAodMiddleSides = sanitizeStringArray(
layoutStatusAodMiddleSides,
this.layoutStatusMiddleSide,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutStatusAodMiddleAutoNearestSides = sanitizeBooleanArray(
layoutStatusAodMiddleAutoNearestSides,
this.layoutStatusMiddleAutoNearestSide,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX);
this.layoutStatusAodVerticalOffsetsPx = sanitizeIntArray(
layoutStatusAodVerticalOffsetsPx,
this.layoutStatusVerticalOffsetPx,
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.clockCameraTypeOverrides = clockCameraTypeOverrides != null
? Collections.unmodifiableMap(new HashMap<>(clockCameraTypeOverrides))
: Collections.emptyMap();
@@ -694,6 +836,7 @@ public final class SbtSettings {
this.debugVisualizeClockContainer = debugVisualizeClockContainer;
this.debugVisualizeChipContainer = debugVisualizeChipContainer;
this.debugVisualizeCameraCutout = debugVisualizeCameraCutout;
this.debugVisualizeAodStockContainers = debugVisualizeAodStockContainers;
}
public static SbtSettings defaults() {
@@ -711,10 +854,12 @@ public final class SbtSettings {
SbtDefaults.CARDS_AOD_MAX_ROWS_DEFAULT,
SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT,
SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT,
SbtDefaults.LAYOUT_CARDS_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT,
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.CARDS_LOCK_MAX_ROWS_DEFAULT,
SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT,
SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT,
SbtDefaults.LAYOUT_CARDS_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT,
SbtDefaults.AOD_KEEP_VISIBLE_DURING_CALL_DEFAULT,
SbtDefaults.BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT_DEFAULT,
SbtDefaults.BATTERY_AOD_UNPLUGGED_PERCENT_DEFAULT,
@@ -761,6 +906,7 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_CHIP_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_CHIP_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_CHIP_HEIGHT_STEPS_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_SORT_ORDER_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_SHOW_DOT_IF_TRUNCATED_DEFAULT,
@@ -770,6 +916,14 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_ICON_HEIGHT_STEPS_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
null,
null,
null,
null,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
null,
null,
@@ -788,6 +942,14 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
null,
null,
null,
null,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
null,
null,
@@ -811,7 +973,8 @@ public final class SbtSettings {
SbtDefaults.DEBUG_VISUALIZE_STATUS_CONTAINER_DEFAULT,
SbtDefaults.DEBUG_VISUALIZE_CLOCK_CONTAINER_DEFAULT,
SbtDefaults.DEBUG_VISUALIZE_CHIP_CONTAINER_DEFAULT,
SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT
SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT,
SbtDefaults.DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS_DEFAULT
);
}
@@ -872,20 +1035,25 @@ public final class SbtSettings {
if (context == null || !AppIconRules.isValidPackageName(packageName)) {
return;
}
long seenMs = firstSeenMs > 0L ? firstSeenMs : System.currentTimeMillis();
int offsetMinutes = AppIconRules.currentTimezoneOffsetMinutes();
try {
if (!MODULE_PACKAGE.equals(context.getPackageName()) && hasSettingsProvider(context)) {
Uri uri = Uri.parse("content://" + SbtSettingsProvider.AUTHORITY);
Bundle extras = new Bundle();
extras.putString(AppIconRules.EXTRA_PACKAGE, packageName);
extras.putLong(AppIconRules.EXTRA_FIRST_SEEN_MS, firstSeenMs);
extras.putLong(AppIconRules.EXTRA_FIRST_SEEN_MS, seenMs);
extras.putLong(AppIconRules.EXTRA_LAST_SEEN_MS, seenMs);
extras.putInt(AppIconRules.EXTRA_LAST_SEEN_TIMEZONE_OFFSET_MINUTES, offsetMinutes);
context.getContentResolver()
.call(uri, SbtSettingsProvider.METHOD_NOTE_SEEN_ICON_APP, null, extras);
return;
}
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
Map<String, Long> seen = AppIconRules.loadSeenSession(prefs);
AppIconRules.noteSeen(seen, packageName, firstSeenMs);
Map<String, AppIconRules.SeenApp> seen = AppIconRules.loadSeenSession(prefs);
if (AppIconRules.noteSeen(seen, packageName, seenMs, offsetMinutes)) {
AppIconRules.saveSeenSession(prefs, seen);
}
} catch (Throwable ignored) {
// This list is advisory UI state; rendering should never fail because it cannot be updated.
}
@@ -932,6 +1100,10 @@ public final class SbtSettings {
SbtDefaults.CARDS_AOD_MAX_ROWS_MAX),
prefs.getBoolean(KEY_CARDS_AOD_EVEN_DISTRIBUTION, SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT),
prefs.getBoolean(KEY_CARDS_AOD_LIMIT_TO_WIDTH, SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT),
clampInt(prefs.getInt(KEY_CARDS_AOD_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_CARDS_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
clampInt(prefs.getInt(KEY_CARDS_LOCK_MAX_ICONS_PER_ROW, SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT),
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MIN,
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MAX),
@@ -940,6 +1112,10 @@ public final class SbtSettings {
SbtDefaults.CARDS_LOCK_MAX_ROWS_MAX),
prefs.getBoolean(KEY_CARDS_LOCK_EVEN_DISTRIBUTION, SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT),
prefs.getBoolean(KEY_CARDS_LOCK_LIMIT_TO_WIDTH, SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT),
clampInt(prefs.getInt(KEY_CARDS_LOCK_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_CARDS_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
prefs.getBoolean(KEY_AOD_KEEP_VISIBLE_DURING_CALL, SbtDefaults.AOD_KEEP_VISIBLE_DURING_CALL_DEFAULT),
prefs.getBoolean(KEY_BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT, SbtDefaults.BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT_DEFAULT),
prefs.getBoolean(KEY_BATTERY_AOD_UNPLUGGED_PERCENT, SbtDefaults.BATTERY_AOD_UNPLUGGED_PERCENT_DEFAULT),
@@ -1007,6 +1183,8 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX),
clampIconHeightSteps(prefs.getInt(KEY_LAYOUT_CHIP_HEIGHT_STEPS,
SbtDefaults.LAYOUT_CHIP_HEIGHT_STEPS_DEFAULT)),
prefs.getString(KEY_LAYOUT_NOTIF_POSITION, SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT),
prefs.getString(KEY_LAYOUT_NOTIF_SORT_ORDER, SbtDefaults.LAYOUT_NOTIF_SORT_ORDER_DEFAULT),
prefs.getBoolean(KEY_LAYOUT_NOTIF_SHOW_DOT_IF_TRUNCATED, SbtDefaults.LAYOUT_NOTIF_SHOW_DOT_IF_TRUNCATED_DEFAULT),
@@ -1019,6 +1197,18 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX),
clampInt(prefs.getInt(KEY_LAYOUT_NOTIF_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_NOTIF_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
clampInt(prefs.getInt(layoutLockNotifIconHeightKey(),
SbtDefaults.LAYOUT_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
clampInt(prefs.getInt(layoutAodNotifIconHeightKey(),
SbtDefaults.LAYOUT_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
readIconContainerCount(
prefs,
KEY_LAYOUT_NOTIF_UNLOCKED_COUNT,
@@ -1063,6 +1253,28 @@ public final class SbtSettings {
prefs,
SbtSettings::layoutLockNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
readIconContainerCount(
prefs,
"layout_aod_notifications_count",
KEY_LAYOUT_NOTIF_ENABLED_AOD,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT),
readStringArrayFromPrefs(
prefs,
SbtSettings::layoutAodNotifPositionKey,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT),
readStringArrayFromPrefs(
prefs,
SbtSettings::layoutAodNotifMiddleSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT),
readBooleanArrayFromPrefs(
prefs,
SbtSettings::layoutAodNotifMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT),
readIntArrayFromPrefs(
prefs,
SbtSettings::layoutAodNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
prefs.getString(KEY_LAYOUT_STATUS_POSITION, SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT),
prefs.getBoolean(KEY_LAYOUT_STATUS_ENABLED_AOD, SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT),
prefs.getBoolean(KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT),
@@ -1074,6 +1286,18 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX),
clampInt(prefs.getInt(KEY_LAYOUT_STATUS_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
clampInt(prefs.getInt(layoutLockStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
clampInt(prefs.getInt(layoutAodStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
readIconContainerCount(
prefs,
KEY_LAYOUT_STATUS_UNLOCKED_COUNT,
@@ -1118,6 +1342,28 @@ public final class SbtSettings {
prefs,
SbtSettings::layoutLockStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
readIconContainerCount(
prefs,
"layout_aod_status_count",
KEY_LAYOUT_STATUS_ENABLED_AOD,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT),
readStringArrayFromPrefs(
prefs,
SbtSettings::layoutAodStatusPositionKey,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT),
readStringArrayFromPrefs(
prefs,
SbtSettings::layoutAodStatusMiddleSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT),
readBooleanArrayFromPrefs(
prefs,
SbtSettings::layoutAodStatusMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT),
readIntArrayFromPrefs(
prefs,
SbtSettings::layoutAodStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
ClockCameraTypeSupport.parseOverrides(copyStringSet(
prefs.getStringSet(KEY_CLOCK_CAMERA_TYPE_OVERRIDES, Collections.emptySet()))),
systemIconBlockedModes,
@@ -1144,7 +1390,10 @@ public final class SbtSettings {
SbtDefaults.DEBUG_VISUALIZE_CHIP_CONTAINER_DEFAULT),
prefs.getBoolean(
KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT,
SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT)
SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT),
prefs.getBoolean(
KEY_DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS,
SbtDefaults.DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS_DEFAULT)
);
}
@@ -1181,6 +1430,10 @@ public final class SbtSettings {
SbtDefaults.CARDS_AOD_MAX_ROWS_MAX),
bundle.getBoolean(KEY_CARDS_AOD_EVEN_DISTRIBUTION, SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT),
bundle.getBoolean(KEY_CARDS_AOD_LIMIT_TO_WIDTH, SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT),
clampInt(bundle.getInt(KEY_CARDS_AOD_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_CARDS_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
clampInt(bundle.getInt(KEY_CARDS_LOCK_MAX_ICONS_PER_ROW, SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT),
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MIN,
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MAX),
@@ -1189,6 +1442,10 @@ public final class SbtSettings {
SbtDefaults.CARDS_LOCK_MAX_ROWS_MAX),
bundle.getBoolean(KEY_CARDS_LOCK_EVEN_DISTRIBUTION, SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT),
bundle.getBoolean(KEY_CARDS_LOCK_LIMIT_TO_WIDTH, SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT),
clampInt(bundle.getInt(KEY_CARDS_LOCK_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_CARDS_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
bundle.getBoolean(KEY_AOD_KEEP_VISIBLE_DURING_CALL, SbtDefaults.AOD_KEEP_VISIBLE_DURING_CALL_DEFAULT),
bundle.getBoolean(KEY_BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT, SbtDefaults.BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT_DEFAULT),
bundle.getBoolean(KEY_BATTERY_AOD_UNPLUGGED_PERCENT, SbtDefaults.BATTERY_AOD_UNPLUGGED_PERCENT_DEFAULT),
@@ -1254,6 +1511,8 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX),
clampIconHeightSteps(bundle.getInt(KEY_LAYOUT_CHIP_HEIGHT_STEPS,
SbtDefaults.LAYOUT_CHIP_HEIGHT_STEPS_DEFAULT)),
bundle.getString(KEY_LAYOUT_NOTIF_POSITION, SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT),
bundle.getString(KEY_LAYOUT_NOTIF_SORT_ORDER, SbtDefaults.LAYOUT_NOTIF_SORT_ORDER_DEFAULT),
bundle.getBoolean(KEY_LAYOUT_NOTIF_SHOW_DOT_IF_TRUNCATED, SbtDefaults.LAYOUT_NOTIF_SHOW_DOT_IF_TRUNCATED_DEFAULT),
@@ -1266,6 +1525,18 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX),
clampInt(bundle.getInt(KEY_LAYOUT_NOTIF_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_NOTIF_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
clampInt(bundle.getInt(layoutLockNotifIconHeightKey(),
SbtDefaults.LAYOUT_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
clampInt(bundle.getInt(layoutAodNotifIconHeightKey(),
SbtDefaults.LAYOUT_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
readIconContainerCount(
bundle,
KEY_LAYOUT_NOTIF_UNLOCKED_COUNT,
@@ -1310,6 +1581,28 @@ public final class SbtSettings {
bundle,
SbtSettings::layoutLockNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
readIconContainerCount(
bundle,
"layout_aod_notifications_count",
KEY_LAYOUT_NOTIF_ENABLED_AOD,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT),
readStringArrayFromBundle(
bundle,
SbtSettings::layoutAodNotifPositionKey,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT),
readStringArrayFromBundle(
bundle,
SbtSettings::layoutAodNotifMiddleSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT),
readBooleanArrayFromBundle(
bundle,
SbtSettings::layoutAodNotifMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT),
readIntArrayFromBundle(
bundle,
SbtSettings::layoutAodNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
bundle.getString(KEY_LAYOUT_STATUS_POSITION, SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT),
bundle.getBoolean(KEY_LAYOUT_STATUS_ENABLED_AOD, SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT),
bundle.getBoolean(KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT),
@@ -1321,6 +1614,18 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX),
clampInt(bundle.getInt(KEY_LAYOUT_STATUS_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
clampInt(bundle.getInt(layoutLockStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
clampInt(bundle.getInt(layoutAodStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
readIconContainerCount(
bundle,
KEY_LAYOUT_STATUS_UNLOCKED_COUNT,
@@ -1365,6 +1670,28 @@ public final class SbtSettings {
bundle,
SbtSettings::layoutLockStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
readIconContainerCount(
bundle,
"layout_aod_status_count",
KEY_LAYOUT_STATUS_ENABLED_AOD,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT),
readStringArrayFromBundle(
bundle,
SbtSettings::layoutAodStatusPositionKey,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT),
readStringArrayFromBundle(
bundle,
SbtSettings::layoutAodStatusMiddleSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT),
readBooleanArrayFromBundle(
bundle,
SbtSettings::layoutAodStatusMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT),
readIntArrayFromBundle(
bundle,
SbtSettings::layoutAodStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
ClockCameraTypeSupport.parseOverrides(readStringSetFromBundle(bundle, KEY_CLOCK_CAMERA_TYPE_OVERRIDES)),
systemIconBlockedModes,
bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_ALL, false),
@@ -1388,7 +1715,10 @@ public final class SbtSettings {
SbtDefaults.DEBUG_VISUALIZE_CHIP_CONTAINER_DEFAULT),
bundle.getBoolean(
KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT,
SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT)
SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT),
bundle.getBoolean(
KEY_DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS,
SbtDefaults.DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS_DEFAULT)
);
}
@@ -1406,6 +1736,12 @@ public final class SbtSettings {
Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, value));
}
private static int clampIconHeightSteps(int value) {
return Math.max(
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
Math.min(SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX, value));
}
private static int readIconContainerCount(
SharedPreferences prefs,
String countKey,
@@ -71,6 +71,9 @@ public class SbtSettingsProvider extends ContentProvider {
prefs.getBoolean(SbtSettings.KEY_CARDS_AOD_EVEN_DISTRIBUTION, SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT));
out.putBoolean(SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH,
prefs.getBoolean(SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH, SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT));
out.putInt(SbtSettings.KEY_CARDS_AOD_ICON_HEIGHT_STEPS,
prefs.getInt(SbtSettings.KEY_CARDS_AOD_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_CARDS_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT));
out.putInt(SbtSettings.KEY_CARDS_LOCK_MAX_ICONS_PER_ROW,
prefs.getInt(SbtSettings.KEY_CARDS_LOCK_MAX_ICONS_PER_ROW, SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT));
out.putInt(SbtSettings.KEY_CARDS_LOCK_MAX_ROWS,
@@ -79,6 +82,9 @@ public class SbtSettingsProvider extends ContentProvider {
prefs.getBoolean(SbtSettings.KEY_CARDS_LOCK_EVEN_DISTRIBUTION, SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT));
out.putBoolean(SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH,
prefs.getBoolean(SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH, SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT));
out.putInt(SbtSettings.KEY_CARDS_LOCK_ICON_HEIGHT_STEPS,
prefs.getInt(SbtSettings.KEY_CARDS_LOCK_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_CARDS_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT));
out.putBoolean(SbtSettings.KEY_AOD_KEEP_VISIBLE_DURING_CALL,
prefs.getBoolean(SbtSettings.KEY_AOD_KEEP_VISIBLE_DURING_CALL, SbtDefaults.AOD_KEEP_VISIBLE_DURING_CALL_DEFAULT));
out.putBoolean(SbtSettings.KEY_BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT,
@@ -157,6 +163,9 @@ public class SbtSettingsProvider extends ContentProvider {
out.putBoolean(SbtSettings.KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT,
prefs.getBoolean(SbtSettings.KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT,
SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT));
out.putBoolean(SbtSettings.KEY_DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS,
prefs.getBoolean(SbtSettings.KEY_DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS,
SbtDefaults.DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS_DEFAULT));
out.putInt(SbtSettings.KEY_LAYOUT_PADDING_CUTOUT_PX,
prefs.getInt(SbtSettings.KEY_LAYOUT_PADDING_CUTOUT_PX,
SbtDefaults.LAYOUT_PADDING_CUTOUT_PX_DEFAULT));
@@ -216,6 +225,9 @@ public class SbtSettingsProvider extends ContentProvider {
out.putInt(SbtSettings.KEY_LAYOUT_CHIP_VERTICAL_OFFSET_PX,
prefs.getInt(SbtSettings.KEY_LAYOUT_CHIP_VERTICAL_OFFSET_PX,
SbtDefaults.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT));
out.putInt(SbtSettings.KEY_LAYOUT_CHIP_HEIGHT_STEPS,
prefs.getInt(SbtSettings.KEY_LAYOUT_CHIP_HEIGHT_STEPS,
SbtDefaults.LAYOUT_CHIP_HEIGHT_STEPS_DEFAULT));
out.putString(SbtSettings.KEY_LAYOUT_NOTIF_POSITION,
prefs.getString(SbtSettings.KEY_LAYOUT_NOTIF_POSITION,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT));
@@ -250,6 +262,9 @@ public class SbtSettingsProvider extends ContentProvider {
out.putInt(SbtSettings.KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX,
prefs.getInt(SbtSettings.KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT));
out.putInt(SbtSettings.KEY_LAYOUT_NOTIF_ICON_HEIGHT_STEPS,
prefs.getInt(SbtSettings.KEY_LAYOUT_NOTIF_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_NOTIF_ICON_HEIGHT_STEPS_DEFAULT));
putIconCopySettings(
out,
prefs,
@@ -268,6 +283,9 @@ public class SbtSettingsProvider extends ContentProvider {
SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT)
? SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT
: 0));
out.putInt(SbtSettings.layoutLockNotifIconHeightKey(),
prefs.getInt(SbtSettings.layoutLockNotifIconHeightKey(),
SbtDefaults.LAYOUT_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT));
putIconCopySettings(
out,
prefs,
@@ -279,6 +297,27 @@ public class SbtSettingsProvider extends ContentProvider {
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtSettings::layoutLockNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT);
out.putInt("layout_aod_notifications_count",
prefs.getInt("layout_aod_notifications_count",
prefs.getBoolean(
SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_AOD,
SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT)
? SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT
: 0));
out.putInt(SbtSettings.layoutAodNotifIconHeightKey(),
prefs.getInt(SbtSettings.layoutAodNotifIconHeightKey(),
SbtDefaults.LAYOUT_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT));
putIconCopySettings(
out,
prefs,
SbtSettings::layoutAodNotifPositionKey,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtSettings::layoutAodNotifMiddleSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtSettings::layoutAodNotifMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtSettings::layoutAodNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT);
out.putString(SbtSettings.KEY_LAYOUT_STATUS_POSITION,
prefs.getString(SbtSettings.KEY_LAYOUT_STATUS_POSITION,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT));
@@ -310,6 +349,9 @@ public class SbtSettingsProvider extends ContentProvider {
out.putInt(SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX,
prefs.getInt(SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT));
out.putInt(SbtSettings.KEY_LAYOUT_STATUS_ICON_HEIGHT_STEPS,
prefs.getInt(SbtSettings.KEY_LAYOUT_STATUS_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT));
putIconCopySettings(
out,
prefs,
@@ -328,6 +370,9 @@ public class SbtSettingsProvider extends ContentProvider {
SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT)
? SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT
: 0));
out.putInt(SbtSettings.layoutLockStatusIconHeightKey(),
prefs.getInt(SbtSettings.layoutLockStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT));
putIconCopySettings(
out,
prefs,
@@ -339,6 +384,27 @@ public class SbtSettingsProvider extends ContentProvider {
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtSettings::layoutLockStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT);
out.putInt("layout_aod_status_count",
prefs.getInt("layout_aod_status_count",
prefs.getBoolean(
SbtSettings.KEY_LAYOUT_STATUS_ENABLED_AOD,
SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT)
? SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT
: 0));
out.putInt(SbtSettings.layoutAodStatusIconHeightKey(),
prefs.getInt(SbtSettings.layoutAodStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT));
putIconCopySettings(
out,
prefs,
SbtSettings::layoutAodStatusPositionKey,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtSettings::layoutAodStatusMiddleSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtSettings::layoutAodStatusMiddleAutoNearestSideKey,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtSettings::layoutAodStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT);
out.putStringArrayList(
SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES,
new ArrayList<>(prefs.getStringSet(
@@ -372,17 +438,26 @@ public class SbtSettingsProvider extends ContentProvider {
}
if (METHOD_NOTE_SEEN_ICON_APP.equals(method)) {
String pkg = extras != null ? extras.getString(AppIconRules.EXTRA_PACKAGE) : null;
long firstSeenMs = extras != null
long seenMs = extras != null
? extras.getLong(AppIconRules.EXTRA_FIRST_SEEN_MS, System.currentTimeMillis())
: System.currentTimeMillis();
if (extras != null && extras.containsKey(AppIconRules.EXTRA_LAST_SEEN_MS)) {
seenMs = extras.getLong(AppIconRules.EXTRA_LAST_SEEN_MS, seenMs);
}
int offsetMinutes = extras != null
? extras.getInt(
AppIconRules.EXTRA_LAST_SEEN_TIMEZONE_OFFSET_MINUTES,
AppIconRules.currentTimezoneOffsetMinutes())
: AppIconRules.currentTimezoneOffsetMinutes();
if (!AppIconRules.isValidPackageName(pkg)) {
return Bundle.EMPTY;
}
synchronized (LOCK) {
Map<String, Long> seen = AppIconRules.loadSeenSession(prefs);
AppIconRules.noteSeen(seen, pkg, firstSeenMs);
Map<String, AppIconRules.SeenApp> seen = AppIconRules.loadSeenSession(prefs);
if (AppIconRules.noteSeen(seen, pkg, seenMs, offsetMinutes)) {
AppIconRules.saveSeenSession(prefs, seen);
}
}
return Bundle.EMPTY;
}
if (METHOD_SET_CLOCK_CAMERA_DEBUG_STATE.equals(method)) {
@@ -32,13 +32,16 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
final class HiddenAppsUiSupport {
interface RowChangedListener {
@@ -81,7 +84,7 @@ final class HiddenAppsUiSupport {
Map<String, Integer> blocked = AppIconRules.loadBlockedModes(prefs);
Map<String, ArrayList<AppIconRules.ExceptionRule>> exceptionRules =
AppIconRules.loadExceptionRules(prefs);
Map<String, Long> seen = AppIconRules.loadSeenSession(prefs);
Map<String, AppIconRules.SeenApp> seen = AppIconRules.loadSeenSession(prefs);
ArrayList<RowItem> blockedItems = new ArrayList<>();
for (Map.Entry<String, Integer> entry : blocked.entrySet()) {
@@ -96,6 +99,7 @@ final class HiddenAppsUiSupport {
item.blockLock = (mask & AppIconRules.MODE_LOCK) != 0;
item.blockUnlock = (mask & AppIconRules.MODE_UNLOCK) != 0;
item.exceptionRules = toEditableExceptionRules(exceptionRules.get(pkg));
applySeenInfo(item, seen.get(pkg));
blockedItems.add(item);
}
blockedItems.sort((a, b) -> {
@@ -122,11 +126,20 @@ final class HiddenAppsUiSupport {
}
RowItem item = buildRowItem(context, packageManager, fallbackIcon, seenApp.packageName);
item.exceptionRules = toEditableExceptionRules(exceptionRules.get(seenApp.packageName));
applySeenInfo(item, seenApp);
rows.add(item);
alreadyAdded.add(seenApp.packageName);
}
}
private static void applySeenInfo(RowItem item, AppIconRules.SeenApp seenApp) {
if (item == null || seenApp == null) {
return;
}
item.lastSeenMs = seenApp.lastSeenMs;
item.lastSeenTimezoneOffsetMinutes = seenApp.lastSeenTimezoneOffsetMinutes;
}
private static RowItem buildRowItem(Context context,
PackageManager packageManager,
Drawable fallbackIcon,
@@ -466,6 +479,41 @@ final class HiddenAppsUiSupport {
return normalized.isEmpty() ? null : normalized;
}
private static String formatLastSeen(Context context, RowItem row) {
if (context == null || row == null || row.lastSeenMs <= 0L) {
return null;
}
int offsetMinutes = row.lastSeenTimezoneOffsetMinutes;
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
format.setTimeZone(timeZoneForOffset(offsetMinutes));
String timestamp = format.format(new Date(row.lastSeenMs));
int currentOffsetMinutes = AppIconRules.currentTimezoneOffsetMinutes();
if (offsetMinutes != currentOffsetMinutes) {
timestamp += formatOffsetSuffix(offsetMinutes);
}
return context.getString(R.string.hidden_apps_last_seen, timestamp);
}
private static TimeZone timeZoneForOffset(int offsetMinutes) {
int absMinutes = Math.abs(offsetMinutes);
int hours = absMinutes / 60;
int minutes = absMinutes % 60;
String id = String.format(Locale.ROOT, "GMT%s%02d:%02d",
offsetMinutes < 0 ? "-" : "+", hours, minutes);
return TimeZone.getTimeZone(id);
}
private static String formatOffsetSuffix(int offsetMinutes) {
int absMinutes = Math.abs(offsetMinutes);
int hours = absMinutes / 60;
int minutes = absMinutes % 60;
if (minutes == 0) {
return String.format(Locale.ROOT, "%s%02d", offsetMinutes < 0 ? "-" : "+", hours);
}
return String.format(Locale.ROOT, "%s%02d:%02d",
offsetMinutes < 0 ? "-" : "+", hours, minutes);
}
static final class RowItem {
final String packageName;
final String label;
@@ -473,6 +521,8 @@ final class HiddenAppsUiSupport {
boolean blockAod;
boolean blockLock;
boolean blockUnlock;
long lastSeenMs;
int lastSeenTimezoneOffsetMinutes;
ArrayList<EditableExceptionRule> exceptionRules = new ArrayList<>();
RowItem(String packageName, String label, Drawable icon) {
@@ -520,6 +570,7 @@ final class HiddenAppsUiSupport {
TextView crossUnlock;
TextView label;
TextView pkg;
TextView lastSeen;
}
static final class HiddenAppsAdapter extends BaseAdapter {
@@ -571,6 +622,7 @@ final class HiddenAppsUiSupport {
holder.crossUnlock = convertView.findViewById(R.id.icon_mode_unlock_cross);
holder.label = convertView.findViewById(R.id.hidden_item_label);
holder.pkg = convertView.findViewById(R.id.hidden_item_package);
holder.lastSeen = convertView.findViewById(R.id.hidden_item_last_seen);
convertView.setTag(holder);
} else {
holder = (RowHolder) convertView.getTag();
@@ -585,6 +637,14 @@ final class HiddenAppsUiSupport {
} else {
holder.pkg.setText(row.packageName);
}
String lastSeenText = formatLastSeen(context, row);
if (lastSeenText == null) {
holder.lastSeen.setVisibility(View.GONE);
holder.lastSeen.setText("");
} else {
holder.lastSeen.setVisibility(View.VISIBLE);
holder.lastSeen.setText(lastSeenText);
}
bindModeIconTriple(
holder.iconAod, holder.crossAod,
holder.iconLock, holder.crossLock,
@@ -59,6 +59,8 @@ public class IconsDebugFragment extends Fragment {
root.findViewById(R.id.debug_visualize_chip_container_switch);
SwitchMaterial visualizeCameraCutoutSwitch =
root.findViewById(R.id.debug_visualize_camera_cutout_switch);
SwitchMaterial visualizeAodStockContainersSwitch =
root.findViewById(R.id.debug_visualize_aod_stock_containers_switch);
MaterialButton restartSystemUiButton = root.findViewById(R.id.debug_restart_systemui_button);
SwitchMaterial cycleIconsSwitch = root.findViewById(R.id.debug_cycle_icons_switch);
TextView currentCameraIdentified = root.findViewById(R.id.debug_camera_current_identified);
@@ -106,13 +108,19 @@ public class IconsDebugFragment extends Fragment {
visualizeCameraCutoutSwitch,
SbtSettings.KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT,
SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT);
bindBooleanSwitch(
prefs,
visualizeAodStockContainersSwitch,
SbtSettings.KEY_DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS,
SbtDefaults.DEBUG_VISUALIZE_AOD_STOCK_CONTAINERS_DEFAULT);
bindRestartSystemUiButton(restartSystemUiButton);
updateVisualizerSwitchAvailability(
prefs,
visualizeNotificationContainerSwitch,
visualizeStatusContainerSwitch,
visualizeClockContainerSwitch,
visualizeChipContainerSwitch);
visualizeChipContainerSwitch,
visualizeAodStockContainersSwitch);
Runnable refreshCameraUi = () -> updateCameraUi(
prefs,
currentCameraIdentified,
@@ -134,7 +142,8 @@ public class IconsDebugFragment extends Fragment {
visualizeNotificationContainerSwitch,
visualizeStatusContainerSwitch,
visualizeClockContainerSwitch,
visualizeChipContainerSwitch);
visualizeChipContainerSwitch,
visualizeAodStockContainersSwitch);
}
};
prefs.registerOnSharedPreferenceChangeListener(listener);
@@ -192,7 +201,8 @@ public class IconsDebugFragment extends Fragment {
SwitchMaterial notificationSwitch,
SwitchMaterial statusSwitch,
SwitchMaterial clockSwitch,
SwitchMaterial chipSwitch) {
SwitchMaterial chipSwitch,
SwitchMaterial aodStockSwitch) {
boolean layoutEnabled = prefs != null && prefs.getBoolean(
SbtSettings.KEY_CLOCK_ENABLED,
SbtDefaults.CLOCK_ENABLED_DEFAULT);
@@ -208,6 +218,9 @@ public class IconsDebugFragment extends Fragment {
if (chipSwitch != null) {
chipSwitch.setEnabled(layoutEnabled);
}
if (aodStockSwitch != null) {
aodStockSwitch.setEnabled(layoutEnabled);
}
}
private void restartSystemUi() {
@@ -153,6 +153,7 @@ public final class LayoutFragment extends Fragment {
root.findViewById(R.id.chip_vertical_offset_plus_fast),
root.findViewById(R.id.chip_vertical_offset_reset),
SbtDefaults.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT);
addChipHeightControl(root, prefs);
bindStepper(prefs,
SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX,
root.findViewById(R.id.status_vertical_offset_input),
@@ -226,10 +227,12 @@ public final class LayoutFragment extends Fragment {
SbtSettings::layoutNotifMiddleAutoNearestSideKey,
SbtSettings::layoutNotifMiddleSideKey,
SbtSettings::layoutNotifVerticalOffsetKey,
SbtSettings.KEY_LAYOUT_NOTIF_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_ICON_HEIGHT_STEPS_DEFAULT,
LayoutSectionCopySupport.Section.NOTIFICATIONS_ICONS);
setupUnlockedIconContainerCount(
root,
@@ -244,10 +247,12 @@ public final class LayoutFragment extends Fragment {
SbtSettings::layoutStatusMiddleAutoNearestSideKey,
SbtSettings::layoutStatusMiddleSideKey,
SbtSettings::layoutStatusVerticalOffsetKey,
SbtSettings.KEY_LAYOUT_STATUS_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT,
LayoutSectionCopySupport.Section.STATUS);
return root;
}
@@ -269,10 +274,12 @@ public final class LayoutFragment extends Fragment {
IndexedKey autoNearestKey,
IndexedKey middleSideKey,
IndexedKey verticalOffsetKey,
String iconHeightKey,
String positionDefault,
boolean autoNearestDefault,
String middleSideDefault,
int verticalOffsetDefault,
int iconHeightDefault,
LayoutSectionCopySupport.Section copySection
) {
CheckBox checkbox = root.findViewById(checkboxId);
@@ -319,6 +326,11 @@ public final class LayoutFragment extends Fragment {
rebuild[0].run();
}
}));
countContainer.addView(iconHeightControl(
prefs,
iconHeightKey,
iconHeightDefault,
R.string.layout_icon_height_steps));
int sourceCardIndex = cardsParent != null ? cardsParent.indexOfChild(sourceCard) : -1;
if (cardsParent == null || sourceCardIndex < 0) {
return;
@@ -407,6 +419,101 @@ public final class LayoutFragment extends Fragment {
return outer;
}
private View iconHeightControl(
SharedPreferences prefs,
String key,
int defaultValue,
int labelRes
) {
LinearLayout outer = new LinearLayout(requireContext());
outer.setOrientation(LinearLayout.VERTICAL);
TextView label = new TextView(requireContext());
label.setText(labelRes);
label.setPadding(0, dp(8), 0, 0);
outer.addView(label);
LinearLayout row = new LinearLayout(requireContext());
row.setOrientation(LinearLayout.HORIZONTAL);
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
MaterialButton minus = smallButton("-");
MaterialButton plus = smallButton("+");
MaterialButton reset = smallButton("D");
EditText input = new EditText(requireContext());
input.setGravity(android.view.Gravity.CENTER);
input.setSingleLine(true);
input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER);
input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(3)});
int current = clampValue(
prefs.getInt(key, defaultValue),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX);
input.setText(String.valueOf(current));
row.addView(minus, compactButtonParams());
row.addView(input, compactInputParams());
row.addView(plus, compactButtonParams());
LinearLayout.LayoutParams resetParams = compactButtonParams();
resetParams.leftMargin = dp(8);
row.addView(reset, resetParams);
outer.addView(row);
View.OnClickListener listener = view -> {
int next = clampValue(
parseInt(input, current) + (view == minus ? -1 : 1),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX);
prefs.edit().putInt(key, next).apply();
SbtSettings.ensureReadable(requireContext());
input.setText(String.valueOf(next));
};
minus.setOnClickListener(listener);
plus.setOnClickListener(listener);
reset.setOnClickListener(v -> {
int next = clampValue(
defaultValue,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX);
prefs.edit().putInt(key, next).apply();
SbtSettings.ensureReadable(requireContext());
input.setText(String.valueOf(next));
});
input.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
int next = clampValue(
parseInt(input, current),
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX);
prefs.edit().putInt(key, next).apply();
SbtSettings.ensureReadable(requireContext());
input.setText(String.valueOf(next));
}
});
return outer;
}
private void addChipHeightControl(View root, SharedPreferences prefs) {
View offsetInput = root.findViewById(R.id.chip_vertical_offset_input);
if (offsetInput == null) {
return;
}
View row = offsetInput;
while (row != null
&& !(row instanceof LinearLayout && row.getParent() instanceof LinearLayout)) {
if (!(row.getParent() instanceof View)) {
return;
}
row = (View) row.getParent();
}
if (row == null || !(row.getParent() instanceof LinearLayout parent)) {
return;
}
int index = parent.indexOfChild(row);
parent.addView(iconHeightControl(
prefs,
SbtSettings.KEY_LAYOUT_CHIP_HEIGHT_STEPS,
SbtDefaults.LAYOUT_CHIP_HEIGHT_STEPS_DEFAULT,
R.string.layout_chip_height_steps), Math.min(parent.getChildCount(), index + 1));
}
private View iconCopyCardFromLayout(
int sourceCheckboxId,
int titleId,
@@ -187,12 +187,14 @@ final class LayoutSectionCopySupport {
copyInt(editor, prefs, src.maxIcons, dst.maxIcons, src.maxIconsDefault);
copyBoolean(editor, prefs, src.evenDistribution, dst.evenDistribution, src.evenDistributionDefault);
copyBoolean(editor, prefs, src.limitWidth, dst.limitWidth, src.limitWidthDefault);
copyInt(editor, prefs, src.iconHeight, dst.iconHeight, src.iconHeightDefault);
}
private static void copyIcons(SharedPreferences.Editor editor, SharedPreferences prefs, KeySet src, KeySet dst) {
copyBoolean(editor, prefs, src.enabled, dst.enabled, src.enabledDefault);
int count = prefs.getInt(src.count, src.countDefault);
editor.putInt(dst.count, count);
copyInt(editor, prefs, src.iconHeight, dst.iconHeight, src.iconHeightDefault);
for (int i = 0; i < MAX_COPIES; i++) {
copyString(editor, prefs, src.positionAt(i), dst.positionAt(i), src.positionDefault);
copyBoolean(editor, prefs, src.autoNearestAt(i), dst.autoNearestAt(i), src.autoNearestDefault);
@@ -277,12 +279,14 @@ final class LayoutSectionCopySupport {
"layout_lock_notifications_middle_auto_nearest_side",
"layout_lock_notifications_middle_side",
"layout_lock_notifications_vertical_offset_px",
SbtSettings.layoutLockNotifIconHeightKey(),
SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT);
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT);
}
if (mode == Mode.AOD) {
return iconKeys(
@@ -292,12 +296,14 @@ final class LayoutSectionCopySupport {
"layout_aod_notifications_middle_auto_nearest_side",
"layout_aod_notifications_middle_side",
"layout_aod_notifications_vertical_offset_px",
SbtSettings.layoutAodNotifIconHeightKey(),
SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT);
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT);
}
return iconKeys(
SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_UNLOCKED,
@@ -306,12 +312,14 @@ final class LayoutSectionCopySupport {
SbtSettings.KEY_LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE,
SbtSettings.KEY_LAYOUT_NOTIF_MIDDLE_SIDE,
SbtSettings.KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX,
SbtSettings.KEY_LAYOUT_NOTIF_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_NOTIF_ENABLED_UNLOCKED_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT);
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_ICON_HEIGHT_STEPS_DEFAULT);
}
private static KeySet statusKeys(Mode mode) {
@@ -323,12 +331,14 @@ final class LayoutSectionCopySupport {
"layout_lock_status_middle_auto_nearest_side",
"layout_lock_status_middle_side",
"layout_lock_status_vertical_offset_px",
SbtSettings.layoutLockStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT);
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT);
}
if (mode == Mode.AOD) {
return iconKeys(
@@ -338,12 +348,14 @@ final class LayoutSectionCopySupport {
"layout_aod_status_middle_auto_nearest_side",
"layout_aod_status_middle_side",
"layout_aod_status_vertical_offset_px",
SbtSettings.layoutAodStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT);
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT);
}
return iconKeys(
SbtSettings.KEY_LAYOUT_STATUS_ENABLED_UNLOCKED,
@@ -352,12 +364,14 @@ final class LayoutSectionCopySupport {
SbtSettings.KEY_LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE,
SbtSettings.KEY_LAYOUT_STATUS_MIDDLE_SIDE,
SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX,
SbtSettings.KEY_LAYOUT_STATUS_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_STATUS_ENABLED_UNLOCKED_DEFAULT,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT);
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT);
}
private static KeySet cardKeys(Mode mode) {
@@ -367,20 +381,24 @@ final class LayoutSectionCopySupport {
SbtSettings.KEY_CARDS_AOD_MAX_ICONS_PER_ROW,
SbtSettings.KEY_CARDS_AOD_EVEN_DISTRIBUTION,
SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH,
SbtSettings.KEY_CARDS_AOD_ICON_HEIGHT_STEPS,
SbtDefaults.CARDS_AOD_MAX_ROWS_DEFAULT,
SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT,
SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT);
SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT,
SbtDefaults.LAYOUT_CARDS_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT);
}
return KeySet.cards(
SbtSettings.KEY_CARDS_LOCK_MAX_ROWS,
SbtSettings.KEY_CARDS_LOCK_MAX_ICONS_PER_ROW,
SbtSettings.KEY_CARDS_LOCK_EVEN_DISTRIBUTION,
SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH,
SbtSettings.KEY_CARDS_LOCK_ICON_HEIGHT_STEPS,
SbtDefaults.CARDS_LOCK_MAX_ROWS_DEFAULT,
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT,
SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT);
SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT,
SbtDefaults.LAYOUT_CARDS_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT);
}
private static KeySet iconKeys(
@@ -390,12 +408,14 @@ final class LayoutSectionCopySupport {
String autoNearest,
String middleSide,
String verticalOffset,
String iconHeight,
boolean enabledDefault,
int countDefault,
String positionDefault,
boolean autoNearestDefault,
String middleSideDefault,
int verticalOffsetDefault
int verticalOffsetDefault,
int iconHeightDefault
) {
return new KeySet(
enabled,
@@ -403,12 +423,14 @@ final class LayoutSectionCopySupport {
autoNearest,
middleSide,
verticalOffset,
iconHeight,
count,
enabledDefault,
positionDefault,
autoNearestDefault,
middleSideDefault,
verticalOffsetDefault,
iconHeightDefault,
countDefault);
}
@@ -537,12 +559,14 @@ final class LayoutSectionCopySupport {
final String autoNearest;
final String middleSide;
final String verticalOffset;
final String iconHeight;
final String count;
final boolean enabledDefault;
final String positionDefault;
final boolean autoNearestDefault;
final String middleSideDefault;
final int verticalOffsetDefault;
final int iconHeightDefault;
final int countDefault;
final String maxRows;
final String maxIcons;
@@ -566,18 +590,53 @@ final class LayoutSectionCopySupport {
String middleSideDefault,
int verticalOffsetDefault,
int countDefault
) {
this(
enabled,
position,
autoNearest,
middleSide,
verticalOffset,
null,
count,
enabledDefault,
positionDefault,
autoNearestDefault,
middleSideDefault,
verticalOffsetDefault,
0,
countDefault);
}
KeySet(
String enabled,
String position,
String autoNearest,
String middleSide,
String verticalOffset,
String iconHeight,
String count,
boolean enabledDefault,
String positionDefault,
boolean autoNearestDefault,
String middleSideDefault,
int verticalOffsetDefault,
int iconHeightDefault,
int countDefault
) {
this.enabled = enabled;
this.position = position;
this.autoNearest = autoNearest;
this.middleSide = middleSide;
this.verticalOffset = verticalOffset;
this.iconHeight = iconHeight;
this.count = count;
this.enabledDefault = enabledDefault;
this.positionDefault = positionDefault;
this.autoNearestDefault = autoNearestDefault;
this.middleSideDefault = middleSideDefault;
this.verticalOffsetDefault = verticalOffsetDefault;
this.iconHeightDefault = iconHeightDefault;
this.countDefault = countDefault;
this.maxRows = null;
this.maxIcons = null;
@@ -594,13 +653,16 @@ final class LayoutSectionCopySupport {
String maxIcons,
String evenDistribution,
String limitWidth,
String iconHeight,
int maxRowsDefault,
int maxIconsDefault,
boolean evenDistributionDefault,
boolean limitWidthDefault
boolean limitWidthDefault,
int iconHeightDefault
) {
return new KeySet(maxRows, maxIcons, evenDistribution, limitWidth,
maxRowsDefault, maxIconsDefault, evenDistributionDefault, limitWidthDefault);
iconHeight, maxRowsDefault, maxIconsDefault,
evenDistributionDefault, limitWidthDefault, iconHeightDefault);
}
private KeySet(
@@ -608,22 +670,26 @@ final class LayoutSectionCopySupport {
String maxIcons,
String evenDistribution,
String limitWidth,
String iconHeight,
int maxRowsDefault,
int maxIconsDefault,
boolean evenDistributionDefault,
boolean limitWidthDefault
boolean limitWidthDefault,
int iconHeightDefault
) {
this.enabled = null;
this.position = null;
this.autoNearest = null;
this.middleSide = null;
this.verticalOffset = null;
this.iconHeight = iconHeight;
this.count = null;
this.enabledDefault = false;
this.positionDefault = null;
this.autoNearestDefault = false;
this.middleSideDefault = null;
this.verticalOffsetDefault = 0;
this.iconHeightDefault = iconHeightDefault;
this.countDefault = 0;
this.maxRows = maxRows;
this.maxIcons = maxIcons;
@@ -174,6 +174,8 @@ abstract class LockedSceneLayoutFragment extends Fragment {
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT,
aod ? SbtSettings.layoutAodStatusIconHeightKey() : SbtSettings.layoutLockStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT,
LayoutSectionCopySupport.Section.STATUS,
false,
refresh));
@@ -205,6 +207,10 @@ abstract class LockedSceneLayoutFragment extends Fragment {
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT,
aod ? SbtSettings.layoutAodNotifIconHeightKey() : SbtSettings.layoutLockNotifIconHeightKey(),
aod
? SbtDefaults.LAYOUT_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT
: SbtDefaults.LAYOUT_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT,
LayoutSectionCopySupport.Section.NOTIFICATIONS_ICONS,
false,
refresh));
@@ -309,6 +315,8 @@ abstract class LockedSceneLayoutFragment extends Fragment {
boolean autoNearestDefault,
String middleSideDefault,
int verticalOffsetDefault,
String iconHeightKey,
int iconHeightDefault,
LayoutSectionCopySupport.Section copySection,
boolean cardsMode,
@Nullable Runnable refresh
@@ -346,6 +354,8 @@ abstract class LockedSceneLayoutFragment extends Fragment {
middleSideDefault,
indexedSceneKey(base + "_vertical_offset_px", 0),
verticalOffsetDefault,
iconHeightKey,
iconHeightDefault,
legacyEnabledKey,
countKey,
count,
@@ -381,6 +391,8 @@ abstract class LockedSceneLayoutFragment extends Fragment {
indexedSceneKey(base + "_vertical_offset_px", i),
verticalOffsetDefault,
null,
0,
null,
null,
count,
null));
@@ -440,6 +452,8 @@ abstract class LockedSceneLayoutFragment extends Fragment {
String middleSideDefault,
String verticalOffsetKey,
int verticalOffsetDefault,
@Nullable String iconHeightKey,
int iconHeightDefault,
@Nullable String legacyEnabledKey,
@Nullable String countKey,
int count,
@@ -453,6 +467,18 @@ abstract class LockedSceneLayoutFragment extends Fragment {
sectionContent.addView(
iconContainerCountControl(context, prefs, countKey, legacyEnabledKey, count, onCountChanged),
insertIndex);
if (iconHeightKey != null) {
sectionContent.addView(
sizeStepper(
context,
prefs,
R.string.layout_icon_height_steps,
iconHeightKey,
iconHeightDefault,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX),
Math.min(insertIndex + 1, sectionContent.getChildCount()));
}
}
updateIconCardTitle(card, titleId, index, count);
bindXmlPositionSection(
@@ -623,6 +649,16 @@ abstract class LockedSceneLayoutFragment extends Fragment {
R.string.label_limit_screen_width,
aod ? SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH : SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH,
aod ? SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT : SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT));
content.addView(sizeStepper(
context,
prefs,
R.string.layout_icon_height_steps,
aod ? SbtSettings.KEY_CARDS_AOD_ICON_HEIGHT_STEPS : SbtSettings.KEY_CARDS_LOCK_ICON_HEIGHT_STEPS,
aod
? SbtDefaults.LAYOUT_CARDS_NOTIF_AOD_ICON_HEIGHT_STEPS_DEFAULT
: SbtDefaults.LAYOUT_CARDS_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MIN,
SbtDefaults.LAYOUT_ICON_HEIGHT_STEPS_MAX));
return card(context, R.string.layout_notification_position_title, content);
}
@@ -855,6 +891,23 @@ abstract class LockedSceneLayoutFragment extends Fragment {
}
private View stepper(Context context, SharedPreferences prefs, int label, String key, int def, int min, int max) {
return stepper(context, prefs, label, key, def, min, max, false);
}
private View sizeStepper(Context context, SharedPreferences prefs, int label, String key, int def, int min, int max) {
return stepper(context, prefs, label, key, def, min, max, true);
}
private View stepper(
Context context,
SharedPreferences prefs,
int label,
String key,
int def,
int min,
int max,
boolean showDefaultButton
) {
LinearLayout outer = vertical(context);
outer.addView(body(context, label));
LinearLayout row = new LinearLayout(context);
@@ -862,11 +915,17 @@ abstract class LockedSceneLayoutFragment extends Fragment {
row.setGravity(Gravity.CENTER_VERTICAL);
MaterialButton minus = button(context, "-");
MaterialButton plus = button(context, "+");
MaterialButton reset = showDefaultButton ? button(context, "D") : null;
EditText input = numericInput(context, true);
input.setText(String.valueOf(clamp(prefs.getInt(key, def), min, max)));
row.addView(minus, buttonParams());
row.addView(input, inputParams());
row.addView(plus, buttonParams());
if (reset != null) {
LinearLayout.LayoutParams resetParams = buttonParams();
resetParams.leftMargin = dp(8);
row.addView(reset, resetParams);
}
outer.addView(row);
View.OnClickListener listener = view -> {
int delta = view == minus ? -1 : 1;
@@ -876,6 +935,13 @@ abstract class LockedSceneLayoutFragment extends Fragment {
};
minus.setOnClickListener(listener);
plus.setOnClickListener(listener);
if (reset != null) {
reset.setOnClickListener(v -> {
int next = clamp(def, min, max);
input.setText(String.valueOf(next));
persistIntPref(prefs, key, next);
});
}
input.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
int next = clamp(parseInt(input, def), min, max);
@@ -64,7 +64,7 @@ public class SystemIconsFragment extends Fragment {
"hotspot"),
new SystemIconEntry(R.string.system_icon_nfc,
new String[]{"stat_sys_nfc"},
android.R.drawable.ic_menu_share,
R.drawable.sbt_ic_nfc,
"nfc_on", "nfc"),
new SystemIconEntry(R.string.system_icon_vpn,
new String[]{"stat_sys_branded_vpn"},
+15
View File
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M4,5h9c1.1,0 2,0.9 2,2v10c0,1.1 -0.9,2 -2,2H4c-1.1,0 -2,-0.9 -2,-2V7c0,-1.1 0.9,-2 2,-2zM4,7v10h9V7H4z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M17.2,7.2c1.3,1.2 2.1,2.9 2.1,4.8s-0.8,3.6 -2.1,4.8l-1.1,-1.2c1,-0.9 1.6,-2.2 1.6,-3.6s-0.6,-2.7 -1.6,-3.6l1.1,-1.2z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M20.1,4.4C22,6.3 23,9 23,12s-1,5.7 -2.9,7.6l-1.2,-1.2c1.6,-1.6 2.5,-3.8 2.5,-6.4s-0.9,-4.8 -2.5,-6.4l1.2,-1.2z" />
</vector>
@@ -70,6 +70,12 @@
android:layout_height="wrap_content"
android:text="@string/debug_visualize_camera_cutout" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/debug_visualize_aod_stock_containers_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/debug_visualize_aod_stock_containers" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
@@ -109,6 +109,15 @@
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceCaption" />
<TextView
android:id="@+id/hidden_item_last_seen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceCaption"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
+4
View File
@@ -26,6 +26,7 @@
<string name="debug_visualize_clock_container">Clock container</string>
<string name="debug_visualize_chip_container">Status chip container</string>
<string name="debug_visualize_camera_cutout">Camera cutout</string>
<string name="debug_visualize_aod_stock_containers">AOD stock containers and derived statusbar</string>
<string name="debug_section_systemui">SystemUI</string>
<string name="debug_restart_systemui">Restart SystemUI</string>
<string name="debug_restart_systemui_started">SystemUI restart requested.</string>
@@ -100,6 +101,7 @@
<string name="hidden_apps_close">Close</string>
<string name="hidden_apps_exception_regex_input_hint">Regex</string>
<string name="hidden_apps_package_with_exceptions">%1$s \u2022 %2$d exceptions</string>
<string name="hidden_apps_last_seen">Last seen: %1$s</string>
<string name="hidden_apps_default_modes_title">Default settings</string>
<string name="hidden_apps_add_exception">Add exception</string>
<string name="hidden_apps_delete_exception">Delete</string>
@@ -171,6 +173,8 @@
<string name="layout_item_enabled">Enabled</string>
<string name="layout_copy_from">Copy from</string>
<string name="layout_icon_container_count">Number of containers</string>
<string name="layout_icon_height_steps">Icon height (% of statusbar)</string>
<string name="layout_chip_height_steps">Chip height (% of statusbar)</string>
<string name="layout_locked_notifications_title">Locked notifications mode</string>
<string name="layout_locked_notifications_hint">Mirrors the Samsung notification display style for AOD and lockscreen.</string>
<string name="layout_misc_title">Misc</string>
@@ -155,6 +155,89 @@ public final class PackedStatusBarLayoutSolverTest {
assertTrue(notifications.width() == 65.0);
}
@Test
public void singleIconGroupsKeepDotWhenChipConsumesSpace() {
ArrayList<LayoutItem> items = new ArrayList<>();
LayoutItem notifications = iconItem("Notifs", "Notifs", "notifications", 4, 30, 120, 0, 30);
LayoutItem status = iconItem("Status", "Status", "status", 4, 30, 120, 0, 30);
LayoutItem chip = LayoutItem.fixed("Chip", "Chip", Position.LEFT, Fallback.LEFT, 200, 30, 0);
items.add(notifications);
items.add(status);
items.add(chip);
PackedStatusBarLayoutSolver.solve(
new Spec(70, 0, 0, 0, new ArrayList<>(Arrays.asList("Notifs", "Status", "Chip"))),
items);
assertTrue(notifications.visibleIcons == 0);
assertTrue(notifications.dot);
assertTrue(notifications.width() > 0);
assertTrue(status.visibleIcons == 0);
assertTrue(status.dot);
assertTrue(status.width() > 0);
}
@Test
public void groupEdgeDotIsNotCompactedAgainstNonIconNeighbor() {
ArrayList<LayoutItem> items = new ArrayList<>();
items.add(new LayoutItem(
"Clock",
"Clock",
Position.LEFT,
Fallback.LEFT,
LayoutItem.boxes(
new Box(102, 14, 33, 38),
new Box(0, 32, 134, 47))));
LayoutItem status = iconItem("Status", "Status", "status", 4, 27, 108, 39, 36);
status.direction = PackedStatusBarLayoutSolver.Direction.LEFT;
LayoutItem chip = LayoutItem.fixed("Chip", "Chip", Position.LEFT, Fallback.LEFT, 200, 44, 33);
LayoutItem notifications = iconItem("Notifs", "Notifs", "notifications", 4, 27, 108, 36, 36);
notifications.direction = PackedStatusBarLayoutSolver.Direction.RIGHT;
items.add(status);
items.add(chip);
items.add(notifications);
PackedStatusBarLayoutSolver.solve(
productionSpec(834, 387, 59, 0),
items);
assertTrue(status.visibleIcons == 0);
assertTrue(status.dot);
assertTrue(status.width() == 27.0);
}
@Test
public void finalIconGroupKeepsDotWhenChipConsumesAllRemainingSpaceBeforeCutout() {
ArrayList<LayoutItem> items = new ArrayList<>();
items.add(new LayoutItem(
"Clock",
"Clock",
Position.LEFT,
Fallback.LEFT,
LayoutItem.boxes(
new Box(102, 14, 33, 38),
new Box(0, 32, 134, 47))));
LayoutItem status = iconItem("Status", "Status", "status", 4, 24, 96, 39, 32);
status.direction = PackedStatusBarLayoutSolver.Direction.LEFT;
LayoutItem chip = LayoutItem.fixed("Chip", "Chip", Position.LEFT, Fallback.LEFT, 397, 44, 33);
LayoutItem notifications = iconItem("Notifs", "Notifs", "notifications", 4, 29, 116, 36, 39);
notifications.direction = PackedStatusBarLayoutSolver.Direction.RIGHT;
notifications.compactDotWidth = 27;
items.add(status);
items.add(chip);
items.add(notifications);
PackedStatusBarLayoutSolver.solve(
productionSpec(834, 387, 59, 0),
items);
assertTrue(notifications.visibleIcons == 0);
assertTrue(notifications.dot);
assertTrue(notifications.width() > 0);
assertTrue(notifications.right() <= 387);
assertTrue(chip.right() <= notifications.left());
}
private static Spec cutoutSpec() {
return new Spec(
1200,
@@ -66,6 +66,42 @@ public final class UnlockedLayoutBandTest {
assertEquals("icon4", band.placements.get(2).key);
}
@Test
public void clippedStatusChipRendersAtClippedSourceWidthInsteadOfCroppingScaledBitmap() {
ArrayList<SnapshotPlacement> placements = new ArrayList<>();
placements.add(new SnapshotPlacement(
null,
new Bounds(181, 26, 417, 59),
"chip",
false,
false,
0xffffffff,
null,
new Bounds(0, 7, 397, 44),
0,
new Bounds(181, 26, 417, 59)));
UnlockedLayoutBand band = UnlockedLayoutBand.icons(
LayoutItemKind.CHIP,
null,
null,
placements,
LayoutPosition.LEFT,
false,
AnchorSide.LEFT,
false);
band.prepareForSide(AnchorSide.RIGHT, "normal");
band.applyPackedContinuousWidth(211, AnchorSide.RIGHT);
band.place(182);
assertEquals(1, band.placements.size());
SnapshotPlacement chip = band.placements.get(0);
assertEquals(231, chip.bounds.width);
assertEquals(211, chip.contentBounds.width);
assertEquals(211, chip.sourceRenderBounds.width);
assertEquals(0, chip.sourceOffsetX);
}
private static ArrayList<SnapshotPlacement> placements(int count, int width, int height) {
ArrayList<SnapshotPlacement> result = new ArrayList<>();
for (int i = 0; i < count; i++) {