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.features.RuntimeContext;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport;
import se.ajpanton.statusbartweak.runtime.mode.SceneKey;
import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache; import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache;
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults; import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings; import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
@@ -46,27 +47,33 @@ final class StockUnlockedNotificationIconsController {
Class<?> containerClass = ReflectionSupport.requireClass(CONTAINER_CLASS_NAME, classLoader); Class<?> containerClass = ReflectionSupport.requireClass(CONTAINER_CLASS_NAME, classLoader);
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "onAttachedToWindow", chain -> { XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "onAttachedToWindow", chain -> {
Object result = chain.proceed(); Object result = chain.proceed();
if (chain.getThisObject() instanceof View view && shouldManageContainer(view)) { if (chain.getThisObject() instanceof View view) {
if (shouldManageContainer(view)) {
trackContainer(view); trackContainer(view);
view.post(() -> refreshContainer(view)); view.post(() -> refreshContainer(view));
} }
}
return result; return result;
}); });
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "calculateIconXTranslations", chain -> { 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); trackContainer(view);
applyLimitIfNeeded(view); applyLimitIfNeeded(view);
} }
}
return chain.proceed(); return chain.proceed();
}); });
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "onLayout", chain -> { XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "onLayout", chain -> {
Object result = chain.proceed(); Object result = chain.proceed();
if (chain.getThisObject() instanceof View view && shouldManageContainer(view)) { if (chain.getThisObject() instanceof View view) {
if (shouldManageContainer(view)) {
trackContainer(view); trackContainer(view);
if (isFeatureEnabled(view.getContext())) { if (isFeatureEnabled(view.getContext())) {
enforceParentWidth(view); enforceParentWidth(view);
} }
} }
}
return result; return result;
}); });
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "shouldForceOverflow", chain -> { XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), containerClass, "shouldForceOverflow", chain -> {
@@ -236,6 +243,9 @@ final class StockUnlockedNotificationIconsController {
} }
private boolean shouldManageContainer(View view) { private boolean shouldManageContainer(View view) {
if (!isUnlockedScene()) {
return false;
}
if (isKeyguardContainer(view)) { if (isKeyguardContainer(view)) {
return false; return false;
} }
@@ -260,6 +270,11 @@ final class StockUnlockedNotificationIconsController {
return inUnlockedStatusBarIconArea; return inUnlockedStatusBarIconArea;
} }
private boolean isUnlockedScene() {
SceneKey scene = runtimeContext.getModeStateRepository().getActiveScene();
return scene != null && scene.isUnlocked();
}
private String resolveIdName(View view) { private String resolveIdName(View view) {
if (view == null || view.getId() == View.NO_ID) { if (view == null || view.getId() == View.NO_ID) {
return ""; return "";
@@ -265,15 +265,17 @@ public final class PackedStatusBarLayoutSolver {
neededForItem = Math.max(neededForItem, constraint.required - assumedAbsorberCapacity); 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 amount = Math.min(Math.max(0, neededForItem), contextualShrinkCapacity(item, item));
double directActual = item.isIconItem() double directActual = item.isIconItem()
? truncateIconItemWithoutDot(item, amount) ? truncateIconItemWithoutDot(item, amount, groupItems != null && groupItems.size() > 1)
: item.truncateAtLeast(amount); : item.truncateAtLeast(amount);
if (directActual != 0) { if (directActual != 0) {
adjustConstraintsForItemWidthDelta(constraints, item, directActual); adjustConstraintsForItemWidthDelta(constraints, item, directActual);
} }
if (item.isIconItem()) { if (item.isIconItem()) {
ArrayList<LayoutItem> groupItems = iconGroups.get(item.iconGroup);
ArrayList<Integer> remaining = remainingIconWidths.get(item.iconGroup); ArrayList<Integer> remaining = remainingIconWidths.get(item.iconGroup);
int remainingStart = remainingIconStarts.getOrDefault(item.iconGroup, 0); int remainingStart = remainingIconStarts.getOrDefault(item.iconGroup, 0);
ArrayList<Integer> remainingAfterItem = allocateCurrentIconItem( ArrayList<Integer> remainingAfterItem = allocateCurrentIconItem(
@@ -336,7 +338,14 @@ public final class PackedStatusBarLayoutSolver {
return item.canShrinkBy(); 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 oldAllowZero = item.allowZero;
boolean oldAllowDot = item.allowDot; boolean oldAllowDot = item.allowDot;
item.allowZero = true; item.allowZero = true;
@@ -403,8 +412,9 @@ public final class PackedStatusBarLayoutSolver {
} }
ArrayList<Integer> remaining = new ArrayList<>(groupItems.get(0).iconWidths()); ArrayList<Integer> remaining = new ArrayList<>(groupItems.get(0).iconWidths());
int start = 0; int start = 0;
boolean allowZeroWithoutDot = groupItems.size() > 1;
for (LayoutItem item : groupItems) { for (LayoutItem item : groupItems) {
setIconItemPool(item, remaining, start); setIconItemPool(item, remaining, start, allowZeroWithoutDot);
int used = item.visibleIcons; int used = item.visibleIcons;
remaining = sliceFrom(remaining, used); remaining = sliceFrom(remaining, used);
start += used; start += used;
@@ -420,12 +430,13 @@ public final class PackedStatusBarLayoutSolver {
ArrayList<FinalConstraint> constraints ArrayList<FinalConstraint> constraints
) { ) {
HashMap<LayoutItem, Double> oldWidths = widthsByItem(groupItems); 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); ArrayList<Integer> remainingAfterItem = sliceFrom(remainingWidths, item.visibleIcons);
int remainingAfterStart = remainingStart + item.visibleIcons; int remainingAfterStart = remainingStart + item.visibleIcons;
int itemIndex = groupItems.indexOf(item); int itemIndex = groupItems.indexOf(item);
for (int i = itemIndex + 1; i < groupItems.size(); i++) { 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); adjustGroupConstraintDeltas(groupItems, oldWidths, constraints);
return remainingAfterItem; return remainingAfterItem;
@@ -451,7 +462,17 @@ public final class PackedStatusBarLayoutSolver {
adjustGroupConstraintDeltas(groupItems, oldWidths, constraints); 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 oldAllowZero = item.allowZero;
boolean oldAllowDot = item.allowDot; boolean oldAllowDot = item.allowDot;
item.allowZero = true; item.allowZero = true;
@@ -494,6 +515,12 @@ public final class PackedStatusBarLayoutSolver {
} }
LayoutItem item = groupItems.get(lastIcons); LayoutItem item = groupItems.get(lastIcons);
if (item.visibleIconWidths.isEmpty()) { 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; return;
} }
item.visibleIconWidths.remove(item.visibleIconWidths.size() - 1); item.visibleIconWidths.remove(item.visibleIconWidths.size() - 1);
@@ -515,7 +542,7 @@ public final class PackedStatusBarLayoutSolver {
break; break;
} }
} }
if (allLaterZero) { if (allLaterZero && item.noCollisionsOnSide(false)) {
return Math.min(dotWidth, item.dotWidth - item.dotWidth / 3.0); return Math.min(dotWidth, item.dotWidth - item.dotWidth / 3.0);
} }
} else { } else {
@@ -526,7 +553,7 @@ public final class PackedStatusBarLayoutSolver {
break; break;
} }
} }
if (allEarlierZero) { if (allEarlierZero && item.noCollisionsOnSide(true)) {
return Math.min(dotWidth, item.dotWidth - item.dotWidth / 3.0); return Math.min(dotWidth, item.dotWidth - item.dotWidth / 3.0);
} }
} }
@@ -13,12 +13,12 @@ import java.util.Objects;
* runtime API. * runtime API.
*/ */
public final class ModeStateRepository { public final class ModeStateRepository {
private SceneKey activeScene = SceneKey.unlocked(); private SceneKey activeScene = SceneKey.unlocked();
private boolean systemUiStatusBarStateKnown; private boolean systemUiStatusBarStateKnown;
private int systemUiStatusBarState; private int systemUiStatusBarState;
private boolean layoutEnabled; private boolean layoutEnabled;
private final List<LayoutEnabledListener> layoutEnabledListeners = new ArrayList<>(); private final List<LayoutEnabledListener> layoutEnabledListeners = new ArrayList<>();
private final List<SceneListener> sceneListeners = new ArrayList<>();
public synchronized SceneKey getActiveScene() { public synchronized SceneKey getActiveScene() {
return activeScene; return activeScene;
@@ -56,39 +56,71 @@ public final class ModeStateRepository {
} }
} }
public synchronized void setUnlocked() { public void setUnlocked() {
activeScene = SceneKey.unlocked(); setActiveScene(SceneKey.unlocked());
} }
public synchronized void setLockscreen(NotificationDisplayStyle style) { public void setLockscreen(NotificationDisplayStyle style) {
activeScene = SceneKey.lockscreen(requireDisplayStyle(style)); setActiveScene(SceneKey.lockscreen(requireDisplayStyle(style)));
} }
public synchronized void setAod(NotificationDisplayStyle style) { public void setAod(NotificationDisplayStyle style) {
activeScene = SceneKey.aod(requireDisplayStyle(style)); setActiveScene(SceneKey.aod(requireDisplayStyle(style)));
} }
public synchronized void setScene(SceneKey sceneKey) { public void setScene(SceneKey sceneKey) {
activeScene = Objects.requireNonNull(sceneKey, "sceneKey"); setActiveScene(Objects.requireNonNull(sceneKey, "sceneKey"));
} }
public synchronized void setSystemUiStatusBarState( public void setSystemUiStatusBarState(
int state, int state,
boolean dozing, boolean dozing,
NotificationDisplayStyle notificationDisplayStyle NotificationDisplayStyle notificationDisplayStyle
) { ) {
systemUiStatusBarStateKnown = true;
systemUiStatusBarState = state;
NotificationDisplayStyle style = notificationDisplayStyle; NotificationDisplayStyle style = notificationDisplayStyle;
if (style == null || style == NotificationDisplayStyle.NONE) { if (style == null || style == NotificationDisplayStyle.NONE) {
style = NotificationDisplayStyle.UNKNOWN; style = NotificationDisplayStyle.UNKNOWN;
} }
if (dozing) { SceneKey scene = dozing
activeScene = SceneKey.aod(style); ? SceneKey.aod(style)
} else if (state == 0) { : state == 0 ? SceneKey.unlocked() : SceneKey.lockscreen(style);
activeScene = SceneKey.unlocked(); synchronized (this) {
} else { systemUiStatusBarStateKnown = true;
activeScene = SceneKey.lockscreen(style); 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 { public interface LayoutEnabledListener {
void onLayoutEnabledChanged(boolean enabled); 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); 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( private static ArrayList<MeasuredClockRow> positionRowsFromBaseline(
ArrayList<MeasuredClockRow> measuredRows, ArrayList<MeasuredClockRow> measuredRows,
int sourceBaselineY, int sourceBaselineY,
@@ -19,6 +19,8 @@ import java.util.Objects;
import se.ajpanton.statusbartweak.R; import se.ajpanton.statusbartweak.R;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver; 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; import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
public final class DebugBoundsOverlayController { 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_CLOCK = Color.argb(96, 64, 220, 96);
private static final int COLOR_CHIP = Color.argb(112, 255, 0, 220); 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_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) { public boolean hasEnabledVisualizer(boolean layoutEnabled, SbtSettings settings) {
return hasEnabledVisualizer(layoutEnabled, settings, true);
}
public boolean hasEnabledVisualizer(
boolean layoutEnabled,
SbtSettings settings,
boolean aodStockVisualizersEnabled
) {
if (settings == null) { if (settings == null) {
return false; return false;
} }
@@ -41,21 +52,44 @@ public final class DebugBoundsOverlayController {
&& (settings.debugVisualizeNotificationContainer && (settings.debugVisualizeNotificationContainer
|| settings.debugVisualizeStatusContainer || settings.debugVisualizeStatusContainer
|| settings.debugVisualizeClockContainer || settings.debugVisualizeClockContainer
|| settings.debugVisualizeChipContainer)); || settings.debugVisualizeChipContainer
|| (aodStockVisualizersEnabled && settings.debugVisualizeAodStockContainers)));
} }
public void apply(View root, boolean layoutEnabled, SbtSettings settings) { 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) { if (!(root instanceof ViewGroup parent) || settings == null) {
return; return;
} }
if (!hasEnabledVisualizer(layoutEnabled, settings)) { if (!hasEnabledVisualizer(layoutEnabled, settings, aodStockVisualizersEnabled)) {
removeOverlay(parent); removeOverlays(parent);
return; return;
} }
ArrayList<OverlaySpec> specs = new ArrayList<>(); ArrayList<OverlaySpec> specs = new ArrayList<>();
if (settings.debugVisualizeCameraCutout) { if (settings.debugVisualizeCameraCutout) {
addCutoutSpecs(root, specs); addCutoutSpecs(root, specs);
} }
if (layoutEnabled && aodStockVisualizersEnabled && settings.debugVisualizeAodStockContainers) {
addAodSourceSpecs(root, settings, specs, scene);
}
if (layoutEnabled && settings.debugVisualizeNotificationContainer) { if (layoutEnabled && settings.debugVisualizeNotificationContainer) {
addHostContainerSpecs( addHostContainerSpecs(
root, root,
@@ -84,10 +118,12 @@ public final class DebugBoundsOverlayController {
specs); specs);
} }
if (specs.isEmpty()) { if (specs.isEmpty()) {
removeOverlay(parent); removeOverlays(parent);
return; return;
} }
OverlayView host = ensureOverlay(parent); ViewGroup overlayParent = overlayParent(root, parent, layoutEnabled, aodStockVisualizersEnabled, settings);
removeOverlayFromOtherParent(parent, overlayParent);
OverlayView host = ensureOverlay(overlayParent);
if (host == null) { if (host == null) {
return; return;
} }
@@ -100,17 +136,183 @@ public final class DebugBoundsOverlayController {
host.layout(0, 0, width, height); host.layout(0, 0, width, height);
} }
host.setSpecs(specs); host.setSpecs(specs);
if (parent.getChildAt(parent.getChildCount() - 1) != host) { if (overlayParent.getChildAt(overlayParent.getChildCount() - 1) != host) {
host.bringToFront(); 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) { public void remove(View root) {
if (root instanceof ViewGroup parent) { 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) { private void addCutoutSpecs(View root, ArrayList<OverlaySpec> specs) {
WindowInsets insets = root.getRootWindowInsets(); WindowInsets insets = root.getRootWindowInsets();
DisplayCutout cutout = insets != null ? insets.getDisplayCutout() : null; DisplayCutout cutout = insets != null ? insets.getDisplayCutout() : null;
@@ -252,13 +454,94 @@ public final class DebugBoundsOverlayController {
return host; 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) { private void removeOverlay(ViewGroup parent) {
if (parent == null) {
return;
}
View existing = findDirectTaggedChild(parent, TAG_OVERLAY_HOST); View existing = findDirectTaggedChild(parent, TAG_OVERLAY_HOST);
if (existing != null) { if (existing != null) {
parent.removeView(existing); 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) { private View findDirectTaggedChild(ViewGroup parent, String tag) {
if (parent == null || tag == null) { if (parent == null || tag == null) {
return null; return null;
@@ -352,7 +635,10 @@ public final class DebugBoundsOverlayController {
return Math.max(min, Math.min(max, value)); 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) { private record Bounds(int left, int top, int width, int height) {
@@ -389,6 +675,8 @@ public final class DebugBoundsOverlayController {
for (OverlaySpec spec : specs) { for (OverlaySpec spec : specs) {
Bounds bounds = spec.bounds; Bounds bounds = spec.bounds;
paint.setColor(spec.color); paint.setColor(spec.color);
paint.setStyle(spec.fill ? Paint.Style.FILL : Paint.Style.STROKE);
paint.setStrokeWidth(Math.max(1f, getResources().getDisplayMetrics().density));
canvas.drawRect( canvas.drawRect(
bounds.left, bounds.left,
bounds.top, bounds.top,
@@ -2,6 +2,7 @@ package se.ajpanton.statusbartweak.runtime.render;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -11,10 +12,17 @@ import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; 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 { public final class LockscreenCardsNotificationIconSourceStore {
private static final WeakHashMap<ViewGroup, ArrayList<SourceEntry>> SOURCES_BY_CONTAINER = private static final WeakHashMap<ViewGroup, ArrayList<SourceEntry>> SOURCES_BY_CONTAINER =
new WeakHashMap<>(); 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() { 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) { static ArrayList<SnapshotSource> snapshotSources(ViewGroup container, View root) {
ArrayList<SourceEntry> views = SOURCES_BY_CONTAINER.get(container);
ArrayList<SnapshotSource> result = new ArrayList<>(); 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; return result;
} }
}
ArrayList<SourceEntry> views = SOURCES_BY_CONTAINER.get(container);
if (views != null && !views.isEmpty()) {
int skipCards = visibleShelfStartIndex(container, views); int skipCards = visibleShelfStartIndex(container, views);
HashSet<String> visibleCardKeys = visibleCardKeys(root, container); HashSet<String> visibleCardKeys = visibleCardKeys(root, container);
for (int i = 0; i < views.size(); i++) { 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)); result.add(new SnapshotSource(view, SnapshotMode.NOTIFICATION_DRAWABLE, entry.key));
} }
}
if (!result.isEmpty()) {
return result; 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) { private static HashSet<String> visibleCardKeys(View root, View shelf) {
HashSet<String> result = new HashSet<>(); HashSet<String> result = new HashSet<>();
@@ -30,6 +30,7 @@ public final class LockscreenCardsNotificationStripRenderer {
private final SnapshotRenderer renderer = new SnapshotRenderer(false, false, false); private final SnapshotRenderer renderer = new SnapshotRenderer(false, false, false);
private final WeakHashMap<ViewGroup, ArrayList<View>> pillViewsByHost = new WeakHashMap<>(); private final WeakHashMap<ViewGroup, ArrayList<View>> pillViewsByHost = new WeakHashMap<>();
private final WeakHashMap<View, Integer> aodInitialStatusbarHeightByRoot = new WeakHashMap<>();
private final NotificationSeenAppReporter seenAppReporter = new NotificationSeenAppReporter(); private final NotificationSeenAppReporter seenAppReporter = new NotificationSeenAppReporter();
public RenderResult render( public RenderResult render(
@@ -38,28 +39,43 @@ public final class LockscreenCardsNotificationStripRenderer {
SbtSettings settings, SbtSettings settings,
SceneKey scene SceneKey scene
) { ) {
if (root != null && (scene == null || !scene.isAod())) {
aodInitialStatusbarHeightByRoot.remove(root);
}
if (root == null if (root == null
|| host == null || host == null
|| settings == null || settings == null
|| scene == null || scene == null
|| !scene.isLockscreen()
|| scene.notificationDisplayStyle() != NotificationDisplayStyle.CARDS || scene.notificationDisplayStyle() != NotificationDisplayStyle.CARDS
|| settings.cardsLockMaxRows <= 0) { || !(scene.isLockscreen() || scene.isAod())
|| maxRows(settings, scene) <= 0) {
clear(host); clear(host);
return RenderResult.empty(); return RenderResult.empty();
} }
ViewGroup sourceRoot = findSourceRoot(root); ViewGroup sourceRoot = scene.isAod()
if (sourceRoot == null) { ? LockscreenCardsNotificationIconSourceStore.sourceContainerForRoot(root)
: findSourceRoot(root);
if (sourceRoot == null && scene.isAod()) {
sourceRoot = findSourceRoot(root);
}
if (sourceRoot == null && !scene.isAod()) {
clear(host); clear(host);
return RenderResult.empty(); return RenderResult.empty();
} }
if (hasHiddenShelfAncestor(sourceRoot, root)) { if (!scene.isAod() && hasHiddenShelfAncestor(sourceRoot, root)) {
clear(host); clear(host);
return RenderResult.empty(); return RenderResult.empty();
} }
if (sourceRoot != null) {
syncHostAnimation(host, sourceRoot); 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); sources = collectSources(sourceRoot);
} }
if (sources.isEmpty()) { if (sources.isEmpty()) {
@@ -72,8 +88,24 @@ public final class LockscreenCardsNotificationStripRenderer {
unfilteredPlacements, unfilteredPlacements,
settings, settings,
scene); scene);
StripLayout layout = layoutPlacements(root, sourceRoot, placements, settings); Bounds sourceBounds = scene.isAod()
renderPills(host, layout.rowBounds, resolvePillColor(sourceRoot)); ? 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); Bounds renderedBounds = renderer.renderPlacements(host, layout.placements);
return new RenderResult(sourceRoot, renderedBounds); return new RenderResult(sourceRoot, renderedBounds);
} }
@@ -85,6 +117,12 @@ public final class LockscreenCardsNotificationStripRenderer {
public void clearAll() { public void clearAll() {
renderer.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) { private ViewGroup findSourceRoot(View root) {
@@ -172,17 +210,19 @@ public final class LockscreenCardsNotificationStripRenderer {
private StripLayout layoutPlacements( private StripLayout layoutPlacements(
View root, View root,
View sourceRoot, Bounds sourceBounds,
ArrayList<SnapshotPlacement> placements, ArrayList<SnapshotPlacement> placements,
SbtSettings settings SbtSettings settings,
SceneKey scene,
int iconHeight
) { ) {
ArrayList<SnapshotPlacement> result = new ArrayList<>(); ArrayList<SnapshotPlacement> result = new ArrayList<>();
ArrayList<Bounds> rowBounds = new ArrayList<>(); ArrayList<Bounds> rowBounds = new ArrayList<>();
if (placements == null || placements.isEmpty()) { if (placements == null || placements.isEmpty()) {
return new StripLayout(result, rowBounds); return new StripLayout(result, rowBounds);
} }
int maxPerRow = Math.max(1, settings.cardsLockMaxIconsPerRow); int maxPerRow = Math.max(1, maxIconsPerRow(settings, scene));
int maxRows = Math.max(0, settings.cardsLockMaxRows); int maxRows = Math.max(0, maxRows(settings, scene));
int capacity = maxPerRow * maxRows; int capacity = maxPerRow * maxRows;
if (capacity <= 0) { if (capacity <= 0) {
return new StripLayout(result, rowBounds); return new StripLayout(result, rowBounds);
@@ -190,18 +230,16 @@ public final class LockscreenCardsNotificationStripRenderer {
boolean showDot = placements.size() > capacity; boolean showDot = placements.size() > capacity;
int iconSlots = showDot ? Math.max(0, capacity - 1) : Math.min(capacity, placements.size()); int iconSlots = showDot ? Math.max(0, capacity - 1) : Math.min(capacity, placements.size());
int visible = iconSlots + (showDot ? 1 : 0); int visible = iconSlots + (showDot ? 1 : 0);
int iconHeight = representativeHeight(placements); int iconWidth = iconHeight;
int iconWidth = representativeWidth(placements, iconHeight);
Bounds sourceBounds = ViewGeometry.boundsRelativeTo(root, sourceRoot);
int top = sourceBounds != null ? sourceBounds.top : placements.get(0).bounds.top; int top = sourceBounds != null ? sourceBounds.top : placements.get(0).bounds.top;
int centerX = sourceBounds != null int centerX = sourceBounds != null
? sourceBounds.left + sourceBounds.width / 2 ? sourceBounds.left + sourceBounds.width / 2
: root.getWidth() / 2; : root.getWidth() / 2;
int availableWidth = settings.cardsLockLimitToWidth int availableWidth = limitToWidth(settings, scene)
? Math.max(0, root.getWidth()) ? Math.max(0, root.getWidth())
: Integer.MAX_VALUE; : Integer.MAX_VALUE;
int index = 0; 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 dotColor = dotColor(placements);
int rowGap = dp(root, 8); int rowGap = dp(root, 8);
for (int row = 0; row < rowCounts.length && index < visible; row++) { for (int row = 0; row < rowCounts.length && index < visible; row++) {
@@ -236,6 +274,30 @@ public final class LockscreenCardsNotificationStripRenderer {
return new StripLayout(result, rowBounds); 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( private int[] rowCounts(
int slots, int slots,
int maxPerRow, int maxPerRow,
@@ -459,15 +521,6 @@ public final class LockscreenCardsNotificationStripRenderer {
return params; 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) { private int representativeHeight(ArrayList<SnapshotPlacement> placements) {
for (SnapshotPlacement placement : placements) { for (SnapshotPlacement placement : placements) {
if (placement != null && placement.bounds != null && placement.bounds.height > 0) { if (placement != null && placement.bounds != null && placement.bounds.height > 0) {
@@ -477,6 +530,50 @@ public final class LockscreenCardsNotificationStripRenderer {
return 1; 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) { private boolean isShelfIconContainer(View view) {
String className = view.getClass().getName(); String className = view.getClass().getName();
return className.contains("SecShelfNotificationIconContainer"); return className.contains("SecShelfNotificationIconContainer");
@@ -6,24 +6,34 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings; import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class NotificationSeenAppReporter { 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, Long> lastReportedMs = new HashMap<>();
private final Map<String, HashSet<String>> activeKeysByPackage = new HashMap<>();
void report(Context context, ArrayList<SnapshotPlacement> placements) { void report(Context context, ArrayList<SnapshotPlacement> placements) {
if (context == null || placements == null || placements.isEmpty()) { if (context == null || placements == null || placements.isEmpty()) {
activeKeysByPackage.clear();
return; return;
} }
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
HashMap<String, HashSet<String>> currentKeysByPackage = new HashMap<>();
HashSet<String> reportedThisPass = new HashSet<>(); HashSet<String> reportedThisPass = new HashSet<>();
for (SnapshotPlacement placement : placements) { for (SnapshotPlacement placement : placements) {
String packageName = SnapshotIconFilter.notificationPackage( String key = placement != null ? placement.key : null;
placement != null ? placement.key : null); String packageName = SnapshotIconFilter.notificationPackage(key);
if (packageName == null || !reportedThisPass.add(packageName)) { if (packageName == null || key == null) {
continue;
}
currentKeysByPackage
.computeIfAbsent(packageName, ignored -> new HashSet<>())
.add(key);
if (wasAlreadyActive(packageName, key) || !reportedThisPass.add(packageName)) {
continue; continue;
} }
Long lastReported = lastReportedMs.get(packageName); Long lastReported = lastReportedMs.get(packageName);
@@ -33,5 +43,12 @@ final class NotificationSeenAppReporter {
lastReportedMs.put(packageName, now); lastReportedMs.put(packageName, now);
SbtSettings.noteSeenIconApp(context, 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"; "statusbartweak.unlocked.chip.host";
public static final String TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST = public static final String TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST =
"statusbartweak.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 = { private static final String[] UNLOCKED_HOST_ORDER = {
TAG_UNLOCKED_STATUS_HOST, TAG_UNLOCKED_STATUS_HOST,
TAG_UNLOCKED_NOTIFICATION_HOST, TAG_UNLOCKED_NOTIFICATION_HOST,
@@ -60,6 +62,10 @@ public final class OwnedIconHostManager {
return ensureHost(root, TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST); 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) { public FrameLayout ensureHost(View root, String tag) {
Objects.requireNonNull(tag, "tag"); Objects.requireNonNull(tag, "tag");
if (!(root instanceof ViewGroup parent)) { if (!(root instanceof ViewGroup parent)) {
@@ -99,6 +105,7 @@ public final class OwnedIconHostManager {
removeHost(root, TAG_UNLOCKED_CARRIER_HOST); removeHost(root, TAG_UNLOCKED_CARRIER_HOST);
removeHost(root, TAG_UNLOCKED_CHIP_HOST); removeHost(root, TAG_UNLOCKED_CHIP_HOST);
removeHost(root, TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST); removeHost(root, TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST);
removeHost(root, TAG_AOD_CARDS_NOTIFICATION_HOST);
} }
public void setUnlockedHostsVisible(View root, boolean visible) { public void setUnlockedHostsVisible(View root, boolean visible) {
@@ -130,7 +137,8 @@ public final class OwnedIconHostManager {
|| TAG_UNLOCKED_CLOCK_HOST.equals(tag) || TAG_UNLOCKED_CLOCK_HOST.equals(tag)
|| TAG_UNLOCKED_CARRIER_HOST.equals(tag) || TAG_UNLOCKED_CARRIER_HOST.equals(tag)
|| TAG_UNLOCKED_CHIP_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) { private FrameLayout findDirectHost(ViewGroup parent, String tag) {
@@ -156,6 +156,7 @@ final class SnapshotIconFilter {
append(sb, placement.key); append(sb, placement.key);
View view = placement.source != null ? placement.source.view() : null; View view = placement.source != null ? placement.source.view() : null;
appendViewText(sb, view); appendViewText(sb, view);
appendAncestorViewText(sb, view);
return sb.toString().toLowerCase(Locale.ROOT); return sb.toString().toLowerCase(Locale.ROOT);
} }
@@ -169,6 +170,15 @@ final class SnapshotIconFilter {
append(sb, contentDescription != null ? contentDescription.toString() : null); 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) { private static ArrayList<String> statusSlots(String matchText) {
ArrayList<String> slots = new ArrayList<>(); ArrayList<String> slots = new ArrayList<>();
add(slots, normalizedSlot(matchText)); add(slots, normalizedSlot(matchText));
@@ -12,16 +12,23 @@ import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bou
record SnapshotSource( record SnapshotSource(
View view, View view,
SnapshotMode mode, 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) { SnapshotSource(View view, SnapshotMode mode) {
this(view, mode, null); this(view, mode, null, null);
} }
} }
enum SnapshotMode { enum SnapshotMode {
VIEW, VIEW,
NOTIFICATION_DRAWABLE STATUS_CHIP,
NOTIFICATION_DRAWABLE,
APP_ICON
} }
final class SnapshotKeys { final class SnapshotKeys {
@@ -43,6 +50,7 @@ final class SnapshotPlacement {
final HeldSignal heldSignal; final HeldSignal heldSignal;
final Bounds contentBounds; final Bounds contentBounds;
final int sourceOffsetX; final int sourceOffsetX;
final Bounds sourceRenderBounds;
SnapshotPlacement( SnapshotPlacement(
SnapshotSource source, SnapshotSource source,
@@ -63,7 +71,8 @@ final class SnapshotPlacement {
Color.WHITE, Color.WHITE,
heldSignal, heldSignal,
contentBounds, contentBounds,
sourceOffsetX); sourceOffsetX,
null);
} }
SnapshotPlacement( SnapshotPlacement(
@@ -76,6 +85,31 @@ final class SnapshotPlacement {
HeldSignal heldSignal, HeldSignal heldSignal,
Bounds contentBounds, Bounds contentBounds,
int sourceOffsetX 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.source = source;
this.bounds = bounds; this.bounds = bounds;
@@ -86,6 +120,7 @@ final class SnapshotPlacement {
this.heldSignal = heldSignal; this.heldSignal = heldSignal;
this.contentBounds = contentBounds; this.contentBounds = contentBounds;
this.sourceOffsetX = sourceOffsetX; this.sourceOffsetX = sourceOffsetX;
this.sourceRenderBounds = sourceRenderBounds;
} }
SnapshotSource source() { SnapshotSource source() {
@@ -123,6 +158,10 @@ final class SnapshotPlacement {
int sourceOffsetX() { int sourceOffsetX() {
return sourceOffsetX; return sourceOffsetX;
} }
Bounds sourceRenderBounds() {
return sourceRenderBounds;
}
} }
record BitmapResult(Bitmap bitmap, boolean created) { record BitmapResult(Bitmap bitmap, boolean created) {
@@ -130,14 +169,13 @@ record BitmapResult(Bitmap bitmap, boolean created) {
record SnapshotRenderState( record SnapshotRenderState(
String key, String key,
int left,
int top,
int width, int width,
int height, int height,
boolean signal, boolean signal,
boolean overflowDot, boolean overflowDot,
int overflowDotColor, int overflowDotColor,
int sourceOffsetX, int sourceOffsetX,
int sourceRenderBoundsSignature,
String heldSignalKey, String heldSignalKey,
int heldBitmapId, int heldBitmapId,
SnapshotMode sourceMode, SnapshotMode sourceMode,
@@ -1,5 +1,7 @@
package se.ajpanton.statusbartweak.runtime.render; package se.ajpanton.statusbartweak.runtime.render;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
@@ -20,6 +22,7 @@ import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.IdentityHashMap;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
@@ -35,6 +38,8 @@ final class SnapshotRenderer {
new WeakHashMap<>(); new WeakHashMap<>();
private final WeakHashMap<ImageView, SnapshotRenderState> statesByProxy = private final WeakHashMap<ImageView, SnapshotRenderState> statesByProxy =
new WeakHashMap<>(); new WeakHashMap<>();
private final WeakHashMap<View, SourceSignatureCache> sourceSignaturesByView =
new WeakHashMap<>();
private final LinkedHashMap<String, HeldSignal> heldSignals = new LinkedHashMap<>(); private final LinkedHashMap<String, HeldSignal> heldSignals = new LinkedHashMap<>();
private long holdSignalsUntilElapsedMs; private long holdSignalsUntilElapsedMs;
@@ -58,11 +63,21 @@ final class SnapshotRenderer {
ArrayList<SnapshotPlacement> rawPlacements( ArrayList<SnapshotPlacement> rawPlacements(
View root, View root,
ArrayList<SnapshotSource> sources ArrayList<SnapshotSource> sources
) {
return rawPlacements(root, sources, sortPlacementsByX);
}
ArrayList<SnapshotPlacement> rawPlacements(
View root,
ArrayList<SnapshotSource> sources,
boolean sortByX
) { ) {
ArrayList<SnapshotPlacement> placements = new ArrayList<>(); ArrayList<SnapshotPlacement> placements = new ArrayList<>();
int order = 0; int order = 0;
for (SnapshotSource source : sources) { 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 (bounds != null && bounds.width > 0 && bounds.height > 0) {
if (source.mode() == SnapshotMode.NOTIFICATION_DRAWABLE) { if (source.mode() == SnapshotMode.NOTIFICATION_DRAWABLE) {
bounds = notificationDrawableBounds(bounds); bounds = notificationDrawableBounds(bounds);
@@ -112,7 +127,7 @@ final class SnapshotRenderer {
} }
order++; order++;
} }
if (sortPlacementsByX) { if (sortByX) {
placements.sort(Comparator.comparingInt(placement -> placement.bounds().left)); placements.sort(Comparator.comparingInt(placement -> placement.bounds().left));
} }
return placements; return placements;
@@ -176,11 +191,14 @@ final class SnapshotRenderer {
|| !desiredState.equals(statesByProxy.get(proxy)); || !desiredState.equals(statesByProxy.get(proxy));
if (bitmapChanged) { if (bitmapChanged) {
if (placement.overflowDot()) { if (placement.overflowDot()) {
drawOverflowDot(bitmap, placement.overflowDotColor()); drawOverflowDot(bitmap, placement.overflowDotColor(), placement.sourceOffsetX());
} else if (placement.heldSignal() != null) { } else if (placement.heldSignal() != null) {
drawHeldSignal(placement.heldSignal(), bitmap); 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) { } else if (placement.source() != null) {
drawSnapshot(placement.source(), bitmap, placement.sourceOffsetX()); drawSnapshot(placement, bitmap);
if (placement.signal()) { if (placement.signal()) {
saveHeldSignal(key, bounds, bitmap); saveHeldSignal(key, bounds, bitmap);
} }
@@ -265,7 +283,9 @@ final class SnapshotRenderer {
} }
private boolean shouldTrimContent(SnapshotSource source) { 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) { private Bounds bitmapContentBounds(Bitmap bitmap) {
@@ -351,6 +371,9 @@ final class SnapshotRenderer {
if (keyHint != null && !keyHint.isEmpty()) { if (keyHint != null && !keyHint.isEmpty()) {
return keyHint; return keyHint;
} }
if (view == null) {
return source.mode().name().toLowerCase(java.util.Locale.ROOT) + "#" + order;
}
String slot = reflectString(view, "getSlot", "mSlot", "slot"); String slot = reflectString(view, "getSlot", "mSlot", "slot");
if (!slot.isEmpty()) { if (!slot.isEmpty()) {
return slot; return slot;
@@ -363,6 +386,9 @@ final class SnapshotRenderer {
} }
private String signalKind(View view, String key) { private String signalKind(View view, String key) {
if (view == null || key == null) {
return null;
}
String lowerKey = key.toLowerCase(java.util.Locale.ROOT); String lowerKey = key.toLowerCase(java.util.Locale.ROOT);
if (lowerKey.contains("wifi")) { if (lowerKey.contains("wifi")) {
return "wifi"; return "wifi";
@@ -475,24 +501,60 @@ final class SnapshotRenderer {
Bounds bounds = placement.bounds(); Bounds bounds = placement.bounds();
return new SnapshotRenderState( return new SnapshotRenderState(
placement.key(), placement.key(),
bounds.left,
bounds.top,
bounds.width, bounds.width,
bounds.height, bounds.height,
placement.signal(), placement.signal(),
placement.overflowDot(), placement.overflowDot(),
placement.overflowDotColor(), placement.overflowDotColor(),
placement.sourceOffsetX(), placement.sourceOffsetX(),
boundsSignature(placement.sourceRenderBounds()),
placement.heldSignal() != null ? placement.heldSignal().key : "", placement.heldSignal() != null ? placement.heldSignal().key : "",
placement.heldSignal() != null ? System.identityHashCode(placement.heldSignal().bitmap) : 0, placement.heldSignal() != null ? System.identityHashCode(placement.heldSignal().bitmap) : 0,
placement.source() != null ? placement.source().mode() : null, 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) { private int sourceTreeSignature(View view) {
if (view == null) { if (view == null) {
return 0; 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; int result = 17;
result = 31 * result + System.identityHashCode(view); result = 31 * result + System.identityHashCode(view);
result = 31 * result + view.getVisibility(); result = 31 * result + view.getVisibility();
@@ -523,6 +585,53 @@ final class SnapshotRenderer {
return result; 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) { private int drawableSignature(Drawable drawable) {
if (drawable == null) { if (drawable == null) {
return 0; return 0;
@@ -548,9 +657,22 @@ final class SnapshotRenderer {
canvas.translate(-offsetX, 0); canvas.translate(-offsetX, 0);
} }
View sourceView = source.view(); View sourceView = source.view();
if (sourceView == null) {
return;
}
int oldVisibility = sourceView.getVisibility(); int oldVisibility = sourceView.getVisibility();
float oldAlpha = sourceView.getAlpha(); float oldAlpha = sourceView.getAlpha();
try { 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 if (source.mode() == SnapshotMode.NOTIFICATION_DRAWABLE
&& drawNotificationDrawable( && drawNotificationDrawable(
sourceView, sourceView,
@@ -561,13 +683,287 @@ final class SnapshotRenderer {
} }
sourceView.setVisibility(View.VISIBLE); sourceView.setVisibility(View.VISIBLE);
sourceView.setAlpha(1f); sourceView.setAlpha(1f);
if (source.mode() == SnapshotMode.STATUS_CHIP) {
drawTemporarilySizedView(sourceView, canvas, bitmap.getWidth(), bitmap.getHeight());
} else {
sourceView.draw(canvas); sourceView.draw(canvas);
}
} finally { } finally {
sourceView.setAlpha(oldAlpha); sourceView.setAlpha(oldAlpha);
sourceView.setVisibility(oldVisibility); 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) { private void drawHeldSignal(HeldSignal heldSignal, Bitmap bitmap) {
bitmap.eraseColor(Color.TRANSPARENT); bitmap.eraseColor(Color.TRANSPARENT);
if (heldSignal.bitmap == null || heldSignal.bitmap.isRecycled()) { if (heldSignal.bitmap == null || heldSignal.bitmap.isRecycled()) {
@@ -579,16 +975,18 @@ final class SnapshotRenderer {
canvas.drawBitmap(heldSignal.bitmap, src, dst, null); 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); bitmap.eraseColor(Color.TRANSPARENT);
if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) { if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
return; 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 paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color); paint.setColor(color);
Canvas canvas = new Canvas(bitmap); 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) { 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; package se.ajpanton.statusbartweak.runtime.render;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList; import java.util.ArrayList;
@@ -9,15 +11,13 @@ import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class UnlockedChipPlacementController { final class UnlockedChipPlacementController {
private final SnapshotRenderer renderer; private final SnapshotRenderer renderer;
private final UnlockedVerticalOffsetScaler offsetScaler;
private ChipHoldState heldPlacements; private ChipHoldState heldPlacements;
private Bounds lastCollisionBounds; private Bounds lastCollisionBounds;
private int lastRootWidth; private int lastRootWidth;
private int lastRootHeight; private int lastRootHeight;
UnlockedChipPlacementController(SnapshotRenderer renderer, UnlockedVerticalOffsetScaler offsetScaler) { UnlockedChipPlacementController(SnapshotRenderer renderer) {
this.renderer = renderer; this.renderer = renderer;
this.offsetScaler = offsetScaler;
} }
void clear() { void clear() {
@@ -33,31 +33,169 @@ final class UnlockedChipPlacementController {
ArrayList<SnapshotSource> metricSources, ArrayList<SnapshotSource> metricSources,
SbtSettings settings SbtSettings settings
) { ) {
ArrayList<SnapshotPlacement> rawPlacements = renderer.rawPlacements(root, visibleSources); ArrayList<SnapshotPlacement> rawPlacements = renderer.rawPlacements(
root,
withNaturalChipBounds(root, visibleSources));
rawPlacements = withVisualChipCollision(rawPlacements);
if (!rawPlacements.isEmpty()) { if (!rawPlacements.isEmpty()) {
rememberMetrics(root, rawPlacements); rememberMetrics(root, rawPlacements);
ArrayList<SnapshotPlacement> placements = offsetPlacements(
rawPlacements,
offsetScaler.chip(
settings.layoutChipVerticalOffsetPx,
representativeChipHeight(rawPlacements)));
heldPlacements = new ChipHoldState( heldPlacements = new ChipHoldState(
copyAsHeldPlacements(placements), copyAsHeldPlacements(rawPlacements),
root.getWidth(), root.getWidth(),
root.getHeight(), root.getHeight(),
0); 0);
return placements; return rawPlacements;
} }
ArrayList<SnapshotPlacement> placements = isAnyChipHidingEnabled(settings) ArrayList<SnapshotPlacement> placements = isAnyChipHidingEnabled(settings)
? new ArrayList<>() ? new ArrayList<>()
: heldPlacements(root); : heldPlacements(root);
if (placements.isEmpty()) { if (placements.isEmpty()) {
placements = emptyPlacement(root, metricSources, settings); placements = emptyPlacement(root, metricSources);
} }
return placements; 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) { private ArrayList<SnapshotPlacement> heldPlacements(View root) {
ChipHoldState state = heldPlacements; ChipHoldState state = heldPlacements;
if (state == null || state.placements.isEmpty()) { if (state == null || state.placements.isEmpty()) {
@@ -81,20 +219,16 @@ final class UnlockedChipPlacementController {
private ArrayList<SnapshotPlacement> emptyPlacement( private ArrayList<SnapshotPlacement> emptyPlacement(
View root, View root,
ArrayList<SnapshotSource> sources, ArrayList<SnapshotSource> sources
SbtSettings settings
) { ) {
ArrayList<SnapshotPlacement> result = new ArrayList<>(); ArrayList<SnapshotPlacement> result = new ArrayList<>();
Bounds bounds = emptyBounds(root, sources); Bounds bounds = emptyBounds(root, sources);
if (bounds == null || bounds.height <= 0) { if (bounds == null || bounds.height <= 0) {
return result; return result;
} }
int verticalOffset = offsetScaler.chip(
settings != null ? settings.layoutChipVerticalOffsetPx : 0,
bounds.height);
result.add(new SnapshotPlacement( result.add(new SnapshotPlacement(
null, null,
new Bounds(0, bounds.top - verticalOffset, 1, bounds.height), new Bounds(0, bounds.top, 1, bounds.height),
SnapshotKeys.EMPTY_CHIP, SnapshotKeys.EMPTY_CHIP,
false, false,
false, false,
@@ -119,7 +253,9 @@ final class UnlockedChipPlacementController {
return null; return null;
} }
for (SnapshotSource source : sources) { 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) { if (bounds == null || bounds.width <= 0 || bounds.height <= 0) {
continue; continue;
} }
@@ -198,40 +334,6 @@ final class UnlockedChipPlacementController {
return held; 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( private record ChipHoldState(
ArrayList<SnapshotPlacement> placements, ArrayList<SnapshotPlacement> placements,
int rootWidth, int rootWidth,
@@ -5,10 +5,14 @@ import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.features.RuntimeContext;
import se.ajpanton.statusbartweak.runtime.features.clock.ClockLayoutTextFactory; import se.ajpanton.statusbartweak.runtime.features.clock.ClockLayoutTextFactory;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; 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.mode.SceneKey;
import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache; import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings; 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. * into owned unlocked rows instead of copying each stock X position.
*/ */
public final class UnlockedIconSnapshotRenderController { public final class UnlockedIconSnapshotRenderController {
private final RuntimeContext runtimeContext;
private final SnapshotRenderer statusRenderer; private final SnapshotRenderer statusRenderer;
private final SnapshotRenderer notificationRenderer; private final SnapshotRenderer notificationRenderer;
private final SnapshotRenderer chipRenderer; private final SnapshotRenderer chipRenderer;
private final ClockProxyRenderer clockRenderer = new ClockProxyRenderer(); private final ClockProxyRenderer clockRenderer = new ClockProxyRenderer();
private final ClockProxyRenderer carrierRenderer = 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 UnlockedVerticalOffsetScaler offsetScaler = new UnlockedVerticalOffsetScaler();
private final UnlockedChipPlacementController chipPlacementController; private final UnlockedChipPlacementController chipPlacementController;
private final UnlockedLayoutPlanner layoutPlanner; private final UnlockedLayoutPlanner layoutPlanner;
private final WeakHashMap<View, Integer> clockBaselineDeltaByRoot = new WeakHashMap<>(); 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, LayoutInputSignature> lastLayoutSignatureByRoot = new WeakHashMap<>();
private final WeakHashMap<View, CollectedIcons> lastCollectedIconsByRoot = new WeakHashMap<>(); private final WeakHashMap<View, CollectedIcons> lastCollectedIconsByRoot = new WeakHashMap<>();
private final WeakHashMap<View, LayoutPlan> lastLayoutPlanByRoot = new WeakHashMap<>(); private final WeakHashMap<View, LayoutPlan> lastLayoutPlanByRoot = new WeakHashMap<>();
private final WeakHashMap<View, Integer> lastClockGeometryByRoot = new WeakHashMap<>(); private final WeakHashMap<View, Integer> lastClockGeometryByRoot = new WeakHashMap<>();
private final WeakHashMap<View, Integer> lastClockSourceGeometryByRoot = new WeakHashMap<>(); private final WeakHashMap<View, Integer> lastClockSourceGeometryByRoot = new WeakHashMap<>();
private final WeakHashMap<View, Integer> lastClockAppearanceByRoot = new WeakHashMap<>(); private final WeakHashMap<View, Integer> lastClockAppearanceByRoot = new WeakHashMap<>();
private final WeakHashMap<View, Boolean> forcedChipRefreshByRoot = new WeakHashMap<>();
public UnlockedIconSnapshotRenderController() { public UnlockedIconSnapshotRenderController() {
this(null);
}
public UnlockedIconSnapshotRenderController(RuntimeContext runtimeContext) {
this.runtimeContext = runtimeContext;
sourceCollector = new UnlockedStockSourceCollector(runtimeContext);
statusRenderer = new SnapshotRenderer(true, true, false); statusRenderer = new SnapshotRenderer(true, true, false);
notificationRenderer = new SnapshotRenderer(false, false, false); notificationRenderer = new SnapshotRenderer(false, false, false);
chipRenderer = new SnapshotRenderer(false, false, true); chipRenderer = new SnapshotRenderer(false, false, true);
chipPlacementController = new UnlockedChipPlacementController(chipRenderer, offsetScaler); chipPlacementController = new UnlockedChipPlacementController(chipRenderer);
layoutPlanner = new UnlockedLayoutPlanner( layoutPlanner = new UnlockedLayoutPlanner(
statusRenderer, statusRenderer,
notificationRenderer, notificationRenderer,
chipRenderer, chipRenderer,
chipPlacementController, chipPlacementController,
offsetScaler); offsetScaler,
runtimeContext);
} }
public RenderResult renderUnlocked( public RenderResult renderUnlocked(
@@ -60,7 +74,9 @@ public final class UnlockedIconSnapshotRenderController {
SceneKey scene, SceneKey scene,
TextView externalCarrierView TextView externalCarrierView
) { ) {
if (scene == null || (!scene.isUnlocked() && !scene.isLockscreen()) || !root.isAttachedToWindow()) { if (scene == null
|| (!scene.isUnlocked() && !scene.isLockscreen() && !scene.isAod())
|| !root.isAttachedToWindow()) {
clearAll(); clearAll();
return new RenderResult(true, true); return new RenderResult(true, true);
} }
@@ -92,11 +108,14 @@ public final class UnlockedIconSnapshotRenderController {
anchors, anchors,
previousIcons); previousIcons);
LayoutInputSignature previousSignature = lastLayoutSignatureByRoot.get(root); 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 boolean stockSourcesChanged = previousSignature == null
|| signature.stockSurfaceSignature() != previousSignature.stockSurfaceSignature(); || signature.stockSurfaceSignature() != previousSignature.stockSurfaceSignature();
boolean chipSourcesChanged = previousSignature == null boolean chipSourcesChanged = previousSignature == null
|| signature.chipSources != previousSignature.chipSources; || signature.chipSources != previousSignature.chipSources;
if (signature.equals(previousSignature)) { if (signature.equals(previousSignature) && !forceChipRefresh) {
return new RenderResult( return new RenderResult(
false, false,
false, false,
@@ -109,6 +128,7 @@ public final class UnlockedIconSnapshotRenderController {
ClockLayout currentClockLayout = null; ClockLayout currentClockLayout = null;
Integer currentClockGeometry = null; Integer currentClockGeometry = null;
if (isClockOnlySignatureChange(previousSignature, signature) if (isClockOnlySignatureChange(previousSignature, signature)
&& !forceChipRefresh
&& scene.isUnlocked() && scene.isUnlocked()
&& settings.clockEnabledUnlocked && settings.clockEnabledUnlocked
&& previousIcons != null) { && previousIcons != null) {
@@ -158,8 +178,11 @@ public final class UnlockedIconSnapshotRenderController {
} }
} }
CollectedIcons collectedIcons = previousIcons; CollectedIcons collectedIcons = previousIcons;
if (stockSourcesChanged || collectedIcons == null) { if (forceChipRefresh && collectedIcons != null) {
collectedIcons = sourceCollector.collect(root, anchors); 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); lastCollectedIconsByRoot.put(root, collectedIcons);
} else if (chipSourcesChanged) { } else if (chipSourcesChanged) {
collectedIcons = sourceCollector.collectChips(root, anchors, collectedIcons); collectedIcons = sourceCollector.collectChips(root, anchors, collectedIcons);
@@ -190,7 +213,6 @@ public final class UnlockedIconSnapshotRenderController {
settings, settings,
currentClockLayout, currentClockLayout,
scene); scene);
lastLayoutPlanByRoot.put(root, layoutPlan);
lastClockGeometryByRoot.put(root, currentClockGeometry != null ? currentClockGeometry : 0); lastClockGeometryByRoot.put(root, currentClockGeometry != null ? currentClockGeometry : 0);
lastClockSourceGeometryByRoot.put(root, currentClockSourceGeometry); lastClockSourceGeometryByRoot.put(root, currentClockSourceGeometry);
layoutPlan = withCurrentDotColors(layoutPlan, collectedIcons); layoutPlan = withCurrentDotColors(layoutPlan, collectedIcons);
@@ -200,7 +222,9 @@ public final class UnlockedIconSnapshotRenderController {
} else { } else {
clockRenderer.clear(clockHost); clockRenderer.clear(clockHost);
} }
if (scene.isLockscreen() && settings.layoutCarrierEnabledLockscreen && layoutPlan.carrierPlacement != null) { if (scene.isLockscreen()
&& settings.layoutCarrierEnabledLockscreen
&& layoutPlan.carrierPlacement != null) {
carrierRenderer.render(carrierHost, layoutPlan.carrierPlacement); carrierRenderer.render(carrierHost, layoutPlan.carrierPlacement);
} else { } else {
carrierRenderer.clear(carrierHost); 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( public boolean refreshUnlockedClockText(
View root, View root,
ViewGroup clockHost, ViewGroup clockHost,
@@ -273,7 +323,7 @@ public final class UnlockedIconSnapshotRenderController {
if (root == null if (root == null
|| clockHost == null || clockHost == null
|| scene == null || scene == null
|| !scene.isUnlocked() || (!scene.isUnlocked() && !scene.isLockscreen())
|| !root.isAttachedToWindow()) { || !root.isAttachedToWindow()) {
return false; return false;
} }
@@ -282,7 +332,7 @@ public final class UnlockedIconSnapshotRenderController {
return false; return false;
} }
SbtSettings settings = RuntimeSettingsCache.get(root.getContext()); SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
if (settings == null || !settings.clockEnabledUnlocked) { if (settings == null || !clockEnabledForScene(settings, scene)) {
return false; return false;
} }
CollectedIcons collectedIcons = lastCollectedIconsByRoot.get(root); CollectedIcons collectedIcons = lastCollectedIconsByRoot.get(root);
@@ -355,9 +405,13 @@ public final class UnlockedIconSnapshotRenderController {
if (settings == null || scene == null) { if (settings == null || scene == null) {
return false; return false;
} }
return scene.isLockscreen() if (scene.isLockscreen()) {
? settings.layoutStatusEnabledLockscreen return settings.layoutStatusEnabledLockscreen;
: settings.layoutStatusEnabledUnlocked; }
if (scene.isAod()) {
return settings.layoutStatusEnabledAod;
}
return settings.layoutStatusEnabledUnlocked;
} }
private boolean notificationsEnabledForScene(SbtSettings settings, SceneKey scene) { private boolean notificationsEnabledForScene(SbtSettings settings, SceneKey scene) {
@@ -366,8 +420,14 @@ public final class UnlockedIconSnapshotRenderController {
} }
if (scene.isLockscreen()) { if (scene.isLockscreen()) {
return settings.layoutNotifEnabledLockscreen return settings.layoutNotifEnabledLockscreen
&& scene.notificationDisplayStyle() != se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle.NONE && scene.notificationDisplayStyle() != NotificationDisplayStyle.NONE
&& scene.notificationDisplayStyle() != se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle.CARDS; && scene.notificationDisplayStyle() != NotificationDisplayStyle.CARDS;
}
if (scene.isAod()) {
NotificationDisplayStyle style = scene.notificationDisplayStyle();
return settings.layoutNotifEnabledAod
&& (style == NotificationDisplayStyle.ICONS
|| style == NotificationDisplayStyle.DOT);
} }
return settings.layoutNotifEnabledUnlocked; return settings.layoutNotifEnabledUnlocked;
} }
@@ -556,6 +616,8 @@ public final class UnlockedIconSnapshotRenderController {
chipRenderer.clearAll(); chipRenderer.clearAll();
sourceCollector.clear(); sourceCollector.clear();
clockBaselineDeltaByRoot.clear(); clockBaselineDeltaByRoot.clear();
clockSourceBaselineByRootKey.clear();
forcedChipRefreshByRoot.clear();
lastLayoutSignatureByRoot.clear(); lastLayoutSignatureByRoot.clear();
lastCollectedIconsByRoot.clear(); lastCollectedIconsByRoot.clear();
lastLayoutPlanByRoot.clear(); lastLayoutPlanByRoot.clear();
@@ -565,9 +627,31 @@ public final class UnlockedIconSnapshotRenderController {
chipPlacementController.clear(); 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() { public void beginRootTransition() {
statusRenderer.beginTransitionHold(); statusRenderer.beginTransitionHold();
sourceCollector.clear(); sourceCollector.clear();
forcedChipRefreshByRoot.clear();
lastLayoutSignatureByRoot.clear(); lastLayoutSignatureByRoot.clear();
lastCollectedIconsByRoot.clear(); lastCollectedIconsByRoot.clear();
lastLayoutPlanByRoot.clear(); lastLayoutPlanByRoot.clear();
@@ -615,12 +699,12 @@ public final class UnlockedIconSnapshotRenderController {
if (model == null || model.isEmpty()) { if (model == null || model.isEmpty()) {
return null; return null;
} }
int baselineY = stableClockBaselineY(root, source, clockContainer);
return ClockLayout.measure( return ClockLayout.measure(
source, source,
model, model,
UnlockedLayoutPlanner.positionFor(settings.clockPosition), UnlockedLayoutPlanner.positionFor(settings.clockPosition),
stableClockBaselineY(root, source, clockContainer) baselineY - offsetScaler.clock(settings.clockVerticalOffsetPx, source),
- offsetScaler.clock(settings.clockVerticalOffsetPx, source),
root.getWidth(), root.getWidth(),
cutoutBounds, cutoutBounds,
settings.clockMiddleAutoNearestSide, settings.clockMiddleAutoNearestSide,
@@ -872,6 +956,7 @@ public final class UnlockedIconSnapshotRenderController {
int sourceBaseline = source.getBaseline() > 0 int sourceBaseline = source.getBaseline() > 0
? source.getBaseline() ? source.getBaseline()
: (sourceBounds != null ? Math.max(0, (sourceBounds.height * 3) / 4) : 0); : (sourceBounds != null ? Math.max(0, (sourceBounds.height * 3) / 4) : 0);
sourceBaseline = stableClockSourceBaseline(root, source, sourceBaseline);
Bounds containerBounds = ViewGeometry.boundsRelativeTo(root, clockContainer); Bounds containerBounds = ViewGeometry.boundsRelativeTo(root, clockContainer);
if (containerBounds != null if (containerBounds != null
&& containerBounds.width > 0 && containerBounds.width > 0
@@ -890,6 +975,28 @@ public final class UnlockedIconSnapshotRenderController {
return sourceBounds != null ? sourceBounds.top + sourceBaseline : 0; 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( public record RenderResult(
boolean layoutChanged, boolean layoutChanged,
boolean stockSourcesChanged, boolean stockSourcesChanged,
@@ -18,6 +18,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
final SnapshotRenderer renderer; final SnapshotRenderer renderer;
final ArrayList<SnapshotPlacement> rawPlacements; final ArrayList<SnapshotPlacement> rawPlacements;
final boolean showDotIfTruncated; final boolean showDotIfTruncated;
final boolean forceOverflowDotOnly;
final Bounds reservedBounds; final Bounds reservedBounds;
final ClockLayout clockLayout; final ClockLayout clockLayout;
@@ -26,6 +27,8 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
Bounds placedBounds; Bounds placedBounds;
int clipStartPx; int clipStartPx;
int clipEndPx; int clipEndPx;
private int solverCollisionTop = Integer.MIN_VALUE;
private int solverCollisionHeight = Integer.MIN_VALUE;
private UnlockedLayoutBand( private UnlockedLayoutBand(
LayoutItemKind kind, LayoutItemKind kind,
@@ -36,6 +39,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
boolean middleAutoNearestSide, boolean middleAutoNearestSide,
AnchorSide middleSide, AnchorSide middleSide,
boolean showDotIfTruncated, boolean showDotIfTruncated,
boolean forceOverflowDotOnly,
Bounds reservedBounds, Bounds reservedBounds,
ClockLayout clockLayout, ClockLayout clockLayout,
SplitRegion splitRegion SplitRegion splitRegion
@@ -45,6 +49,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
this.renderer = renderer; this.renderer = renderer;
this.rawPlacements = rawPlacements; this.rawPlacements = rawPlacements;
this.showDotIfTruncated = showDotIfTruncated; this.showDotIfTruncated = showDotIfTruncated;
this.forceOverflowDotOnly = forceOverflowDotOnly;
this.reservedBounds = reservedBounds; this.reservedBounds = reservedBounds;
this.clockLayout = clockLayout; this.clockLayout = clockLayout;
} }
@@ -64,6 +69,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
settings.clockMiddleAutoNearestSide, settings.clockMiddleAutoNearestSide,
sideFor(settings.clockMiddleCutoutSide), sideFor(settings.clockMiddleCutoutSide),
false, false,
false,
null, null,
clockLayout, clockLayout,
SplitRegion.NONE); SplitRegion.NONE);
@@ -84,6 +90,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
settings.clockMiddleAutoNearestSide, settings.clockMiddleAutoNearestSide,
sideFor(settings.clockMiddleCutoutSide), sideFor(settings.clockMiddleCutoutSide),
false, false,
false,
null, null,
clockLayout, clockLayout,
splitRegion); splitRegion);
@@ -105,6 +112,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
middleAutoNearestSide, middleAutoNearestSide,
middleSide, middleSide,
false, false,
false,
null, null,
carrierLayout, carrierLayout,
SplitRegion.NONE); SplitRegion.NONE);
@@ -129,6 +137,37 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
middleAutoNearestSide, middleAutoNearestSide,
middleSide, middleSide,
showDotIfTruncated, 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,
null, null,
SplitRegion.NONE); SplitRegion.NONE);
@@ -201,6 +240,16 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
if (kind != LayoutItemKind.NOTIFICATIONS && kind != LayoutItemKind.STATUS) { if (kind != LayoutItemKind.NOTIFICATIONS && kind != LayoutItemKind.STATUS) {
return; 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); int targetVisible = Math.max(0, visibleIcons);
placements = iconSlice(rawPlacements, firstIconIndex, targetVisible); placements = iconSlice(rawPlacements, firstIconIndex, targetVisible);
if (dot) { if (dot) {
@@ -295,7 +344,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
withoutDot.add(placement); withoutDot.add(placement);
} }
} }
SnapshotPlacement dotPlacement = dotPlacement(width, height); SnapshotPlacement dotPlacement = dotPlacement(width, height, rowTop(withoutDot));
if (kind == LayoutItemKind.STATUS) { if (kind == LayoutItemKind.STATUS) {
withoutDot.add(0, dotPlacement); withoutDot.add(0, dotPlacement);
} else { } else {
@@ -417,6 +466,25 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
return boxes; 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 @Override
protected ArrayList<Bounds> placedCollisionBounds() { protected ArrayList<Bounds> placedCollisionBounds() {
ArrayList<Bounds> boxes = new ArrayList<>(); ArrayList<Bounds> boxes = new ArrayList<>();
@@ -454,8 +522,8 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
private int overflowDotVisualWidth(SnapshotPlacement placement) { private int overflowDotVisualWidth(SnapshotPlacement placement) {
Bounds bounds = placement.bounds; Bounds bounds = placement.bounds;
int base = Math.max(1, Math.min(bounds.width, bounds.height)); int base = Math.max(1, Math.min(bounds.width, bounds.height));
int radius = Math.max(1, Math.round(base * 0.16f)); int radius = Math.max(1, Math.round(base * 0.125f));
return Math.min(bounds.width, radius * 2 + 2); return Math.min(bounds.width, radius * 2);
} }
private Bounds collisionBounds(SnapshotPlacement placement) { private Bounds collisionBounds(SnapshotPlacement placement) {
@@ -487,7 +555,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
boolean hadDot = hasOverflowDot(icons); boolean hadDot = hasOverflowDot(icons);
icons.remove(removeIndex); icons.remove(removeIndex);
if (showDotIfTruncated && !hadDot) { if (showDotIfTruncated && !hadDot) {
SnapshotPlacement dot = dotPlacement(dotWidth(icons), dotHeight(icons)); SnapshotPlacement dot = dotPlacement(dotWidth(icons), dotHeight(icons), rowTop(icons));
if (fromStart) { if (fromStart) {
icons.add(0, dot); icons.add(0, dot);
} else { } else {
@@ -604,17 +672,61 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
return 0; 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( return new SnapshotPlacement(
null, 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), SnapshotKeys.OVERFLOW_DOT + ":" + kind.name().toLowerCase(java.util.Locale.ROOT),
false, false,
true, true,
dotColor(), dotColor(),
null, null,
new Bounds(0, 0, Math.max(1, width), Math.max(1, height)), new Bounds(0, 0, safeWidth, safeHeight),
0); 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() { private int dotColor() {
@@ -649,7 +761,7 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
int height = Math.max(1, dotHeight(activePlacements())); int height = Math.max(1, dotHeight(activePlacements()));
return new SnapshotPlacement( return new SnapshotPlacement(
null, 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), SnapshotKeys.EMPTY_ICON_CONTAINER + ":" + kind.name().toLowerCase(java.util.Locale.ROOT),
false, false,
false, false,
@@ -689,6 +801,14 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
collisionWidth, collisionWidth,
placement.contentBounds.height) placement.contentBounds.height)
: null; : 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( return new SnapshotPlacement(
placement.source, placement.source,
clippedBounds, clippedBounds,
@@ -698,7 +818,20 @@ final class UnlockedLayoutBand extends StatusBarLayoutSolver.LayoutBand {
placement.overflowDotColor, placement.overflowDotColor,
placement.heldSignal, placement.heldSignal,
clippedContent, 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) { 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.content.res.Resources;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
@@ -9,20 +10,36 @@ import android.widget.TextView;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.features.RuntimeContext;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds; 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.mode.SceneKey;
import se.ajpanton.statusbartweak.shell.settings.SbtSettings; import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
final class UnlockedStockSourceCollector { final class UnlockedStockSourceCollector {
private final WeakHashMap<View, RootAnchors> anchorsByRoot = new WeakHashMap<>(); private final WeakHashMap<View, RootAnchors> anchorsByRoot = new WeakHashMap<>();
UnlockedStockSourceCollector() {
this(null);
}
UnlockedStockSourceCollector(RuntimeContext runtimeContext) {
}
void clear() { void clear() {
anchorsByRoot.clear(); anchorsByRoot.clear();
} }
void clearRoot(View root) {
if (root != null) {
anchorsByRoot.remove(root);
}
}
RootAnchors anchorsFor(View root) { RootAnchors anchorsFor(View root) {
RootAnchors cached = anchorsByRoot.get(root); RootAnchors cached = anchorsByRoot.get(root);
if (cached != null && cached.isUsable()) { if (cached != null && cached.isUsable()) {
@@ -37,21 +54,40 @@ final class UnlockedStockSourceCollector {
return anchors; return anchors;
} }
CollectedIcons collect(View root, RootAnchors anchors) { CollectedIcons collect(View root, RootAnchors anchors, SceneKey scene) {
ArrayList<SnapshotSource> statusIcons = new ArrayList<>(); ArrayList<SnapshotSource> statusIcons = new ArrayList<>();
ArrayList<SnapshotSource> notificationIcons = new ArrayList<>(); ArrayList<SnapshotSource> notificationIcons = new ArrayList<>();
ArrayList<SnapshotSource> statusChips = new ArrayList<>(); ArrayList<SnapshotSource> statusChips = new ArrayList<>();
ArrayList<SnapshotSource> statusChipMetrics = new ArrayList<>(); ArrayList<SnapshotSource> statusChipMetrics = new ArrayList<>();
if (scene != null && scene.isAod()) {
collectAodStatusSnapshotViews(root, statusIcons, scene);
}
if (statusIcons.isEmpty()) {
if (anchors.statusRoot != null) { if (anchors.statusRoot != null) {
collectStatusSnapshotViews(anchors.statusRoot, statusIcons); 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)) { if (anchors.battery != null && !isDescendantOf(anchors.battery, anchors.statusRoot)) {
collectStatusSnapshotViews(anchors.battery, statusIcons); 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); collectNotificationSnapshotViews(anchors.notificationRoot, notificationIcons);
} }
if (scene == null || scene.isUnlocked()) {
collectStatusChipSnapshotViews(root, statusChips, statusChipMetrics); collectStatusChipSnapshotViews(root, statusChips, statusChipMetrics);
}
return new CollectedIcons( return new CollectedIcons(
anchors.clockView, anchors.clockView,
statusIcons, statusIcons,
@@ -61,6 +97,53 @@ final class UnlockedStockSourceCollector {
anchors.carrierView); 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( CollectedIcons collectChips(
View root, View root,
RootAnchors anchors, RootAnchors anchors,
@@ -89,9 +172,17 @@ final class UnlockedStockSourceCollector {
RootAnchors anchors, RootAnchors anchors,
CollectedIcons previousIcons CollectedIcons previousIcons
) { ) {
int chipSourceSignature = settings.layoutChipEnabledUnlocked int chipSourceSignature = scene != null
&& scene.isUnlocked()
&& settings.layoutChipEnabledUnlocked
? chipSourceSignature(root, previousIcons) ? chipSourceSignature(root, previousIcons)
: 0; : 0;
int statusSourceSignature = sourceLayoutListSignature(
previousIcons != null ? previousIcons.statusIcons : null);
if (scene != null && scene.isAod()) {
statusSourceSignature = 31 * statusSourceSignature
+ aodLayoutAnchorSignature(root, anchors, scene);
}
return new LayoutInputSignature( return new LayoutInputSignature(
scene != null ? scene.toString() : "", scene != null ? scene.toString() : "",
root.getWidth(), root.getWidth(),
@@ -102,7 +193,7 @@ final class UnlockedStockSourceCollector {
cutout != null ? cutout.top : Integer.MIN_VALUE, cutout != null ? cutout.top : Integer.MIN_VALUE,
cutout != null ? cutout.right : Integer.MIN_VALUE, cutout != null ? cutout.right : Integer.MIN_VALUE,
cutout != null ? cutout.bottom : Integer.MIN_VALUE, cutout != null ? cutout.bottom : Integer.MIN_VALUE,
layoutSettingsSignature(settings), loggingAwareSettingsSignature(root, settings),
clockTimeSignature(settings), clockTimeSignature(settings),
viewSignatureOrZero(anchors != null ? anchors.clockView : null), viewSignatureOrZero(anchors != null ? anchors.clockView : null),
shallowTreeSignature(anchors != null ? anchors.clockContainer : null), shallowTreeSignature(anchors != null ? anchors.clockContainer : null),
@@ -120,11 +211,36 @@ final class UnlockedStockSourceCollector {
anchors != null ? anchors.clockContainer : null), anchors != null ? anchors.clockContainer : null),
notificationRootSignature(anchors != null ? anchors.notificationRoot : null), notificationRootSignature(anchors != null ? anchors.notificationRoot : null),
viewSignatureOrZero(anchors != null ? anchors.carrierView : null), viewSignatureOrZero(anchors != null ? anchors.carrierView : null),
sourceLayoutListSignature(previousIcons != null ? previousIcons.statusIcons : null), statusSourceSignature,
sourceLayoutListSignature(previousIcons != null ? previousIcons.notificationIcons : null), sourceLayoutListSignature(previousIcons != null ? previousIcons.notificationIcons : null),
chipSourceSignature); 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( private void collectKnownStatusChipSnapshotViews(
View root, View root,
CollectedIcons previousIcons, CollectedIcons previousIcons,
@@ -136,8 +252,7 @@ final class UnlockedStockSourceCollector {
} }
if (previousIcons.statusChips != null) { if (previousIcons.statusChips != null) {
for (SnapshotSource source : previousIcons.statusChips) { for (SnapshotSource source : previousIcons.statusChips) {
View view = source.view(); if (isKnownStatusChipSnapshotSource(root, source)) {
if (isStatusChipSnapshotCandidate(root, view)) {
visibleOut.add(source); visibleOut.add(source);
metricOut.add(source); metricOut.add(source);
return; return;
@@ -146,7 +261,13 @@ final class UnlockedStockSourceCollector {
} }
if (previousIcons.statusChipMetrics != null) { if (previousIcons.statusChipMetrics != null) {
for (SnapshotSource source : previousIcons.statusChipMetrics) { 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); metricOut.add(source);
return; return;
} }
@@ -191,7 +312,7 @@ final class UnlockedStockSourceCollector {
if (battery == null && idName.equals("battery")) { if (battery == null && idName.equals("battery")) {
battery = view; battery = view;
} }
if (notificationRoot == null && idName.equals("notificationIcons")) { if (notificationRoot == null && isNotificationRootCandidate(view, idName)) {
notificationRoot = view; notificationRoot = view;
} }
if (carrierView == null && view instanceof TextView textView && isCarrierTextView(textView)) { if (carrierView == null && view instanceof TextView textView && isCarrierTextView(textView)) {
@@ -252,8 +373,35 @@ final class UnlockedStockSourceCollector {
return Integer.MAX_VALUE; 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) { 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); boolean candidate = isStatusSnapshotCandidate(source);
if (includeHidden) {
candidate = isStatusSnapshotCandidate(source, true);
}
if (isModernSignalContainer(source)) { if (isModernSignalContainer(source)) {
if (candidate) { if (candidate) {
out.add(new SnapshotSource(source, SnapshotMode.VIEW)); out.add(new SnapshotSource(source, SnapshotMode.VIEW));
@@ -268,11 +416,269 @@ final class UnlockedStockSourceCollector {
} }
if (source instanceof ViewGroup group) { if (source instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) { 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) { private boolean isModernSignalContainer(View view) {
String className = view.getClass().getName(); String className = view.getClass().getName();
return className.contains("ModernStatusBarWifiView") return className.contains("ModernStatusBarWifiView")
@@ -304,12 +710,13 @@ final class UnlockedStockSourceCollector {
while (!queue.isEmpty()) { while (!queue.isEmpty()) {
View view = queue.removeFirst(); View view = queue.removeFirst();
if (isStatusChipSnapshotCandidate(root, view)) { if (isStatusChipSnapshotCandidate(root, view)) {
visibleOut.add(new SnapshotSource(view, SnapshotMode.VIEW)); SnapshotSource source = statusChipSource(root, view);
metricOut.add(new SnapshotSource(view, SnapshotMode.VIEW)); visibleOut.add(source);
metricOut.add(source);
return; return;
} }
if (metricOut.isEmpty() && isStatusChipMetricCandidate(root, view)) { if (metricOut.isEmpty() && isStatusChipMetricCandidate(root, view)) {
metricOut.add(new SnapshotSource(view, SnapshotMode.VIEW)); metricOut.add(statusChipSource(root, view));
} }
if (view instanceof ViewGroup group) { if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) { for (int i = 0; i < group.getChildCount(); i++) {
@@ -320,9 +727,58 @@ final class UnlockedStockSourceCollector {
} }
private boolean isStatusChipSnapshotCandidate(View root, View view) { 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) { if (view == null || view == root || view.getVisibility() != View.VISIBLE) {
return false; return false;
} }
if (!allowHidden && view.getAlpha() <= 0f && !hasVisibleStatusChipContent(view)) {
return false;
}
if (ReflectionSupport.getBooleanField(view, "sbtStatusChipForcedHidden", false)) { if (ReflectionSupport.getBooleanField(view, "sbtStatusChipForcedHidden", false)) {
return false; return false;
} }
@@ -348,10 +804,46 @@ final class UnlockedStockSourceCollector {
return true; 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) { 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) { if (view == null || view == root || root == null) {
return false; return false;
} }
if (!allowHidden && view.getAlpha() <= 0f && !hasVisibleStatusChipContent(view)) {
return false;
}
int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth(); int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth();
int height = view.getHeight() > 0 ? view.getHeight() : view.getMeasuredHeight(); int height = view.getHeight() > 0 ? view.getHeight() : view.getMeasuredHeight();
if (width <= 0 || height <= 0 || width >= root.getWidth() / 2) { if (width <= 0 || height <= 0 || width >= root.getWidth() / 2) {
@@ -386,7 +878,14 @@ final class UnlockedStockSourceCollector {
} }
private boolean isStatusSnapshotCandidate(View view) { 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; return false;
} }
int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth(); int width = view.getWidth() > 0 ? view.getWidth() : view.getMeasuredWidth();
@@ -424,6 +923,9 @@ final class UnlockedStockSourceCollector {
} }
private boolean isNotificationSnapshotCandidate(View view) { private boolean isNotificationSnapshotCandidate(View view) {
if (isOwnedRuntimeView(view)) {
return false;
}
if (view instanceof TextView) { if (view instanceof TextView) {
return false; return false;
} }
@@ -466,6 +968,19 @@ final class UnlockedStockSourceCollector {
return false; 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) { private static int viewSignatureOrZero(View view) {
return view != null ? viewSignature(view) : 0; return view != null ? viewSignature(view) : 0;
} }
@@ -571,9 +1086,6 @@ final class UnlockedStockSourceCollector {
hasKnownChipSource = true; hasKnownChipSource = true;
result = 31 * result + chipSourceListSignature(previousIcons.statusChipMetrics); result = 31 * result + chipSourceListSignature(previousIcons.statusChipMetrics);
} }
if (!hasKnownChipSource) {
return 0;
}
} }
if (!hasKnownChipSource) { if (!hasKnownChipSource) {
result = 31 * result + statusChipCandidateSignature(root); result = 31 * result + statusChipCandidateSignature(root);
@@ -590,7 +1102,7 @@ final class UnlockedStockSourceCollector {
queue.add(root); queue.add(root);
while (!queue.isEmpty()) { while (!queue.isEmpty()) {
View view = queue.removeFirst(); View view = queue.removeFirst();
if (view != root && isStatusChipCandidate(view)) { if (view != root && isStatusChipCandidate(view) && hasVisibleStatusChipContent(view)) {
result = 31 * result + chipViewLayoutSignature(view); result = 31 * result + chipViewLayoutSignature(view);
} }
if (view instanceof ViewGroup group) { if (view instanceof ViewGroup group) {
@@ -602,7 +1114,7 @@ final class UnlockedStockSourceCollector {
return result; return result;
} }
private static int chipSourceListSignature(ArrayList<SnapshotSource> sources) { private int chipSourceListSignature(ArrayList<SnapshotSource> sources) {
if (sources == null || sources.isEmpty()) { if (sources == null || sources.isEmpty()) {
return 0; return 0;
} }
@@ -610,6 +1122,7 @@ final class UnlockedStockSourceCollector {
result = 31 * result + sources.size(); result = 31 * result + sources.size();
for (SnapshotSource source : sources) { for (SnapshotSource source : sources) {
result = 31 * result + source.mode().ordinal(); result = 31 * result + source.mode().ordinal();
result = 31 * result + (hasVisibleStatusChipContent(source.view()) ? 1 : 0);
result = 31 * result + chipViewLayoutSignature(source.view()); result = 31 * result + chipViewLayoutSignature(source.view());
} }
return result; return result;
@@ -679,6 +1192,18 @@ final class UnlockedStockSourceCollector {
return result; return result;
} }
private static int boundsSignature(Bounds bounds) {
if (bounds == null) {
return 0;
}
int result = 17;
result = 31 * result + bounds.left;
result = 31 * result + bounds.top;
result = 31 * result + bounds.width;
result = 31 * result + bounds.height;
return result;
}
private static int drawableLayoutSignature(android.graphics.drawable.Drawable drawable) { private static int drawableLayoutSignature(android.graphics.drawable.Drawable drawable) {
if (drawable == null) { if (drawable == null) {
return 0; return 0;
@@ -761,6 +1286,7 @@ final class UnlockedStockSourceCollector {
settings.layoutChipMiddleSide, settings.layoutChipMiddleSide,
settings.layoutChipMiddleAutoNearestSide, settings.layoutChipMiddleAutoNearestSide,
settings.layoutChipVerticalOffsetPx, settings.layoutChipVerticalOffsetPx,
settings.layoutChipHeightSteps,
settings.layoutNotifPosition, settings.layoutNotifPosition,
settings.layoutNotifSortOrder, settings.layoutNotifSortOrder,
settings.layoutNotifEnabledUnlocked, settings.layoutNotifEnabledUnlocked,
@@ -775,6 +1301,12 @@ final class UnlockedStockSourceCollector {
java.util.Arrays.hashCode(settings.layoutNotifLockMiddleSides), java.util.Arrays.hashCode(settings.layoutNotifLockMiddleSides),
java.util.Arrays.hashCode(settings.layoutNotifLockMiddleAutoNearestSides), java.util.Arrays.hashCode(settings.layoutNotifLockMiddleAutoNearestSides),
java.util.Arrays.hashCode(settings.layoutNotifLockVerticalOffsetsPx), 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.layoutNotifMiddleSide,
settings.layoutNotifMiddleAutoNearestSide, settings.layoutNotifMiddleAutoNearestSide,
settings.layoutNotifVerticalOffsetPx, settings.layoutNotifVerticalOffsetPx,
@@ -791,6 +1323,12 @@ final class UnlockedStockSourceCollector {
java.util.Arrays.hashCode(settings.layoutStatusLockMiddleSides), java.util.Arrays.hashCode(settings.layoutStatusLockMiddleSides),
java.util.Arrays.hashCode(settings.layoutStatusLockMiddleAutoNearestSides), java.util.Arrays.hashCode(settings.layoutStatusLockMiddleAutoNearestSides),
java.util.Arrays.hashCode(settings.layoutStatusLockVerticalOffsetsPx), 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.layoutStatusMiddleSide,
settings.layoutStatusMiddleAutoNearestSide, settings.layoutStatusMiddleAutoNearestSide,
settings.layoutStatusVerticalOffsetPx, settings.layoutStatusVerticalOffsetPx,
@@ -802,4 +1340,12 @@ final class UnlockedStockSourceCollector {
settings.statusChipsHideNavUnlocked, settings.statusChipsHideNavUnlocked,
settings.statusChipsHideCallUnlocked); 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 { final class UnlockedVerticalOffsetScaler {
private int baselineNotificationIconSizePx; private int baselineNotificationIconSizePx;
private int baselineStatusIconHeightPx; private int baselineStatusIconHeightPx;
private int baselineChipHeightPx;
private float baselineClockTextSizePx; private float baselineClockTextSizePx;
int notification(int offsetPx, int currentIconHeightPx) { int notification(int offsetPx, int currentIconHeightPx) {
@@ -22,11 +21,6 @@ final class UnlockedVerticalOffsetScaler {
return scaledOffset(offsetPx, currentIconHeightPx, baselineStatusIconHeightPx); 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) { int clock(int offsetPx, TextView source) {
if (offsetPx == 0 || source == null || source.getTextSize() <= 0f) { if (offsetPx == 0 || source == null || source.getTextSize() <= 0f) {
return offsetPx; return offsetPx;
@@ -12,6 +12,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public final class AppIconRules { public final class AppIconRules {
@@ -24,6 +25,9 @@ public final class AppIconRules {
public static final String EXTRA_PACKAGE = "package"; public static final String EXTRA_PACKAGE = "package";
public static final String EXTRA_FIRST_SEEN_MS = "first_seen_ms"; 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_AOD = 1;
public static final int MODE_LOCK = 1 << 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(); 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) { if (prefs == null) {
return new HashMap<>(); return new HashMap<>();
} }
@@ -97,7 +101,7 @@ public final class AppIconRules {
prefs.edit().putStringSet(PREF_KEY_EXCEPTION_RULES, encoded).apply(); 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) { if (prefs == null) {
return; return;
} }
@@ -112,18 +116,37 @@ public final class AppIconRules {
prefs.edit().remove(PREF_KEY_SEEN_SESSION).apply(); 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)) { 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) { if (existing == null) {
seen.put(pkg, firstSeenMs); seen.put(pkg, new SeenApp(pkg, seenMs, seenMs, timezoneOffsetMinutes));
return; return true;
} }
if (firstSeenMs > 0 && firstSeenMs < existing) { long firstSeenMs = existing.firstSeenMs > 0L
seen.put(pkg, firstSeenMs); ? 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) { public static boolean isBlocked(Map<String, Integer> masks, String pkg, String mode) {
@@ -367,8 +390,8 @@ public final class AppIconRules {
return MODE_NAME_UNKNOWN; return MODE_NAME_UNKNOWN;
} }
public static Map<String, Long> parseSeenSession(Set<String> rawSet) { public static Map<String, SeenApp> parseSeenSession(Set<String> rawSet) {
Map<String, Long> out = new HashMap<>(); Map<String, SeenApp> out = new HashMap<>();
if (rawSet == null || rawSet.isEmpty()) { if (rawSet == null || rawSet.isEmpty()) {
return out; return out;
} }
@@ -377,7 +400,7 @@ public final class AppIconRules {
continue; continue;
} }
String[] parts = entry.split("\\|", -1); String[] parts = entry.split("\\|", -1);
if (parts.length != 2) { if (parts.length != 2 && parts.length != 4) {
continue; continue;
} }
String pkg = parts[0]; String pkg = parts[0];
@@ -385,38 +408,61 @@ public final class AppIconRules {
continue; continue;
} }
long firstSeen; long firstSeen;
long lastSeen;
int lastSeenOffsetMinutes;
try { try {
firstSeen = Long.parseLong(parts[1]); 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) { } catch (NumberFormatException ignored) {
continue; continue;
} }
if (firstSeen <= 0L) { if (firstSeen <= 0L || lastSeen <= 0L) {
continue; continue;
} }
Long existing = out.get(pkg); SeenApp existing = out.get(pkg);
if (existing == null || firstSeen < existing) { if (existing == null) {
out.put(pkg, firstSeen); 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; 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<>(); Set<String> out = new HashSet<>();
if (map == null || map.isEmpty()) { if (map == null || map.isEmpty()) {
return out; return out;
} }
for (Map.Entry<String, Long> e : map.entrySet()) { for (Map.Entry<String, SeenApp> e : map.entrySet()) {
String pkg = e.getKey(); String pkg = e.getKey();
Long firstSeenObj = e.getValue(); SeenApp app = e.getValue();
if (!isValidPackageName(pkg) || firstSeenObj == null) { if (!isValidPackageName(pkg) || app == null) {
continue; continue;
} }
long firstSeen = firstSeenObj; long firstSeen = app.firstSeenMs;
long lastSeen = app.lastSeenMs;
if (firstSeen <= 0L) { if (firstSeen <= 0L) {
continue; continue;
} }
out.add(pkg + "|" + firstSeen); if (lastSeen <= 0L) {
lastSeen = firstSeen;
}
out.add(pkg + "|" + firstSeen + "|" + lastSeen + "|"
+ app.lastSeenTimezoneOffsetMinutes);
} }
return out; return out;
} }
@@ -424,10 +470,17 @@ public final class AppIconRules {
public static final class SeenApp { public static final class SeenApp {
public final String packageName; public final String packageName;
public final long firstSeenMs; 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.packageName = packageName;
this.firstSeenMs = firstSeenMs; 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<>(); List<SeenApp> out = new ArrayList<>();
if (seen == null || seen.isEmpty()) { if (seen == null || seen.isEmpty()) {
return out; return out;
} }
for (Map.Entry<String, Long> e : seen.entrySet()) { for (Map.Entry<String, SeenApp> e : seen.entrySet()) {
String pkg = e.getKey(); String pkg = e.getKey();
Long firstSeen = e.getValue(); SeenApp app = e.getValue();
if (!isValidPackageName(pkg) || firstSeen == null || firstSeen <= 0L) { if (!isValidPackageName(pkg) || app == null || app.firstSeenMs <= 0L) {
continue; continue;
} }
out.add(new SeenApp(pkg, firstSeen)); out.add(app);
} }
out.sort((a, b) -> Long.compare(a.firstSeenMs, b.firstSeenMs)); out.sort((a, b) -> Long.compare(a.firstSeenMs, b.firstSeenMs));
return out; 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_CLOCK_CONTAINER_DEFAULT = false;
public static final boolean DEBUG_VISUALIZE_CHIP_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_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_CUTOUT_PX_DEFAULT = 0;
public static final int LAYOUT_PADDING_LEFT_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 String LAYOUT_CHIP_MIDDLE_SIDE_DEFAULT = "left";
public static final boolean LAYOUT_CHIP_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT = true; 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_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_POSITION_DEFAULT = "left";
public static final String LAYOUT_NOTIF_SORT_ORDER_DEFAULT = "automatic"; public static final String LAYOUT_NOTIF_SORT_ORDER_DEFAULT = "automatic";
public static final boolean LAYOUT_NOTIF_SHOW_DOT_IF_TRUNCATED_DEFAULT = true; 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 String LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT = "left";
public static final boolean LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT = true; 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_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 String LAYOUT_STATUS_POSITION_DEFAULT = "right";
public static final boolean LAYOUT_STATUS_ENABLED_AOD_DEFAULT = true; public static final boolean LAYOUT_STATUS_ENABLED_AOD_DEFAULT = true;
public static final boolean LAYOUT_STATUS_ENABLED_LOCKSCREEN_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 String LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT = "left";
public static final boolean LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT = true; 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_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_MIN = 0;
public static final int LAYOUT_ICON_CONTAINER_COUNT_MAX = 3; public static final int LAYOUT_ICON_CONTAINER_COUNT_MAX = 3;
@@ -35,6 +35,8 @@ public final class SbtSettings {
"debug_visualize_chip_container"; "debug_visualize_chip_container";
public static final String KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT = public static final String KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT =
"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 = public static final String KEY_LOCKED_NOTIFICATION_MODE =
"locked_notification_mode"; "locked_notification_mode";
public static final String KEY_UNLOCKED_MAX_ICONS_PER_ROW = "unlocked_max_icons_per_row"; 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_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_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_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_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_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_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_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_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_MEDIA_UNLOCKED = "status_chips_hide_media_unlocked";
public static final String KEY_STATUS_CHIPS_HIDE_NAV_UNLOCKED = "status_chips_hide_nav_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_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_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_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_POSITION = "layout_notif_position";
public static final String KEY_LAYOUT_NOTIF_SORT_ORDER = "layout_notif_sort_order"; 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"; 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_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_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_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_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_AOD = "layout_status_enabled_aod";
public static final String KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN = "layout_status_enabled_lockscreen"; 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_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_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_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_BLOCKED_MODES = SystemIconRules.PREF_KEY_BLOCKED_MODES;
public static final String KEY_SYSTEM_ICON_HIDE_SLOTS = "system_icon_hide_slots"; public static final String KEY_SYSTEM_ICON_HIDE_SLOTS = "system_icon_hide_slots";
public static final String KEY_APP_ICON_BLOCKED_MODES = AppIconRules.PREF_KEY_BLOCKED_MODES; 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); 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) { public static String layoutLockStatusPositionKey(int index) {
return suffixedKey("layout_lock_status_position", 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); 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) { private static String suffixedKey(String base, int index) {
return index <= 0 ? base : base + "_" + (index + 1); return index <= 0 ? base : base + "_" + (index + 1);
} }
@@ -209,10 +264,12 @@ public final class SbtSettings {
public final int cardsAodMaxRows; public final int cardsAodMaxRows;
public final boolean cardsAodEvenDistribution; public final boolean cardsAodEvenDistribution;
public final boolean cardsAodLimitToWidth; public final boolean cardsAodLimitToWidth;
public final int cardsAodIconHeightSteps;
public final int cardsLockMaxIconsPerRow; public final int cardsLockMaxIconsPerRow;
public final int cardsLockMaxRows; public final int cardsLockMaxRows;
public final boolean cardsLockEvenDistribution; public final boolean cardsLockEvenDistribution;
public final boolean cardsLockLimitToWidth; public final boolean cardsLockLimitToWidth;
public final int cardsLockIconHeightSteps;
public final boolean keepAodVisibleDuringCall; public final boolean keepAodVisibleDuringCall;
public final boolean batteryLockscreenUnpluggedPercent; public final boolean batteryLockscreenUnpluggedPercent;
public final boolean batteryAodUnpluggedPercent; public final boolean batteryAodUnpluggedPercent;
@@ -259,6 +316,7 @@ public final class SbtSettings {
public final String layoutChipMiddleSide; public final String layoutChipMiddleSide;
public final boolean layoutChipMiddleAutoNearestSide; public final boolean layoutChipMiddleAutoNearestSide;
public final int layoutChipVerticalOffsetPx; public final int layoutChipVerticalOffsetPx;
public final int layoutChipHeightSteps;
public final String layoutNotifPosition; public final String layoutNotifPosition;
public final String layoutNotifSortOrder; public final String layoutNotifSortOrder;
public final boolean layoutNotifShowDotIfTruncated; public final boolean layoutNotifShowDotIfTruncated;
@@ -275,9 +333,17 @@ public final class SbtSettings {
public final String[] layoutNotifLockMiddleSides; public final String[] layoutNotifLockMiddleSides;
public final boolean[] layoutNotifLockMiddleAutoNearestSides; public final boolean[] layoutNotifLockMiddleAutoNearestSides;
public final int[] layoutNotifLockVerticalOffsetsPx; 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 String layoutNotifMiddleSide;
public final boolean layoutNotifMiddleAutoNearestSide; public final boolean layoutNotifMiddleAutoNearestSide;
public final int layoutNotifVerticalOffsetPx; public final int layoutNotifVerticalOffsetPx;
public final int layoutNotifIconHeightSteps;
public final int layoutNotifLockIconHeightSteps;
public final int layoutNotifAodIconHeightSteps;
public final String layoutStatusPosition; public final String layoutStatusPosition;
public final boolean layoutStatusEnabledAod; public final boolean layoutStatusEnabledAod;
public final boolean layoutStatusEnabledLockscreen; public final boolean layoutStatusEnabledLockscreen;
@@ -292,10 +358,18 @@ public final class SbtSettings {
public final String[] layoutStatusLockMiddleSides; public final String[] layoutStatusLockMiddleSides;
public final boolean[] layoutStatusLockMiddleAutoNearestSides; public final boolean[] layoutStatusLockMiddleAutoNearestSides;
public final int[] layoutStatusLockVerticalOffsetsPx; 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 boolean layoutStatusShowDotIfTruncated;
public final String layoutStatusMiddleSide; public final String layoutStatusMiddleSide;
public final boolean layoutStatusMiddleAutoNearestSide; public final boolean layoutStatusMiddleAutoNearestSide;
public final int layoutStatusVerticalOffsetPx; 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, String> clockCameraTypeOverrides;
public final Map<String, Integer> systemIconBlockedModes; public final Map<String, Integer> systemIconBlockedModes;
public final boolean statusChipsHideAll; public final boolean statusChipsHideAll;
@@ -310,6 +384,7 @@ public final class SbtSettings {
public final boolean debugVisualizeClockContainer; public final boolean debugVisualizeClockContainer;
public final boolean debugVisualizeChipContainer; public final boolean debugVisualizeChipContainer;
public final boolean debugVisualizeCameraCutout; public final boolean debugVisualizeCameraCutout;
public final boolean debugVisualizeAodStockContainers;
private SbtSettings(boolean enableAodCutoutLimit, private SbtSettings(boolean enableAodCutoutLimit,
boolean enableLockCutoutLimit, boolean enableLockCutoutLimit,
@@ -324,10 +399,12 @@ public final class SbtSettings {
int cardsAodMaxRows, int cardsAodMaxRows,
boolean cardsAodEvenDistribution, boolean cardsAodEvenDistribution,
boolean cardsAodLimitToWidth, boolean cardsAodLimitToWidth,
int cardsAodIconHeightSteps,
int cardsLockMaxIconsPerRow, int cardsLockMaxIconsPerRow,
int cardsLockMaxRows, int cardsLockMaxRows,
boolean cardsLockEvenDistribution, boolean cardsLockEvenDistribution,
boolean cardsLockLimitToWidth, boolean cardsLockLimitToWidth,
int cardsLockIconHeightSteps,
boolean keepAodVisibleDuringCall, boolean keepAodVisibleDuringCall,
boolean batteryLockscreenUnpluggedPercent, boolean batteryLockscreenUnpluggedPercent,
boolean batteryAodUnpluggedPercent, boolean batteryAodUnpluggedPercent,
@@ -374,6 +451,7 @@ public final class SbtSettings {
String layoutChipMiddleSide, String layoutChipMiddleSide,
boolean layoutChipMiddleAutoNearestSide, boolean layoutChipMiddleAutoNearestSide,
int layoutChipVerticalOffsetPx, int layoutChipVerticalOffsetPx,
int layoutChipHeightSteps,
String layoutNotifPosition, String layoutNotifPosition,
String layoutNotifSortOrder, String layoutNotifSortOrder,
boolean layoutNotifShowDotIfTruncated, boolean layoutNotifShowDotIfTruncated,
@@ -383,6 +461,9 @@ public final class SbtSettings {
String layoutNotifMiddleSide, String layoutNotifMiddleSide,
boolean layoutNotifMiddleAutoNearestSide, boolean layoutNotifMiddleAutoNearestSide,
int layoutNotifVerticalOffsetPx, int layoutNotifVerticalOffsetPx,
int layoutNotifIconHeightSteps,
int layoutNotifLockIconHeightSteps,
int layoutNotifAodIconHeightSteps,
int layoutNotifUnlockedCount, int layoutNotifUnlockedCount,
String[] layoutNotifPositions, String[] layoutNotifPositions,
String[] layoutNotifMiddleSides, String[] layoutNotifMiddleSides,
@@ -393,6 +474,11 @@ public final class SbtSettings {
String[] layoutNotifLockMiddleSides, String[] layoutNotifLockMiddleSides,
boolean[] layoutNotifLockMiddleAutoNearestSides, boolean[] layoutNotifLockMiddleAutoNearestSides,
int[] layoutNotifLockVerticalOffsetsPx, int[] layoutNotifLockVerticalOffsetsPx,
int layoutNotifAodCount,
String[] layoutNotifAodPositions,
String[] layoutNotifAodMiddleSides,
boolean[] layoutNotifAodMiddleAutoNearestSides,
int[] layoutNotifAodVerticalOffsetsPx,
String layoutStatusPosition, String layoutStatusPosition,
boolean layoutStatusEnabledAod, boolean layoutStatusEnabledAod,
boolean layoutStatusEnabledLockscreen, boolean layoutStatusEnabledLockscreen,
@@ -401,6 +487,9 @@ public final class SbtSettings {
String layoutStatusMiddleSide, String layoutStatusMiddleSide,
boolean layoutStatusMiddleAutoNearestSide, boolean layoutStatusMiddleAutoNearestSide,
int layoutStatusVerticalOffsetPx, int layoutStatusVerticalOffsetPx,
int layoutStatusIconHeightSteps,
int layoutStatusLockIconHeightSteps,
int layoutStatusAodIconHeightSteps,
int layoutStatusUnlockedCount, int layoutStatusUnlockedCount,
String[] layoutStatusPositions, String[] layoutStatusPositions,
String[] layoutStatusMiddleSides, String[] layoutStatusMiddleSides,
@@ -411,6 +500,11 @@ public final class SbtSettings {
String[] layoutStatusLockMiddleSides, String[] layoutStatusLockMiddleSides,
boolean[] layoutStatusLockMiddleAutoNearestSides, boolean[] layoutStatusLockMiddleAutoNearestSides,
int[] layoutStatusLockVerticalOffsetsPx, int[] layoutStatusLockVerticalOffsetsPx,
int layoutStatusAodCount,
String[] layoutStatusAodPositions,
String[] layoutStatusAodMiddleSides,
boolean[] layoutStatusAodMiddleAutoNearestSides,
int[] layoutStatusAodVerticalOffsetsPx,
Map<String, String> clockCameraTypeOverrides, Map<String, String> clockCameraTypeOverrides,
Map<String, Integer> systemIconBlockedModes, Map<String, Integer> systemIconBlockedModes,
boolean statusChipsHideAll, boolean statusChipsHideAll,
@@ -424,7 +518,8 @@ public final class SbtSettings {
boolean debugVisualizeStatusContainer, boolean debugVisualizeStatusContainer,
boolean debugVisualizeClockContainer, boolean debugVisualizeClockContainer,
boolean debugVisualizeChipContainer, boolean debugVisualizeChipContainer,
boolean debugVisualizeCameraCutout) { boolean debugVisualizeCameraCutout,
boolean debugVisualizeAodStockContainers) {
this.enableAodCutoutLimit = enableAodCutoutLimit; this.enableAodCutoutLimit = enableAodCutoutLimit;
this.enableLockCutoutLimit = enableLockCutoutLimit; this.enableLockCutoutLimit = enableLockCutoutLimit;
this.unlockedMaxIconsPerRow = unlockedMaxIconsPerRow; this.unlockedMaxIconsPerRow = unlockedMaxIconsPerRow;
@@ -438,10 +533,12 @@ public final class SbtSettings {
this.cardsAodMaxRows = cardsAodMaxRows; this.cardsAodMaxRows = cardsAodMaxRows;
this.cardsAodEvenDistribution = cardsAodEvenDistribution; this.cardsAodEvenDistribution = cardsAodEvenDistribution;
this.cardsAodLimitToWidth = cardsAodLimitToWidth; this.cardsAodLimitToWidth = cardsAodLimitToWidth;
this.cardsAodIconHeightSteps = clampIconHeightSteps(cardsAodIconHeightSteps);
this.cardsLockMaxIconsPerRow = cardsLockMaxIconsPerRow; this.cardsLockMaxIconsPerRow = cardsLockMaxIconsPerRow;
this.cardsLockMaxRows = cardsLockMaxRows; this.cardsLockMaxRows = cardsLockMaxRows;
this.cardsLockEvenDistribution = cardsLockEvenDistribution; this.cardsLockEvenDistribution = cardsLockEvenDistribution;
this.cardsLockLimitToWidth = cardsLockLimitToWidth; this.cardsLockLimitToWidth = cardsLockLimitToWidth;
this.cardsLockIconHeightSteps = clampIconHeightSteps(cardsLockIconHeightSteps);
this.keepAodVisibleDuringCall = keepAodVisibleDuringCall; this.keepAodVisibleDuringCall = keepAodVisibleDuringCall;
this.batteryLockscreenUnpluggedPercent = batteryLockscreenUnpluggedPercent; this.batteryLockscreenUnpluggedPercent = batteryLockscreenUnpluggedPercent;
this.batteryAodUnpluggedPercent = batteryAodUnpluggedPercent; this.batteryAodUnpluggedPercent = batteryAodUnpluggedPercent;
@@ -550,6 +647,7 @@ public final class SbtSettings {
layoutChipVerticalOffsetPx, layoutChipVerticalOffsetPx,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN, SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX); SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutChipHeightSteps = clampIconHeightSteps(layoutChipHeightSteps);
this.layoutNotifPosition = layoutNotifPosition != null this.layoutNotifPosition = layoutNotifPosition != null
? layoutNotifPosition ? layoutNotifPosition
: SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT; : SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT;
@@ -568,6 +666,9 @@ public final class SbtSettings {
layoutNotifVerticalOffsetPx, layoutNotifVerticalOffsetPx,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN, SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX); SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutNotifIconHeightSteps = clampIconHeightSteps(layoutNotifIconHeightSteps);
this.layoutNotifLockIconHeightSteps = clampIconHeightSteps(layoutNotifLockIconHeightSteps);
this.layoutNotifAodIconHeightSteps = clampIconHeightSteps(layoutNotifAodIconHeightSteps);
this.layoutNotifUnlockedCount = Math.max( this.layoutNotifUnlockedCount = Math.max(
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN, SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN,
Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, layoutNotifUnlockedCount)); Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, layoutNotifUnlockedCount));
@@ -608,6 +709,25 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN, SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX); 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 this.layoutStatusPosition = layoutStatusPosition != null
? layoutStatusPosition ? layoutStatusPosition
: SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT; : SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT;
@@ -623,6 +743,9 @@ public final class SbtSettings {
layoutStatusVerticalOffsetPx, layoutStatusVerticalOffsetPx,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN, SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX); SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX);
this.layoutStatusIconHeightSteps = clampIconHeightSteps(layoutStatusIconHeightSteps);
this.layoutStatusLockIconHeightSteps = clampIconHeightSteps(layoutStatusLockIconHeightSteps);
this.layoutStatusAodIconHeightSteps = clampIconHeightSteps(layoutStatusAodIconHeightSteps);
this.layoutStatusUnlockedCount = Math.max( this.layoutStatusUnlockedCount = Math.max(
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN, SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MIN,
Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, layoutStatusUnlockedCount)); Math.min(SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, layoutStatusUnlockedCount));
@@ -663,6 +786,25 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX, SbtDefaults.LAYOUT_ICON_CONTAINER_COUNT_MAX,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN, SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX); 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 this.clockCameraTypeOverrides = clockCameraTypeOverrides != null
? Collections.unmodifiableMap(new HashMap<>(clockCameraTypeOverrides)) ? Collections.unmodifiableMap(new HashMap<>(clockCameraTypeOverrides))
: Collections.emptyMap(); : Collections.emptyMap();
@@ -694,6 +836,7 @@ public final class SbtSettings {
this.debugVisualizeClockContainer = debugVisualizeClockContainer; this.debugVisualizeClockContainer = debugVisualizeClockContainer;
this.debugVisualizeChipContainer = debugVisualizeChipContainer; this.debugVisualizeChipContainer = debugVisualizeChipContainer;
this.debugVisualizeCameraCutout = debugVisualizeCameraCutout; this.debugVisualizeCameraCutout = debugVisualizeCameraCutout;
this.debugVisualizeAodStockContainers = debugVisualizeAodStockContainers;
} }
public static SbtSettings defaults() { public static SbtSettings defaults() {
@@ -711,10 +854,12 @@ public final class SbtSettings {
SbtDefaults.CARDS_AOD_MAX_ROWS_DEFAULT, SbtDefaults.CARDS_AOD_MAX_ROWS_DEFAULT,
SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_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,
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT, SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.CARDS_LOCK_MAX_ROWS_DEFAULT, SbtDefaults.CARDS_LOCK_MAX_ROWS_DEFAULT,
SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_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,
SbtDefaults.AOD_KEEP_VISIBLE_DURING_CALL_DEFAULT, SbtDefaults.AOD_KEEP_VISIBLE_DURING_CALL_DEFAULT,
SbtDefaults.BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT_DEFAULT, SbtDefaults.BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT_DEFAULT,
SbtDefaults.BATTERY_AOD_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_SIDE_DEFAULT,
SbtDefaults.LAYOUT_CHIP_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_CHIP_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT, SbtDefaults.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT,
SbtDefaults.LAYOUT_CHIP_HEIGHT_STEPS_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT, SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_SORT_ORDER_DEFAULT, SbtDefaults.LAYOUT_NOTIF_SORT_ORDER_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_SHOW_DOT_IF_TRUNCATED_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_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_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, SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
null, null,
null, null,
@@ -788,6 +942,14 @@ public final class SbtSettings {
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT, SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_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, SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
null, null,
null, null,
@@ -811,7 +973,8 @@ public final class SbtSettings {
SbtDefaults.DEBUG_VISUALIZE_STATUS_CONTAINER_DEFAULT, SbtDefaults.DEBUG_VISUALIZE_STATUS_CONTAINER_DEFAULT,
SbtDefaults.DEBUG_VISUALIZE_CLOCK_CONTAINER_DEFAULT, SbtDefaults.DEBUG_VISUALIZE_CLOCK_CONTAINER_DEFAULT,
SbtDefaults.DEBUG_VISUALIZE_CHIP_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)) { if (context == null || !AppIconRules.isValidPackageName(packageName)) {
return; return;
} }
long seenMs = firstSeenMs > 0L ? firstSeenMs : System.currentTimeMillis();
int offsetMinutes = AppIconRules.currentTimezoneOffsetMinutes();
try { try {
if (!MODULE_PACKAGE.equals(context.getPackageName()) && hasSettingsProvider(context)) { if (!MODULE_PACKAGE.equals(context.getPackageName()) && hasSettingsProvider(context)) {
Uri uri = Uri.parse("content://" + SbtSettingsProvider.AUTHORITY); Uri uri = Uri.parse("content://" + SbtSettingsProvider.AUTHORITY);
Bundle extras = new Bundle(); Bundle extras = new Bundle();
extras.putString(AppIconRules.EXTRA_PACKAGE, packageName); 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() context.getContentResolver()
.call(uri, SbtSettingsProvider.METHOD_NOTE_SEEN_ICON_APP, null, extras); .call(uri, SbtSettingsProvider.METHOD_NOTE_SEEN_ICON_APP, null, extras);
return; return;
} }
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
Map<String, Long> seen = AppIconRules.loadSeenSession(prefs); Map<String, AppIconRules.SeenApp> seen = AppIconRules.loadSeenSession(prefs);
AppIconRules.noteSeen(seen, packageName, firstSeenMs); if (AppIconRules.noteSeen(seen, packageName, seenMs, offsetMinutes)) {
AppIconRules.saveSeenSession(prefs, seen); AppIconRules.saveSeenSession(prefs, seen);
}
} catch (Throwable ignored) { } catch (Throwable ignored) {
// This list is advisory UI state; rendering should never fail because it cannot be updated. // 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), SbtDefaults.CARDS_AOD_MAX_ROWS_MAX),
prefs.getBoolean(KEY_CARDS_AOD_EVEN_DISTRIBUTION, SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT), 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), 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), 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_MIN,
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MAX), SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MAX),
@@ -940,6 +1112,10 @@ public final class SbtSettings {
SbtDefaults.CARDS_LOCK_MAX_ROWS_MAX), SbtDefaults.CARDS_LOCK_MAX_ROWS_MAX),
prefs.getBoolean(KEY_CARDS_LOCK_EVEN_DISTRIBUTION, SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT), 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), 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_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_LOCKSCREEN_UNPLUGGED_PERCENT, SbtDefaults.BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT_DEFAULT),
prefs.getBoolean(KEY_BATTERY_AOD_UNPLUGGED_PERCENT, SbtDefaults.BATTERY_AOD_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.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN, SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX), 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_POSITION, SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT),
prefs.getString(KEY_LAYOUT_NOTIF_SORT_ORDER, SbtDefaults.LAYOUT_NOTIF_SORT_ORDER_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), 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.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN, SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX), 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( readIconContainerCount(
prefs, prefs,
KEY_LAYOUT_NOTIF_UNLOCKED_COUNT, KEY_LAYOUT_NOTIF_UNLOCKED_COUNT,
@@ -1063,6 +1253,28 @@ public final class SbtSettings {
prefs, prefs,
SbtSettings::layoutLockNotifVerticalOffsetKey, SbtSettings::layoutLockNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT), 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.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_AOD, SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT),
prefs.getBoolean(KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_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.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN, SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX), 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( readIconContainerCount(
prefs, prefs,
KEY_LAYOUT_STATUS_UNLOCKED_COUNT, KEY_LAYOUT_STATUS_UNLOCKED_COUNT,
@@ -1118,6 +1342,28 @@ public final class SbtSettings {
prefs, prefs,
SbtSettings::layoutLockStatusVerticalOffsetKey, SbtSettings::layoutLockStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT), 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( ClockCameraTypeSupport.parseOverrides(copyStringSet(
prefs.getStringSet(KEY_CLOCK_CAMERA_TYPE_OVERRIDES, Collections.emptySet()))), prefs.getStringSet(KEY_CLOCK_CAMERA_TYPE_OVERRIDES, Collections.emptySet()))),
systemIconBlockedModes, systemIconBlockedModes,
@@ -1144,7 +1390,10 @@ public final class SbtSettings {
SbtDefaults.DEBUG_VISUALIZE_CHIP_CONTAINER_DEFAULT), SbtDefaults.DEBUG_VISUALIZE_CHIP_CONTAINER_DEFAULT),
prefs.getBoolean( prefs.getBoolean(
KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT, 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), SbtDefaults.CARDS_AOD_MAX_ROWS_MAX),
bundle.getBoolean(KEY_CARDS_AOD_EVEN_DISTRIBUTION, SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT), 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), 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), 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_MIN,
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MAX), SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_MAX),
@@ -1189,6 +1442,10 @@ public final class SbtSettings {
SbtDefaults.CARDS_LOCK_MAX_ROWS_MAX), SbtDefaults.CARDS_LOCK_MAX_ROWS_MAX),
bundle.getBoolean(KEY_CARDS_LOCK_EVEN_DISTRIBUTION, SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT), 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), 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_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_LOCKSCREEN_UNPLUGGED_PERCENT, SbtDefaults.BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT_DEFAULT),
bundle.getBoolean(KEY_BATTERY_AOD_UNPLUGGED_PERCENT, SbtDefaults.BATTERY_AOD_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.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN, SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX), 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_POSITION, SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT),
bundle.getString(KEY_LAYOUT_NOTIF_SORT_ORDER, SbtDefaults.LAYOUT_NOTIF_SORT_ORDER_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), 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.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN, SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX), 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( readIconContainerCount(
bundle, bundle,
KEY_LAYOUT_NOTIF_UNLOCKED_COUNT, KEY_LAYOUT_NOTIF_UNLOCKED_COUNT,
@@ -1310,6 +1581,28 @@ public final class SbtSettings {
bundle, bundle,
SbtSettings::layoutLockNotifVerticalOffsetKey, SbtSettings::layoutLockNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT), 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.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_AOD, SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT),
bundle.getBoolean(KEY_LAYOUT_STATUS_ENABLED_LOCKSCREEN, SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_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.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT),
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN, SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MIN,
SbtDefaults.CLOCK_VERTICAL_OFFSET_PX_MAX), 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( readIconContainerCount(
bundle, bundle,
KEY_LAYOUT_STATUS_UNLOCKED_COUNT, KEY_LAYOUT_STATUS_UNLOCKED_COUNT,
@@ -1365,6 +1670,28 @@ public final class SbtSettings {
bundle, bundle,
SbtSettings::layoutLockStatusVerticalOffsetKey, SbtSettings::layoutLockStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT), 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)), ClockCameraTypeSupport.parseOverrides(readStringSetFromBundle(bundle, KEY_CLOCK_CAMERA_TYPE_OVERRIDES)),
systemIconBlockedModes, systemIconBlockedModes,
bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_ALL, false), bundle.getBoolean(KEY_STATUS_CHIPS_HIDE_ALL, false),
@@ -1388,7 +1715,10 @@ public final class SbtSettings {
SbtDefaults.DEBUG_VISUALIZE_CHIP_CONTAINER_DEFAULT), SbtDefaults.DEBUG_VISUALIZE_CHIP_CONTAINER_DEFAULT),
bundle.getBoolean( bundle.getBoolean(
KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT, 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)); 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( private static int readIconContainerCount(
SharedPreferences prefs, SharedPreferences prefs,
String countKey, 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)); prefs.getBoolean(SbtSettings.KEY_CARDS_AOD_EVEN_DISTRIBUTION, SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_DEFAULT));
out.putBoolean(SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH, 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)); 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, 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)); 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, 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)); prefs.getBoolean(SbtSettings.KEY_CARDS_LOCK_EVEN_DISTRIBUTION, SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_DEFAULT));
out.putBoolean(SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH, 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)); 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, 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)); prefs.getBoolean(SbtSettings.KEY_AOD_KEEP_VISIBLE_DURING_CALL, SbtDefaults.AOD_KEEP_VISIBLE_DURING_CALL_DEFAULT));
out.putBoolean(SbtSettings.KEY_BATTERY_LOCKSCREEN_UNPLUGGED_PERCENT, 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, out.putBoolean(SbtSettings.KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT,
prefs.getBoolean(SbtSettings.KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT, prefs.getBoolean(SbtSettings.KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT,
SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT)); 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, out.putInt(SbtSettings.KEY_LAYOUT_PADDING_CUTOUT_PX,
prefs.getInt(SbtSettings.KEY_LAYOUT_PADDING_CUTOUT_PX, prefs.getInt(SbtSettings.KEY_LAYOUT_PADDING_CUTOUT_PX,
SbtDefaults.LAYOUT_PADDING_CUTOUT_PX_DEFAULT)); SbtDefaults.LAYOUT_PADDING_CUTOUT_PX_DEFAULT));
@@ -216,6 +225,9 @@ public class SbtSettingsProvider extends ContentProvider {
out.putInt(SbtSettings.KEY_LAYOUT_CHIP_VERTICAL_OFFSET_PX, out.putInt(SbtSettings.KEY_LAYOUT_CHIP_VERTICAL_OFFSET_PX,
prefs.getInt(SbtSettings.KEY_LAYOUT_CHIP_VERTICAL_OFFSET_PX, prefs.getInt(SbtSettings.KEY_LAYOUT_CHIP_VERTICAL_OFFSET_PX,
SbtDefaults.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT)); 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, out.putString(SbtSettings.KEY_LAYOUT_NOTIF_POSITION,
prefs.getString(SbtSettings.KEY_LAYOUT_NOTIF_POSITION, prefs.getString(SbtSettings.KEY_LAYOUT_NOTIF_POSITION,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT)); SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT));
@@ -250,6 +262,9 @@ public class SbtSettingsProvider extends ContentProvider {
out.putInt(SbtSettings.KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX, out.putInt(SbtSettings.KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX,
prefs.getInt(SbtSettings.KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX, prefs.getInt(SbtSettings.KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT)); 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( putIconCopySettings(
out, out,
prefs, prefs,
@@ -268,6 +283,9 @@ public class SbtSettingsProvider extends ContentProvider {
SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT) SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT)
? SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT ? SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT
: 0)); : 0));
out.putInt(SbtSettings.layoutLockNotifIconHeightKey(),
prefs.getInt(SbtSettings.layoutLockNotifIconHeightKey(),
SbtDefaults.LAYOUT_NOTIF_LOCK_ICON_HEIGHT_STEPS_DEFAULT));
putIconCopySettings( putIconCopySettings(
out, out,
prefs, prefs,
@@ -279,6 +297,27 @@ public class SbtSettingsProvider extends ContentProvider {
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtSettings::layoutLockNotifVerticalOffsetKey, SbtSettings::layoutLockNotifVerticalOffsetKey,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_DEFAULT); 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, out.putString(SbtSettings.KEY_LAYOUT_STATUS_POSITION,
prefs.getString(SbtSettings.KEY_LAYOUT_STATUS_POSITION, prefs.getString(SbtSettings.KEY_LAYOUT_STATUS_POSITION,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT)); SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT));
@@ -310,6 +349,9 @@ public class SbtSettingsProvider extends ContentProvider {
out.putInt(SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX, out.putInt(SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX,
prefs.getInt(SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX, prefs.getInt(SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT)); 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( putIconCopySettings(
out, out,
prefs, prefs,
@@ -328,6 +370,9 @@ public class SbtSettingsProvider extends ContentProvider {
SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT) SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT)
? SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT ? SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT
: 0)); : 0));
out.putInt(SbtSettings.layoutLockStatusIconHeightKey(),
prefs.getInt(SbtSettings.layoutLockStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT));
putIconCopySettings( putIconCopySettings(
out, out,
prefs, prefs,
@@ -339,6 +384,27 @@ public class SbtSettingsProvider extends ContentProvider {
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtSettings::layoutLockStatusVerticalOffsetKey, SbtSettings::layoutLockStatusVerticalOffsetKey,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT); 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( out.putStringArrayList(
SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES, SbtSettings.KEY_CLOCK_CAMERA_TYPE_OVERRIDES,
new ArrayList<>(prefs.getStringSet( new ArrayList<>(prefs.getStringSet(
@@ -372,17 +438,26 @@ public class SbtSettingsProvider extends ContentProvider {
} }
if (METHOD_NOTE_SEEN_ICON_APP.equals(method)) { if (METHOD_NOTE_SEEN_ICON_APP.equals(method)) {
String pkg = extras != null ? extras.getString(AppIconRules.EXTRA_PACKAGE) : null; 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()) ? extras.getLong(AppIconRules.EXTRA_FIRST_SEEN_MS, System.currentTimeMillis())
: 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)) { if (!AppIconRules.isValidPackageName(pkg)) {
return Bundle.EMPTY; return Bundle.EMPTY;
} }
synchronized (LOCK) { synchronized (LOCK) {
Map<String, Long> seen = AppIconRules.loadSeenSession(prefs); Map<String, AppIconRules.SeenApp> seen = AppIconRules.loadSeenSession(prefs);
AppIconRules.noteSeen(seen, pkg, firstSeenMs); if (AppIconRules.noteSeen(seen, pkg, seenMs, offsetMinutes)) {
AppIconRules.saveSeenSession(prefs, seen); AppIconRules.saveSeenSession(prefs, seen);
} }
}
return Bundle.EMPTY; return Bundle.EMPTY;
} }
if (METHOD_SET_CLOCK_CAMERA_DEBUG_STATE.equals(method)) { 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 com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TimeZone;
final class HiddenAppsUiSupport { final class HiddenAppsUiSupport {
interface RowChangedListener { interface RowChangedListener {
@@ -81,7 +84,7 @@ final class HiddenAppsUiSupport {
Map<String, Integer> blocked = AppIconRules.loadBlockedModes(prefs); Map<String, Integer> blocked = AppIconRules.loadBlockedModes(prefs);
Map<String, ArrayList<AppIconRules.ExceptionRule>> exceptionRules = Map<String, ArrayList<AppIconRules.ExceptionRule>> exceptionRules =
AppIconRules.loadExceptionRules(prefs); AppIconRules.loadExceptionRules(prefs);
Map<String, Long> seen = AppIconRules.loadSeenSession(prefs); Map<String, AppIconRules.SeenApp> seen = AppIconRules.loadSeenSession(prefs);
ArrayList<RowItem> blockedItems = new ArrayList<>(); ArrayList<RowItem> blockedItems = new ArrayList<>();
for (Map.Entry<String, Integer> entry : blocked.entrySet()) { for (Map.Entry<String, Integer> entry : blocked.entrySet()) {
@@ -96,6 +99,7 @@ final class HiddenAppsUiSupport {
item.blockLock = (mask & AppIconRules.MODE_LOCK) != 0; item.blockLock = (mask & AppIconRules.MODE_LOCK) != 0;
item.blockUnlock = (mask & AppIconRules.MODE_UNLOCK) != 0; item.blockUnlock = (mask & AppIconRules.MODE_UNLOCK) != 0;
item.exceptionRules = toEditableExceptionRules(exceptionRules.get(pkg)); item.exceptionRules = toEditableExceptionRules(exceptionRules.get(pkg));
applySeenInfo(item, seen.get(pkg));
blockedItems.add(item); blockedItems.add(item);
} }
blockedItems.sort((a, b) -> { blockedItems.sort((a, b) -> {
@@ -122,11 +126,20 @@ final class HiddenAppsUiSupport {
} }
RowItem item = buildRowItem(context, packageManager, fallbackIcon, seenApp.packageName); RowItem item = buildRowItem(context, packageManager, fallbackIcon, seenApp.packageName);
item.exceptionRules = toEditableExceptionRules(exceptionRules.get(seenApp.packageName)); item.exceptionRules = toEditableExceptionRules(exceptionRules.get(seenApp.packageName));
applySeenInfo(item, seenApp);
rows.add(item); rows.add(item);
alreadyAdded.add(seenApp.packageName); 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, private static RowItem buildRowItem(Context context,
PackageManager packageManager, PackageManager packageManager,
Drawable fallbackIcon, Drawable fallbackIcon,
@@ -466,6 +479,41 @@ final class HiddenAppsUiSupport {
return normalized.isEmpty() ? null : normalized; 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 { static final class RowItem {
final String packageName; final String packageName;
final String label; final String label;
@@ -473,6 +521,8 @@ final class HiddenAppsUiSupport {
boolean blockAod; boolean blockAod;
boolean blockLock; boolean blockLock;
boolean blockUnlock; boolean blockUnlock;
long lastSeenMs;
int lastSeenTimezoneOffsetMinutes;
ArrayList<EditableExceptionRule> exceptionRules = new ArrayList<>(); ArrayList<EditableExceptionRule> exceptionRules = new ArrayList<>();
RowItem(String packageName, String label, Drawable icon) { RowItem(String packageName, String label, Drawable icon) {
@@ -520,6 +570,7 @@ final class HiddenAppsUiSupport {
TextView crossUnlock; TextView crossUnlock;
TextView label; TextView label;
TextView pkg; TextView pkg;
TextView lastSeen;
} }
static final class HiddenAppsAdapter extends BaseAdapter { static final class HiddenAppsAdapter extends BaseAdapter {
@@ -571,6 +622,7 @@ final class HiddenAppsUiSupport {
holder.crossUnlock = convertView.findViewById(R.id.icon_mode_unlock_cross); holder.crossUnlock = convertView.findViewById(R.id.icon_mode_unlock_cross);
holder.label = convertView.findViewById(R.id.hidden_item_label); holder.label = convertView.findViewById(R.id.hidden_item_label);
holder.pkg = convertView.findViewById(R.id.hidden_item_package); holder.pkg = convertView.findViewById(R.id.hidden_item_package);
holder.lastSeen = convertView.findViewById(R.id.hidden_item_last_seen);
convertView.setTag(holder); convertView.setTag(holder);
} else { } else {
holder = (RowHolder) convertView.getTag(); holder = (RowHolder) convertView.getTag();
@@ -585,6 +637,14 @@ final class HiddenAppsUiSupport {
} else { } else {
holder.pkg.setText(row.packageName); 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( bindModeIconTriple(
holder.iconAod, holder.crossAod, holder.iconAod, holder.crossAod,
holder.iconLock, holder.crossLock, holder.iconLock, holder.crossLock,
@@ -59,6 +59,8 @@ public class IconsDebugFragment extends Fragment {
root.findViewById(R.id.debug_visualize_chip_container_switch); root.findViewById(R.id.debug_visualize_chip_container_switch);
SwitchMaterial visualizeCameraCutoutSwitch = SwitchMaterial visualizeCameraCutoutSwitch =
root.findViewById(R.id.debug_visualize_camera_cutout_switch); 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); MaterialButton restartSystemUiButton = root.findViewById(R.id.debug_restart_systemui_button);
SwitchMaterial cycleIconsSwitch = root.findViewById(R.id.debug_cycle_icons_switch); SwitchMaterial cycleIconsSwitch = root.findViewById(R.id.debug_cycle_icons_switch);
TextView currentCameraIdentified = root.findViewById(R.id.debug_camera_current_identified); TextView currentCameraIdentified = root.findViewById(R.id.debug_camera_current_identified);
@@ -106,13 +108,19 @@ public class IconsDebugFragment extends Fragment {
visualizeCameraCutoutSwitch, visualizeCameraCutoutSwitch,
SbtSettings.KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT, SbtSettings.KEY_DEBUG_VISUALIZE_CAMERA_CUTOUT,
SbtDefaults.DEBUG_VISUALIZE_CAMERA_CUTOUT_DEFAULT); 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); bindRestartSystemUiButton(restartSystemUiButton);
updateVisualizerSwitchAvailability( updateVisualizerSwitchAvailability(
prefs, prefs,
visualizeNotificationContainerSwitch, visualizeNotificationContainerSwitch,
visualizeStatusContainerSwitch, visualizeStatusContainerSwitch,
visualizeClockContainerSwitch, visualizeClockContainerSwitch,
visualizeChipContainerSwitch); visualizeChipContainerSwitch,
visualizeAodStockContainersSwitch);
Runnable refreshCameraUi = () -> updateCameraUi( Runnable refreshCameraUi = () -> updateCameraUi(
prefs, prefs,
currentCameraIdentified, currentCameraIdentified,
@@ -134,7 +142,8 @@ public class IconsDebugFragment extends Fragment {
visualizeNotificationContainerSwitch, visualizeNotificationContainerSwitch,
visualizeStatusContainerSwitch, visualizeStatusContainerSwitch,
visualizeClockContainerSwitch, visualizeClockContainerSwitch,
visualizeChipContainerSwitch); visualizeChipContainerSwitch,
visualizeAodStockContainersSwitch);
} }
}; };
prefs.registerOnSharedPreferenceChangeListener(listener); prefs.registerOnSharedPreferenceChangeListener(listener);
@@ -192,7 +201,8 @@ public class IconsDebugFragment extends Fragment {
SwitchMaterial notificationSwitch, SwitchMaterial notificationSwitch,
SwitchMaterial statusSwitch, SwitchMaterial statusSwitch,
SwitchMaterial clockSwitch, SwitchMaterial clockSwitch,
SwitchMaterial chipSwitch) { SwitchMaterial chipSwitch,
SwitchMaterial aodStockSwitch) {
boolean layoutEnabled = prefs != null && prefs.getBoolean( boolean layoutEnabled = prefs != null && prefs.getBoolean(
SbtSettings.KEY_CLOCK_ENABLED, SbtSettings.KEY_CLOCK_ENABLED,
SbtDefaults.CLOCK_ENABLED_DEFAULT); SbtDefaults.CLOCK_ENABLED_DEFAULT);
@@ -208,6 +218,9 @@ public class IconsDebugFragment extends Fragment {
if (chipSwitch != null) { if (chipSwitch != null) {
chipSwitch.setEnabled(layoutEnabled); chipSwitch.setEnabled(layoutEnabled);
} }
if (aodStockSwitch != null) {
aodStockSwitch.setEnabled(layoutEnabled);
}
} }
private void restartSystemUi() { 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_plus_fast),
root.findViewById(R.id.chip_vertical_offset_reset), root.findViewById(R.id.chip_vertical_offset_reset),
SbtDefaults.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT); SbtDefaults.LAYOUT_CHIP_VERTICAL_OFFSET_PX_DEFAULT);
addChipHeightControl(root, prefs);
bindStepper(prefs, bindStepper(prefs,
SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX, SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX,
root.findViewById(R.id.status_vertical_offset_input), root.findViewById(R.id.status_vertical_offset_input),
@@ -226,10 +227,12 @@ public final class LayoutFragment extends Fragment {
SbtSettings::layoutNotifMiddleAutoNearestSideKey, SbtSettings::layoutNotifMiddleAutoNearestSideKey,
SbtSettings::layoutNotifMiddleSideKey, SbtSettings::layoutNotifMiddleSideKey,
SbtSettings::layoutNotifVerticalOffsetKey, SbtSettings::layoutNotifVerticalOffsetKey,
SbtSettings.KEY_LAYOUT_NOTIF_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT, SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_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,
LayoutSectionCopySupport.Section.NOTIFICATIONS_ICONS); LayoutSectionCopySupport.Section.NOTIFICATIONS_ICONS);
setupUnlockedIconContainerCount( setupUnlockedIconContainerCount(
root, root,
@@ -244,10 +247,12 @@ public final class LayoutFragment extends Fragment {
SbtSettings::layoutStatusMiddleAutoNearestSideKey, SbtSettings::layoutStatusMiddleAutoNearestSideKey,
SbtSettings::layoutStatusMiddleSideKey, SbtSettings::layoutStatusMiddleSideKey,
SbtSettings::layoutStatusVerticalOffsetKey, SbtSettings::layoutStatusVerticalOffsetKey,
SbtSettings.KEY_LAYOUT_STATUS_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT, SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_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,
LayoutSectionCopySupport.Section.STATUS); LayoutSectionCopySupport.Section.STATUS);
return root; return root;
} }
@@ -269,10 +274,12 @@ public final class LayoutFragment extends Fragment {
IndexedKey autoNearestKey, IndexedKey autoNearestKey,
IndexedKey middleSideKey, IndexedKey middleSideKey,
IndexedKey verticalOffsetKey, IndexedKey verticalOffsetKey,
String iconHeightKey,
String positionDefault, String positionDefault,
boolean autoNearestDefault, boolean autoNearestDefault,
String middleSideDefault, String middleSideDefault,
int verticalOffsetDefault, int verticalOffsetDefault,
int iconHeightDefault,
LayoutSectionCopySupport.Section copySection LayoutSectionCopySupport.Section copySection
) { ) {
CheckBox checkbox = root.findViewById(checkboxId); CheckBox checkbox = root.findViewById(checkboxId);
@@ -319,6 +326,11 @@ public final class LayoutFragment extends Fragment {
rebuild[0].run(); rebuild[0].run();
} }
})); }));
countContainer.addView(iconHeightControl(
prefs,
iconHeightKey,
iconHeightDefault,
R.string.layout_icon_height_steps));
int sourceCardIndex = cardsParent != null ? cardsParent.indexOfChild(sourceCard) : -1; int sourceCardIndex = cardsParent != null ? cardsParent.indexOfChild(sourceCard) : -1;
if (cardsParent == null || sourceCardIndex < 0) { if (cardsParent == null || sourceCardIndex < 0) {
return; return;
@@ -407,6 +419,101 @@ public final class LayoutFragment extends Fragment {
return outer; 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( private View iconCopyCardFromLayout(
int sourceCheckboxId, int sourceCheckboxId,
int titleId, int titleId,
@@ -187,12 +187,14 @@ final class LayoutSectionCopySupport {
copyInt(editor, prefs, src.maxIcons, dst.maxIcons, src.maxIconsDefault); copyInt(editor, prefs, src.maxIcons, dst.maxIcons, src.maxIconsDefault);
copyBoolean(editor, prefs, src.evenDistribution, dst.evenDistribution, src.evenDistributionDefault); copyBoolean(editor, prefs, src.evenDistribution, dst.evenDistribution, src.evenDistributionDefault);
copyBoolean(editor, prefs, src.limitWidth, dst.limitWidth, src.limitWidthDefault); 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) { private static void copyIcons(SharedPreferences.Editor editor, SharedPreferences prefs, KeySet src, KeySet dst) {
copyBoolean(editor, prefs, src.enabled, dst.enabled, src.enabledDefault); copyBoolean(editor, prefs, src.enabled, dst.enabled, src.enabledDefault);
int count = prefs.getInt(src.count, src.countDefault); int count = prefs.getInt(src.count, src.countDefault);
editor.putInt(dst.count, count); editor.putInt(dst.count, count);
copyInt(editor, prefs, src.iconHeight, dst.iconHeight, src.iconHeightDefault);
for (int i = 0; i < MAX_COPIES; i++) { for (int i = 0; i < MAX_COPIES; i++) {
copyString(editor, prefs, src.positionAt(i), dst.positionAt(i), src.positionDefault); copyString(editor, prefs, src.positionAt(i), dst.positionAt(i), src.positionDefault);
copyBoolean(editor, prefs, src.autoNearestAt(i), dst.autoNearestAt(i), src.autoNearestDefault); 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_auto_nearest_side",
"layout_lock_notifications_middle_side", "layout_lock_notifications_middle_side",
"layout_lock_notifications_vertical_offset_px", "layout_lock_notifications_vertical_offset_px",
SbtSettings.layoutLockNotifIconHeightKey(),
SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT, SbtDefaults.LAYOUT_NOTIF_ENABLED_LOCKSCREEN_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT, SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_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) { if (mode == Mode.AOD) {
return iconKeys( return iconKeys(
@@ -292,12 +296,14 @@ final class LayoutSectionCopySupport {
"layout_aod_notifications_middle_auto_nearest_side", "layout_aod_notifications_middle_auto_nearest_side",
"layout_aod_notifications_middle_side", "layout_aod_notifications_middle_side",
"layout_aod_notifications_vertical_offset_px", "layout_aod_notifications_vertical_offset_px",
SbtSettings.layoutAodNotifIconHeightKey(),
SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT, SbtDefaults.LAYOUT_NOTIF_ENABLED_AOD_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT, SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_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( return iconKeys(
SbtSettings.KEY_LAYOUT_NOTIF_ENABLED_UNLOCKED, 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_AUTO_NEAREST_SIDE,
SbtSettings.KEY_LAYOUT_NOTIF_MIDDLE_SIDE, SbtSettings.KEY_LAYOUT_NOTIF_MIDDLE_SIDE,
SbtSettings.KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX, SbtSettings.KEY_LAYOUT_NOTIF_VERTICAL_OFFSET_PX,
SbtSettings.KEY_LAYOUT_NOTIF_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_NOTIF_ENABLED_UNLOCKED_DEFAULT, SbtDefaults.LAYOUT_NOTIF_ENABLED_UNLOCKED_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_NOTIF_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT, SbtDefaults.LAYOUT_NOTIF_POSITION_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_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) { private static KeySet statusKeys(Mode mode) {
@@ -323,12 +331,14 @@ final class LayoutSectionCopySupport {
"layout_lock_status_middle_auto_nearest_side", "layout_lock_status_middle_auto_nearest_side",
"layout_lock_status_middle_side", "layout_lock_status_middle_side",
"layout_lock_status_vertical_offset_px", "layout_lock_status_vertical_offset_px",
SbtSettings.layoutLockStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT, SbtDefaults.LAYOUT_STATUS_ENABLED_LOCKSCREEN_DEFAULT,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT, SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_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) { if (mode == Mode.AOD) {
return iconKeys( return iconKeys(
@@ -338,12 +348,14 @@ final class LayoutSectionCopySupport {
"layout_aod_status_middle_auto_nearest_side", "layout_aod_status_middle_auto_nearest_side",
"layout_aod_status_middle_side", "layout_aod_status_middle_side",
"layout_aod_status_vertical_offset_px", "layout_aod_status_vertical_offset_px",
SbtSettings.layoutAodStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT, SbtDefaults.LAYOUT_STATUS_ENABLED_AOD_DEFAULT,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT, SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_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( return iconKeys(
SbtSettings.KEY_LAYOUT_STATUS_ENABLED_UNLOCKED, 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_AUTO_NEAREST_SIDE,
SbtSettings.KEY_LAYOUT_STATUS_MIDDLE_SIDE, SbtSettings.KEY_LAYOUT_STATUS_MIDDLE_SIDE,
SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX, SbtSettings.KEY_LAYOUT_STATUS_VERTICAL_OFFSET_PX,
SbtSettings.KEY_LAYOUT_STATUS_ICON_HEIGHT_STEPS,
SbtDefaults.LAYOUT_STATUS_ENABLED_UNLOCKED_DEFAULT, SbtDefaults.LAYOUT_STATUS_ENABLED_UNLOCKED_DEFAULT,
SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT, SbtDefaults.LAYOUT_STATUS_UNLOCKED_COUNT_DEFAULT,
SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT, SbtDefaults.LAYOUT_STATUS_POSITION_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_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) { 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_MAX_ICONS_PER_ROW,
SbtSettings.KEY_CARDS_AOD_EVEN_DISTRIBUTION, SbtSettings.KEY_CARDS_AOD_EVEN_DISTRIBUTION,
SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH, SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH,
SbtSettings.KEY_CARDS_AOD_ICON_HEIGHT_STEPS,
SbtDefaults.CARDS_AOD_MAX_ROWS_DEFAULT, SbtDefaults.CARDS_AOD_MAX_ROWS_DEFAULT,
SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_DEFAULT, SbtDefaults.CARDS_AOD_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.CARDS_AOD_EVEN_DISTRIBUTION_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( return KeySet.cards(
SbtSettings.KEY_CARDS_LOCK_MAX_ROWS, SbtSettings.KEY_CARDS_LOCK_MAX_ROWS,
SbtSettings.KEY_CARDS_LOCK_MAX_ICONS_PER_ROW, SbtSettings.KEY_CARDS_LOCK_MAX_ICONS_PER_ROW,
SbtSettings.KEY_CARDS_LOCK_EVEN_DISTRIBUTION, SbtSettings.KEY_CARDS_LOCK_EVEN_DISTRIBUTION,
SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH, SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH,
SbtSettings.KEY_CARDS_LOCK_ICON_HEIGHT_STEPS,
SbtDefaults.CARDS_LOCK_MAX_ROWS_DEFAULT, SbtDefaults.CARDS_LOCK_MAX_ROWS_DEFAULT,
SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT, SbtDefaults.CARDS_LOCK_MAX_ICONS_PER_ROW_DEFAULT,
SbtDefaults.CARDS_LOCK_EVEN_DISTRIBUTION_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( private static KeySet iconKeys(
@@ -390,12 +408,14 @@ final class LayoutSectionCopySupport {
String autoNearest, String autoNearest,
String middleSide, String middleSide,
String verticalOffset, String verticalOffset,
String iconHeight,
boolean enabledDefault, boolean enabledDefault,
int countDefault, int countDefault,
String positionDefault, String positionDefault,
boolean autoNearestDefault, boolean autoNearestDefault,
String middleSideDefault, String middleSideDefault,
int verticalOffsetDefault int verticalOffsetDefault,
int iconHeightDefault
) { ) {
return new KeySet( return new KeySet(
enabled, enabled,
@@ -403,12 +423,14 @@ final class LayoutSectionCopySupport {
autoNearest, autoNearest,
middleSide, middleSide,
verticalOffset, verticalOffset,
iconHeight,
count, count,
enabledDefault, enabledDefault,
positionDefault, positionDefault,
autoNearestDefault, autoNearestDefault,
middleSideDefault, middleSideDefault,
verticalOffsetDefault, verticalOffsetDefault,
iconHeightDefault,
countDefault); countDefault);
} }
@@ -537,12 +559,14 @@ final class LayoutSectionCopySupport {
final String autoNearest; final String autoNearest;
final String middleSide; final String middleSide;
final String verticalOffset; final String verticalOffset;
final String iconHeight;
final String count; final String count;
final boolean enabledDefault; final boolean enabledDefault;
final String positionDefault; final String positionDefault;
final boolean autoNearestDefault; final boolean autoNearestDefault;
final String middleSideDefault; final String middleSideDefault;
final int verticalOffsetDefault; final int verticalOffsetDefault;
final int iconHeightDefault;
final int countDefault; final int countDefault;
final String maxRows; final String maxRows;
final String maxIcons; final String maxIcons;
@@ -566,18 +590,53 @@ final class LayoutSectionCopySupport {
String middleSideDefault, String middleSideDefault,
int verticalOffsetDefault, int verticalOffsetDefault,
int countDefault 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.enabled = enabled;
this.position = position; this.position = position;
this.autoNearest = autoNearest; this.autoNearest = autoNearest;
this.middleSide = middleSide; this.middleSide = middleSide;
this.verticalOffset = verticalOffset; this.verticalOffset = verticalOffset;
this.iconHeight = iconHeight;
this.count = count; this.count = count;
this.enabledDefault = enabledDefault; this.enabledDefault = enabledDefault;
this.positionDefault = positionDefault; this.positionDefault = positionDefault;
this.autoNearestDefault = autoNearestDefault; this.autoNearestDefault = autoNearestDefault;
this.middleSideDefault = middleSideDefault; this.middleSideDefault = middleSideDefault;
this.verticalOffsetDefault = verticalOffsetDefault; this.verticalOffsetDefault = verticalOffsetDefault;
this.iconHeightDefault = iconHeightDefault;
this.countDefault = countDefault; this.countDefault = countDefault;
this.maxRows = null; this.maxRows = null;
this.maxIcons = null; this.maxIcons = null;
@@ -594,13 +653,16 @@ final class LayoutSectionCopySupport {
String maxIcons, String maxIcons,
String evenDistribution, String evenDistribution,
String limitWidth, String limitWidth,
String iconHeight,
int maxRowsDefault, int maxRowsDefault,
int maxIconsDefault, int maxIconsDefault,
boolean evenDistributionDefault, boolean evenDistributionDefault,
boolean limitWidthDefault boolean limitWidthDefault,
int iconHeightDefault
) { ) {
return new KeySet(maxRows, maxIcons, evenDistribution, limitWidth, return new KeySet(maxRows, maxIcons, evenDistribution, limitWidth,
maxRowsDefault, maxIconsDefault, evenDistributionDefault, limitWidthDefault); iconHeight, maxRowsDefault, maxIconsDefault,
evenDistributionDefault, limitWidthDefault, iconHeightDefault);
} }
private KeySet( private KeySet(
@@ -608,22 +670,26 @@ final class LayoutSectionCopySupport {
String maxIcons, String maxIcons,
String evenDistribution, String evenDistribution,
String limitWidth, String limitWidth,
String iconHeight,
int maxRowsDefault, int maxRowsDefault,
int maxIconsDefault, int maxIconsDefault,
boolean evenDistributionDefault, boolean evenDistributionDefault,
boolean limitWidthDefault boolean limitWidthDefault,
int iconHeightDefault
) { ) {
this.enabled = null; this.enabled = null;
this.position = null; this.position = null;
this.autoNearest = null; this.autoNearest = null;
this.middleSide = null; this.middleSide = null;
this.verticalOffset = null; this.verticalOffset = null;
this.iconHeight = iconHeight;
this.count = null; this.count = null;
this.enabledDefault = false; this.enabledDefault = false;
this.positionDefault = null; this.positionDefault = null;
this.autoNearestDefault = false; this.autoNearestDefault = false;
this.middleSideDefault = null; this.middleSideDefault = null;
this.verticalOffsetDefault = 0; this.verticalOffsetDefault = 0;
this.iconHeightDefault = iconHeightDefault;
this.countDefault = 0; this.countDefault = 0;
this.maxRows = maxRows; this.maxRows = maxRows;
this.maxIcons = maxIcons; this.maxIcons = maxIcons;
@@ -174,6 +174,8 @@ abstract class LockedSceneLayoutFragment extends Fragment {
SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_STATUS_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT, SbtDefaults.LAYOUT_STATUS_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT, SbtDefaults.LAYOUT_STATUS_VERTICAL_OFFSET_PX_DEFAULT,
aod ? SbtSettings.layoutAodStatusIconHeightKey() : SbtSettings.layoutLockStatusIconHeightKey(),
SbtDefaults.LAYOUT_STATUS_ICON_HEIGHT_STEPS_DEFAULT,
LayoutSectionCopySupport.Section.STATUS, LayoutSectionCopySupport.Section.STATUS,
false, false,
refresh)); refresh));
@@ -205,6 +207,10 @@ abstract class LockedSceneLayoutFragment extends Fragment {
SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT, SbtDefaults.LAYOUT_NOTIF_MIDDLE_AUTO_NEAREST_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT, SbtDefaults.LAYOUT_NOTIF_MIDDLE_SIDE_DEFAULT,
SbtDefaults.LAYOUT_NOTIF_VERTICAL_OFFSET_PX_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, LayoutSectionCopySupport.Section.NOTIFICATIONS_ICONS,
false, false,
refresh)); refresh));
@@ -309,6 +315,8 @@ abstract class LockedSceneLayoutFragment extends Fragment {
boolean autoNearestDefault, boolean autoNearestDefault,
String middleSideDefault, String middleSideDefault,
int verticalOffsetDefault, int verticalOffsetDefault,
String iconHeightKey,
int iconHeightDefault,
LayoutSectionCopySupport.Section copySection, LayoutSectionCopySupport.Section copySection,
boolean cardsMode, boolean cardsMode,
@Nullable Runnable refresh @Nullable Runnable refresh
@@ -346,6 +354,8 @@ abstract class LockedSceneLayoutFragment extends Fragment {
middleSideDefault, middleSideDefault,
indexedSceneKey(base + "_vertical_offset_px", 0), indexedSceneKey(base + "_vertical_offset_px", 0),
verticalOffsetDefault, verticalOffsetDefault,
iconHeightKey,
iconHeightDefault,
legacyEnabledKey, legacyEnabledKey,
countKey, countKey,
count, count,
@@ -381,6 +391,8 @@ abstract class LockedSceneLayoutFragment extends Fragment {
indexedSceneKey(base + "_vertical_offset_px", i), indexedSceneKey(base + "_vertical_offset_px", i),
verticalOffsetDefault, verticalOffsetDefault,
null, null,
0,
null,
null, null,
count, count,
null)); null));
@@ -440,6 +452,8 @@ abstract class LockedSceneLayoutFragment extends Fragment {
String middleSideDefault, String middleSideDefault,
String verticalOffsetKey, String verticalOffsetKey,
int verticalOffsetDefault, int verticalOffsetDefault,
@Nullable String iconHeightKey,
int iconHeightDefault,
@Nullable String legacyEnabledKey, @Nullable String legacyEnabledKey,
@Nullable String countKey, @Nullable String countKey,
int count, int count,
@@ -453,6 +467,18 @@ abstract class LockedSceneLayoutFragment extends Fragment {
sectionContent.addView( sectionContent.addView(
iconContainerCountControl(context, prefs, countKey, legacyEnabledKey, count, onCountChanged), iconContainerCountControl(context, prefs, countKey, legacyEnabledKey, count, onCountChanged),
insertIndex); 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); updateIconCardTitle(card, titleId, index, count);
bindXmlPositionSection( bindXmlPositionSection(
@@ -623,6 +649,16 @@ abstract class LockedSceneLayoutFragment extends Fragment {
R.string.label_limit_screen_width, R.string.label_limit_screen_width,
aod ? SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH : SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH, aod ? SbtSettings.KEY_CARDS_AOD_LIMIT_TO_WIDTH : SbtSettings.KEY_CARDS_LOCK_LIMIT_TO_WIDTH,
aod ? SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT : SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT)); aod ? SbtDefaults.CARDS_AOD_LIMIT_TO_WIDTH_DEFAULT : SbtDefaults.CARDS_LOCK_LIMIT_TO_WIDTH_DEFAULT));
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); 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) { 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); LinearLayout outer = vertical(context);
outer.addView(body(context, label)); outer.addView(body(context, label));
LinearLayout row = new LinearLayout(context); LinearLayout row = new LinearLayout(context);
@@ -862,11 +915,17 @@ abstract class LockedSceneLayoutFragment extends Fragment {
row.setGravity(Gravity.CENTER_VERTICAL); row.setGravity(Gravity.CENTER_VERTICAL);
MaterialButton minus = button(context, "-"); MaterialButton minus = button(context, "-");
MaterialButton plus = button(context, "+"); MaterialButton plus = button(context, "+");
MaterialButton reset = showDefaultButton ? button(context, "D") : null;
EditText input = numericInput(context, true); EditText input = numericInput(context, true);
input.setText(String.valueOf(clamp(prefs.getInt(key, def), min, max))); input.setText(String.valueOf(clamp(prefs.getInt(key, def), min, max)));
row.addView(minus, buttonParams()); row.addView(minus, buttonParams());
row.addView(input, inputParams()); row.addView(input, inputParams());
row.addView(plus, buttonParams()); row.addView(plus, buttonParams());
if (reset != null) {
LinearLayout.LayoutParams resetParams = buttonParams();
resetParams.leftMargin = dp(8);
row.addView(reset, resetParams);
}
outer.addView(row); outer.addView(row);
View.OnClickListener listener = view -> { View.OnClickListener listener = view -> {
int delta = view == minus ? -1 : 1; int delta = view == minus ? -1 : 1;
@@ -876,6 +935,13 @@ abstract class LockedSceneLayoutFragment extends Fragment {
}; };
minus.setOnClickListener(listener); minus.setOnClickListener(listener);
plus.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) -> { input.setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) { if (!hasFocus) {
int next = clamp(parseInt(input, def), min, max); int next = clamp(parseInt(input, def), min, max);
@@ -64,7 +64,7 @@ public class SystemIconsFragment extends Fragment {
"hotspot"), "hotspot"),
new SystemIconEntry(R.string.system_icon_nfc, new SystemIconEntry(R.string.system_icon_nfc,
new String[]{"stat_sys_nfc"}, new String[]{"stat_sys_nfc"},
android.R.drawable.ic_menu_share, R.drawable.sbt_ic_nfc,
"nfc_on", "nfc"), "nfc_on", "nfc"),
new SystemIconEntry(R.string.system_icon_vpn, new SystemIconEntry(R.string.system_icon_vpn,
new String[]{"stat_sys_branded_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:layout_height="wrap_content"
android:text="@string/debug_visualize_camera_cutout" /> 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> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
@@ -109,6 +109,15 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textAppearance="?attr/textAppearanceCaption" /> 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>
</LinearLayout> </LinearLayout>
+4
View File
@@ -26,6 +26,7 @@
<string name="debug_visualize_clock_container">Clock container</string> <string name="debug_visualize_clock_container">Clock container</string>
<string name="debug_visualize_chip_container">Status chip 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_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_section_systemui">SystemUI</string>
<string name="debug_restart_systemui">Restart SystemUI</string> <string name="debug_restart_systemui">Restart SystemUI</string>
<string name="debug_restart_systemui_started">SystemUI restart requested.</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_close">Close</string>
<string name="hidden_apps_exception_regex_input_hint">Regex</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_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_default_modes_title">Default settings</string>
<string name="hidden_apps_add_exception">Add exception</string> <string name="hidden_apps_add_exception">Add exception</string>
<string name="hidden_apps_delete_exception">Delete</string> <string name="hidden_apps_delete_exception">Delete</string>
@@ -171,6 +173,8 @@
<string name="layout_item_enabled">Enabled</string> <string name="layout_item_enabled">Enabled</string>
<string name="layout_copy_from">Copy from</string> <string name="layout_copy_from">Copy from</string>
<string name="layout_icon_container_count">Number of containers</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_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_locked_notifications_hint">Mirrors the Samsung notification display style for AOD and lockscreen.</string>
<string name="layout_misc_title">Misc</string> <string name="layout_misc_title">Misc</string>
@@ -155,6 +155,89 @@ public final class PackedStatusBarLayoutSolverTest {
assertTrue(notifications.width() == 65.0); 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() { private static Spec cutoutSpec() {
return new Spec( return new Spec(
1200, 1200,
@@ -66,6 +66,42 @@ public final class UnlockedLayoutBandTest {
assertEquals("icon4", band.placements.get(2).key); 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) { private static ArrayList<SnapshotPlacement> placements(int count, int width, int height) {
ArrayList<SnapshotPlacement> result = new ArrayList<>(); ArrayList<SnapshotPlacement> result = new ArrayList<>();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {