Optimize statusbar rendering paths
This commit is contained in:
+320
-45
@@ -1,5 +1,6 @@
|
|||||||
package se.ajpanton.statusbartweak.runtime.layout;
|
package se.ajpanton.statusbartweak.runtime.layout;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
import android.app.KeyguardManager;
|
import android.app.KeyguardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@@ -22,6 +23,7 @@ import java.util.ArrayDeque;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -47,6 +49,7 @@ import se.ajpanton.statusbartweak.runtime.render.LockscreenCardsNotificationStri
|
|||||||
import se.ajpanton.statusbartweak.runtime.notifications.NotificationActiveKeyStore;
|
import se.ajpanton.statusbartweak.runtime.notifications.NotificationActiveKeyStore;
|
||||||
import se.ajpanton.statusbartweak.runtime.notifications.NotificationUnreadTracker;
|
import se.ajpanton.statusbartweak.runtime.notifications.NotificationUnreadTracker;
|
||||||
import se.ajpanton.statusbartweak.runtime.render.OwnedIconHostManager;
|
import se.ajpanton.statusbartweak.runtime.render.OwnedIconHostManager;
|
||||||
|
import se.ajpanton.statusbartweak.runtime.render.StatusBarTintTarget;
|
||||||
import se.ajpanton.statusbartweak.runtime.render.UnlockedIconSnapshotRenderController;
|
import se.ajpanton.statusbartweak.runtime.render.UnlockedIconSnapshotRenderController;
|
||||||
import se.ajpanton.statusbartweak.runtime.render.UnlockedIconSnapshotRenderController.RenderResult;
|
import se.ajpanton.statusbartweak.runtime.render.UnlockedIconSnapshotRenderController.RenderResult;
|
||||||
import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache;
|
import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache;
|
||||||
@@ -106,12 +109,19 @@ final class StockLayoutCanvasController {
|
|||||||
private final Map<View, Integer> debugOverlaySignatures = new WeakHashMap<>();
|
private final Map<View, Integer> debugOverlaySignatures = new WeakHashMap<>();
|
||||||
private final Map<View, Integer> settingsIdentityByRoot = new WeakHashMap<>();
|
private final Map<View, Integer> settingsIdentityByRoot = new WeakHashMap<>();
|
||||||
private final Map<View, Integer> lockscreenCardsRowFilterSignatures = new WeakHashMap<>();
|
private final Map<View, Integer> lockscreenCardsRowFilterSignatures = new WeakHashMap<>();
|
||||||
|
private final Map<View, Integer> lockscreenCardsNotificationRenderGenerationByRoot =
|
||||||
|
new WeakHashMap<>();
|
||||||
private final Map<View, Long> clockTimeBucketByRoot = new WeakHashMap<>();
|
private final Map<View, Long> clockTimeBucketByRoot = new WeakHashMap<>();
|
||||||
private final Map<View, Integer> aodCardsNotificationRenderGenerationByRoot = new WeakHashMap<>();
|
private final Map<View, Integer> aodCardsNotificationRenderGenerationByRoot = new WeakHashMap<>();
|
||||||
private final Map<View, AodSourceGeometry> aodSourceGeometryByView = new WeakHashMap<>();
|
private final Map<View, AodSourceGeometry> aodSourceGeometryByView = new WeakHashMap<>();
|
||||||
private final Map<View, Integer> aodNotificationWidgetSteadyWidths = new WeakHashMap<>();
|
private final Map<View, Integer> aodNotificationWidgetSteadyWidths = new WeakHashMap<>();
|
||||||
private final Map<View, Integer> aodNotificationIconOnlyAreaSteadyLefts = new WeakHashMap<>();
|
private final Map<View, Integer> aodNotificationIconOnlyAreaSteadyLefts = new WeakHashMap<>();
|
||||||
private final Map<View, Integer> originalAodNotificationWidgetLayoutWidths = new WeakHashMap<>();
|
private final Map<View, Integer> originalAodNotificationWidgetLayoutWidths = new WeakHashMap<>();
|
||||||
|
private final Map<Class<?>, ArrayList<NotifCollectionAccessor>> notifCollectionAccessorsByClass =
|
||||||
|
new WeakHashMap<>();
|
||||||
|
private final Map<String, IconFingerprint> notificationFingerprintByCacheKey = new HashMap<>();
|
||||||
|
private final Map<ImageView, DrawableFingerprintCache> viewFingerprintCacheByImageView =
|
||||||
|
new WeakHashMap<>();
|
||||||
private final Set<View> confirmedAodPluginViews =
|
private final Set<View> confirmedAodPluginViews =
|
||||||
Collections.newSetFromMap(new WeakHashMap<>());
|
Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
private final Map<View, Float> aodPluginVisualAlphaByView = new WeakHashMap<>();
|
private final Map<View, Float> aodPluginVisualAlphaByView = new WeakHashMap<>();
|
||||||
@@ -440,6 +450,12 @@ final class StockLayoutCanvasController {
|
|||||||
if (darkIconDispatcherClass != null) {
|
if (darkIconDispatcherClass != null) {
|
||||||
darkIconDispatcherGuard.install(darkIconDispatcherClass);
|
darkIconDispatcherGuard.install(darkIconDispatcherClass);
|
||||||
}
|
}
|
||||||
|
Class<?> lightBarTransitionsControllerClass = ReflectionSupport.findClassIfExists(
|
||||||
|
"com.android.systemui.statusbar.phone.LightBarTransitionsController",
|
||||||
|
classLoader);
|
||||||
|
if (lightBarTransitionsControllerClass != null) {
|
||||||
|
hookLightBarTintTarget(lightBarTransitionsControllerClass);
|
||||||
|
}
|
||||||
|
|
||||||
Class<?> statusBarIconViewClass = ReflectionSupport.findClassIfExists(
|
Class<?> statusBarIconViewClass = ReflectionSupport.findClassIfExists(
|
||||||
"com.android.systemui.statusbar.StatusBarIconView",
|
"com.android.systemui.statusbar.StatusBarIconView",
|
||||||
@@ -540,6 +556,32 @@ final class StockLayoutCanvasController {
|
|||||||
hookStatusChipContentInvalidation(ImageView.class, "setImageLevel");
|
hookStatusChipContentInvalidation(ImageView.class, "setImageLevel");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void hookLightBarTintTarget(Class<?> type) {
|
||||||
|
for (Method method : type.getDeclaredMethods()) {
|
||||||
|
if (!isSetIconsDarkMethod(method)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
method.setAccessible(true);
|
||||||
|
runtimeContext.getFramework().hook(method).intercept(
|
||||||
|
chain -> {
|
||||||
|
List<Object> args = chain.getArgs();
|
||||||
|
Object result = chain.proceed();
|
||||||
|
if (args.size() >= 1
|
||||||
|
&& args.get(0) instanceof Boolean value
|
||||||
|
&& StatusBarTintTarget.setDarkIcons(value)) {
|
||||||
|
scheduleUnlockedAppearanceRefreshForAllRoots(true);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSetIconsDarkMethod(Method method) {
|
||||||
|
return "setIconsDark".equals(method.getName())
|
||||||
|
&& method.getParameterCount() >= 1
|
||||||
|
&& method.getParameterTypes()[0] == boolean.class;
|
||||||
|
}
|
||||||
|
|
||||||
private void hookViewAppearanceInvalidation(Class<?> type, String methodName) {
|
private void hookViewAppearanceInvalidation(Class<?> type, String methodName) {
|
||||||
XposedHookSupport.hookAllMethodsIfExists(
|
XposedHookSupport.hookAllMethodsIfExists(
|
||||||
runtimeContext.getFramework(),
|
runtimeContext.getFramework(),
|
||||||
@@ -857,6 +899,8 @@ final class StockLayoutCanvasController {
|
|||||||
if (notifications.isEmpty()) {
|
if (notifications.isEmpty()) {
|
||||||
latestAodNotifications = new ArrayList<>();
|
latestAodNotifications = new ArrayList<>();
|
||||||
latestAodIconImageViews = new ArrayList<>();
|
latestAodIconImageViews = new ArrayList<>();
|
||||||
|
notificationFingerprintByCacheKey.clear();
|
||||||
|
viewFingerprintCacheByImageView.clear();
|
||||||
NotificationActiveKeyStore.replace(Collections.emptySet());
|
NotificationActiveKeyStore.replace(Collections.emptySet());
|
||||||
NotificationUnreadTracker.retainActiveKeys(Collections.emptySet());
|
NotificationUnreadTracker.retainActiveKeys(Collections.emptySet());
|
||||||
LockscreenCardsNotificationIconSourceStore.clearAodSources();
|
LockscreenCardsNotificationIconSourceStore.clearAodSources();
|
||||||
@@ -880,6 +924,7 @@ final class StockLayoutCanvasController {
|
|||||||
changed |= NotificationUnreadTracker.retainActiveKeys(activeKeys);
|
changed |= NotificationUnreadTracker.retainActiveKeys(activeKeys);
|
||||||
LockscreenCardsNotificationIconSourceStore.putAodNotificationPackages(packages);
|
LockscreenCardsNotificationIconSourceStore.putAodNotificationPackages(packages);
|
||||||
latestAodNotifications = notifications;
|
latestAodNotifications = notifications;
|
||||||
|
pruneNotificationFingerprintCache(notifications);
|
||||||
if (changed) {
|
if (changed) {
|
||||||
markKnownAodNotificationRootsDirty();
|
markKnownAodNotificationRootsDirty();
|
||||||
}
|
}
|
||||||
@@ -891,38 +936,95 @@ final class StockLayoutCanvasController {
|
|||||||
if (collection == null) {
|
if (collection == null) {
|
||||||
return notifications;
|
return notifications;
|
||||||
}
|
}
|
||||||
for (String methodName : NOTIF_COLLECTION_METHOD_NAMES) {
|
for (NotifCollectionAccessor accessor : notifCollectionAccessors(collection.getClass())) {
|
||||||
Object value = ReflectionSupport.invokeMethod(collection, methodName);
|
appendNotificationsFromNotifCollectionValue(notifications, accessor.get(collection));
|
||||||
appendNotificationsFromNotifCollectionValue(notifications, value);
|
|
||||||
if (!notifications.isEmpty()) {
|
if (!notifications.isEmpty()) {
|
||||||
return notifications;
|
return notifications;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (String fieldName : NOTIF_COLLECTION_FIELD_NAMES) {
|
|
||||||
Object value = ReflectionSupport.getFieldValue(collection, fieldName);
|
|
||||||
appendNotificationsFromNotifCollectionValue(notifications, value);
|
|
||||||
if (!notifications.isEmpty()) {
|
|
||||||
return notifications;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Field field : collection.getClass().getDeclaredFields()) {
|
|
||||||
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
field.setAccessible(true);
|
|
||||||
Object value = field.get(collection);
|
|
||||||
appendNotificationsFromNotifCollectionValue(notifications, value);
|
|
||||||
if (!notifications.isEmpty()) {
|
|
||||||
return notifications;
|
|
||||||
}
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
// Best-effort source matching; inaccessible fields just do not contribute.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return notifications;
|
return notifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ArrayList<NotifCollectionAccessor> notifCollectionAccessors(Class<?> type) {
|
||||||
|
ArrayList<NotifCollectionAccessor> cached = notifCollectionAccessorsByClass.get(type);
|
||||||
|
if (cached != null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
ArrayList<NotifCollectionAccessor> accessors = buildNotifCollectionAccessors(type);
|
||||||
|
notifCollectionAccessorsByClass.put(type, accessors);
|
||||||
|
return accessors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<NotifCollectionAccessor> buildNotifCollectionAccessors(Class<?> type) {
|
||||||
|
ArrayList<NotifCollectionAccessor> accessors = new ArrayList<>();
|
||||||
|
HashSet<String> fieldNames = new HashSet<>();
|
||||||
|
for (String methodName : NOTIF_COLLECTION_METHOD_NAMES) {
|
||||||
|
Method method = findNoArgMethod(type, methodName);
|
||||||
|
if (method != null) {
|
||||||
|
accessors.add(new MethodNotifCollectionAccessor(method));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String fieldName : NOTIF_COLLECTION_FIELD_NAMES) {
|
||||||
|
Field field = findField(type, fieldName);
|
||||||
|
if (field != null) {
|
||||||
|
fieldNames.add(field.getDeclaringClass().getName() + "#" + field.getName());
|
||||||
|
accessors.add(new FieldNotifCollectionAccessor(field));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Class<?> current = type;
|
||||||
|
while (current != null) {
|
||||||
|
for (Field field : current.getDeclaredFields()) {
|
||||||
|
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String key = field.getDeclaringClass().getName() + "#" + field.getName();
|
||||||
|
if (!fieldNames.add(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
field.setAccessible(true);
|
||||||
|
accessors.add(new FieldNotifCollectionAccessor(field));
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// Best-effort source matching; inaccessible fields just do not contribute.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current = current.getSuperclass();
|
||||||
|
}
|
||||||
|
return accessors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method findNoArgMethod(Class<?> type, String methodName) {
|
||||||
|
Class<?> current = type;
|
||||||
|
while (current != null) {
|
||||||
|
try {
|
||||||
|
Method method = current.getDeclaredMethod(methodName);
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method;
|
||||||
|
} catch (NoSuchMethodException ignored) {
|
||||||
|
current = current.getSuperclass();
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Field findField(Class<?> type, String fieldName) {
|
||||||
|
Class<?> current = type;
|
||||||
|
while (current != null) {
|
||||||
|
try {
|
||||||
|
Field field = current.getDeclaredField(fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
return field;
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
current = current.getSuperclass();
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void appendNotificationsFromNotifCollectionValue(
|
private void appendNotificationsFromNotifCollectionValue(
|
||||||
ArrayList<StatusBarNotification> notifications,
|
ArrayList<StatusBarNotification> notifications,
|
||||||
Object value
|
Object value
|
||||||
@@ -1185,7 +1287,7 @@ final class StockLayoutCanvasController {
|
|||||||
if (hasVisibleConfirmedAodPluginView()) {
|
if (hasVisibleConfirmedAodPluginView()) {
|
||||||
hideConfirmedAodPluginViews();
|
hideConfirmedAodPluginViews();
|
||||||
}
|
}
|
||||||
removeLockscreenCardsNotificationHost(root);
|
hideLockscreenCardsNotificationHost(root);
|
||||||
if (activeScene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS
|
if (activeScene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS
|
||||||
&& isAodLayoutRoot(root)) {
|
&& isAodLayoutRoot(root)) {
|
||||||
renderAodCardsNotificationStripIfNeeded(root, settings, activeScene);
|
renderAodCardsNotificationStripIfNeeded(root, settings, activeScene);
|
||||||
@@ -1229,8 +1331,8 @@ final class StockLayoutCanvasController {
|
|||||||
if (activeScene != null && activeScene.isUnlocked()) {
|
if (activeScene != null && activeScene.isUnlocked()) {
|
||||||
if (isKnownRootGeometryChanged(root)) {
|
if (isKnownRootGeometryChanged(root)) {
|
||||||
ownedIconHostManager.setUnlockedHostsVisible(root, false);
|
ownedIconHostManager.setUnlockedHostsVisible(root, false);
|
||||||
removeDebugOverlay(root);
|
|
||||||
}
|
}
|
||||||
|
removeDebugOverlay(root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1515,8 +1617,12 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleUnlockedAppearanceRefreshForAllRoots() {
|
private void scheduleUnlockedAppearanceRefreshForAllRoots() {
|
||||||
|
scheduleUnlockedAppearanceRefreshForAllRoots(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleUnlockedAppearanceRefreshForAllRoots(boolean highPriority) {
|
||||||
for (View root : new ArrayList<>(unlockedHostsByRoot.keySet())) {
|
for (View root : new ArrayList<>(unlockedHostsByRoot.keySet())) {
|
||||||
scheduleUnlockedAppearanceRefreshForRoot(root);
|
scheduleUnlockedAppearanceRefreshForRoot(root, highPriority);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1586,16 +1692,29 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleUnlockedAppearanceRefreshForRoot(View root) {
|
private void scheduleUnlockedAppearanceRefreshForRoot(View root) {
|
||||||
|
scheduleUnlockedAppearanceRefreshForRoot(root, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleUnlockedAppearanceRefreshForRoot(View root, boolean highPriority) {
|
||||||
if (root == null
|
if (root == null
|
||||||
|| !root.isAttachedToWindow()
|
|| !root.isAttachedToWindow()
|
||||||
|| !isUnlockedStatusBarRoot(root)
|
|| !isUnlockedStatusBarRoot(root)) {
|
||||||
|| !pendingUnlockedAppearanceRefreshRoots.add(root)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
root.post(() -> {
|
if (!pendingUnlockedAppearanceRefreshRoots.add(root) && !highPriority) {
|
||||||
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
return;
|
||||||
|
}
|
||||||
|
Runnable refresh = () -> {
|
||||||
|
if (!pendingUnlockedAppearanceRefreshRoots.remove(root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
refreshUnlockedAppearanceNow(root);
|
refreshUnlockedAppearanceNow(root);
|
||||||
});
|
};
|
||||||
|
if (highPriority && root.getHandler() != null) {
|
||||||
|
root.getHandler().postAtFrontOfQueue(refresh);
|
||||||
|
} else {
|
||||||
|
root.post(refresh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean clockTextRefreshEnabledForScene(SbtSettings settings, SceneKey scene) {
|
private boolean clockTextRefreshEnabledForScene(SbtSettings settings, SceneKey scene) {
|
||||||
@@ -1762,13 +1881,15 @@ final class StockLayoutCanvasController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!layoutRoot) {
|
if (!layoutRoot) {
|
||||||
removeSharedLayoutHosts(root);
|
if (activeScene != null
|
||||||
if (!unlockedScene && canContainLockedStatusBarIconSurfaces(root)) {
|
&& activeScene.isLockscreen()
|
||||||
|
&& canContainLockedStatusBarIconSurfaces(root)) {
|
||||||
|
removeUnlockedLayoutHosts(root);
|
||||||
|
removeAodCardsNotificationHost(root);
|
||||||
renderLockscreenCardsNotificationStripIfNeeded(root, settings, activeScene);
|
renderLockscreenCardsNotificationStripIfNeeded(root, settings, activeScene);
|
||||||
hideOrDiscoverLockedStatusBarIconSurfaces(root);
|
hideOrDiscoverLockedStatusBarIconSurfaces(root);
|
||||||
} else {
|
} else {
|
||||||
removeLockscreenCardsNotificationHost(root);
|
removeSharedLayoutHosts(root);
|
||||||
removeAodCardsNotificationHost(root);
|
|
||||||
}
|
}
|
||||||
removeDebugOverlay(root);
|
removeDebugOverlay(root);
|
||||||
return;
|
return;
|
||||||
@@ -2562,8 +2683,7 @@ final class StockLayoutCanvasController {
|
|||||||
// writes conflicting values both while tracking expansion and when it clears
|
// writes conflicting values both while tracking expansion and when it clears
|
||||||
// its state after collapse.
|
// its state after collapse.
|
||||||
return !runtimeContext.getModeStateRepository().isSystemUiShadeLocked()
|
return !runtimeContext.getModeStateRepository().isSystemUiShadeLocked()
|
||||||
&& (stackContains("LegacyQsExpandAnimator", "setQsExpansionPosition")
|
&& stackContainsConflictingShadeHeaderAnimator();
|
||||||
|| stackContains("LegacyQsExpandAnimator", "clearAnimationState"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSplitShadeStatusBarCandidate(View view) {
|
private boolean isSplitShadeStatusBarCandidate(View view) {
|
||||||
@@ -2579,16 +2699,18 @@ final class StockLayoutCanvasController {
|
|||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean stackContains(String classNamePart, String methodName) {
|
private boolean stackContainsConflictingShadeHeaderAnimator() {
|
||||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||||||
for (StackTraceElement element : stack) {
|
for (StackTraceElement element : stack) {
|
||||||
if (element == null) {
|
if (element == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String className = element.getClassName();
|
String className = element.getClassName();
|
||||||
|
String methodName = element.getMethodName();
|
||||||
if (className != null
|
if (className != null
|
||||||
&& className.contains(classNamePart)
|
&& className.contains("LegacyQsExpandAnimator")
|
||||||
&& methodName.equals(element.getMethodName())) {
|
&& ("setQsExpansionPosition".equals(methodName)
|
||||||
|
|| "clearAnimationState".equals(methodName))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2966,6 +3088,7 @@ final class StockLayoutCanvasController {
|
|||||||
dirtyUnlockedLayoutRoots.remove(root);
|
dirtyUnlockedLayoutRoots.remove(root);
|
||||||
settingsIdentityByRoot.remove(root);
|
settingsIdentityByRoot.remove(root);
|
||||||
lockscreenCardsRowFilterSignatures.remove(root);
|
lockscreenCardsRowFilterSignatures.remove(root);
|
||||||
|
lockscreenCardsNotificationRenderGenerationByRoot.remove(root);
|
||||||
clockTimeBucketByRoot.remove(root);
|
clockTimeBucketByRoot.remove(root);
|
||||||
removeDebugOverlay(root);
|
removeDebugOverlay(root);
|
||||||
root.requestLayout();
|
root.requestLayout();
|
||||||
@@ -3190,6 +3313,7 @@ final class StockLayoutCanvasController {
|
|||||||
ArrayList<ImageView> imageViews = imageViewsFromArgs(args);
|
ArrayList<ImageView> imageViews = imageViewsFromArgs(args);
|
||||||
if (!imageViews.isEmpty()) {
|
if (!imageViews.isEmpty()) {
|
||||||
latestAodIconImageViews = imageViews;
|
latestAodIconImageViews = imageViews;
|
||||||
|
pruneViewFingerprintCache(imageViews);
|
||||||
if (!runtimeContext.getModeStateRepository().isLayoutEnabled()) {
|
if (!runtimeContext.getModeStateRepository().isLayoutEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3201,6 +3325,10 @@ final class StockLayoutCanvasController {
|
|||||||
if (!(containerView instanceof ViewGroup container)) {
|
if (!(containerView instanceof ViewGroup container)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||||
|
if (activeScene == null || activeScene.isUnlocked()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!isLayoutEnabled(container.getContext())) {
|
if (!isLayoutEnabled(container.getContext())) {
|
||||||
knownAodNotificationContainers.remove(container);
|
knownAodNotificationContainers.remove(container);
|
||||||
LockscreenCardsNotificationIconSourceStore.clearAodSources(container);
|
LockscreenCardsNotificationIconSourceStore.clearAodSources(container);
|
||||||
@@ -3218,6 +3346,10 @@ final class StockLayoutCanvasController {
|
|||||||
if (knownAodNotificationContainers.isEmpty()) {
|
if (knownAodNotificationContainers.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||||
|
if (activeScene == null || activeScene.isUnlocked()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (ViewGroup container : knownAodNotificationContainers) {
|
for (ViewGroup container : knownAodNotificationContainers) {
|
||||||
if (container == null || !container.isAttachedToWindow()) {
|
if (container == null || !container.isAttachedToWindow()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -3345,9 +3477,18 @@ final class StockLayoutCanvasController {
|
|||||||
|| !AppIconRules.isValidPackageName(sbn.getPackageName())) {
|
|| !AppIconRules.isValidPackageName(sbn.getPackageName())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
String cacheKey = notificationFingerprintCacheKey(sbn);
|
||||||
|
IconFingerprint cached = notificationFingerprintByCacheKey.get(cacheKey);
|
||||||
|
if (cached != null) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Drawable drawable = sbn.getNotification().getSmallIcon().loadDrawable(context);
|
Drawable drawable = sbn.getNotification().getSmallIcon().loadDrawable(context);
|
||||||
return fingerprintForDrawable(drawable, sbn.getPackageName());
|
IconFingerprint fingerprint = fingerprintForDrawable(drawable, sbn.getPackageName());
|
||||||
|
if (fingerprint != null) {
|
||||||
|
notificationFingerprintByCacheKey.put(cacheKey, fingerprint);
|
||||||
|
}
|
||||||
|
return fingerprint;
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -3357,7 +3498,57 @@ final class StockLayoutCanvasController {
|
|||||||
if (imageView == null || imageView.getDrawable() == null) {
|
if (imageView == null || imageView.getDrawable() == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return fingerprintForDrawable(imageView.getDrawable(), null);
|
Drawable drawable = imageView.getDrawable();
|
||||||
|
DrawableFingerprintCache cached = viewFingerprintCacheByImageView.get(imageView);
|
||||||
|
if (cached != null && cached.drawable == drawable) {
|
||||||
|
return cached.fingerprint;
|
||||||
|
}
|
||||||
|
IconFingerprint fingerprint = fingerprintForDrawable(drawable, null);
|
||||||
|
if (fingerprint != null) {
|
||||||
|
viewFingerprintCacheByImageView.put(imageView, new DrawableFingerprintCache(drawable, fingerprint));
|
||||||
|
} else {
|
||||||
|
viewFingerprintCacheByImageView.remove(imageView);
|
||||||
|
}
|
||||||
|
return fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String notificationFingerprintCacheKey(StatusBarNotification sbn) {
|
||||||
|
Notification notification = sbn != null ? sbn.getNotification() : null;
|
||||||
|
Object smallIcon = notification != null ? notification.getSmallIcon() : null;
|
||||||
|
return sbn.getKey()
|
||||||
|
+ "|"
|
||||||
|
+ sbn.getPackageName()
|
||||||
|
+ "|"
|
||||||
|
+ sbn.getId()
|
||||||
|
+ "|"
|
||||||
|
+ sbn.getPostTime()
|
||||||
|
+ "|"
|
||||||
|
+ (notification != null ? notification.when : 0L)
|
||||||
|
+ "|"
|
||||||
|
+ System.identityHashCode(smallIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pruneNotificationFingerprintCache(ArrayList<StatusBarNotification> notifications) {
|
||||||
|
if (notificationFingerprintByCacheKey.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HashSet<String> activeCacheKeys = new HashSet<>();
|
||||||
|
if (notifications != null) {
|
||||||
|
for (StatusBarNotification sbn : notifications) {
|
||||||
|
if (sbn != null && sbn.getNotification() != null) {
|
||||||
|
activeCacheKeys.add(notificationFingerprintCacheKey(sbn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notificationFingerprintByCacheKey.keySet().removeIf(key -> !activeCacheKeys.contains(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pruneViewFingerprintCache(ArrayList<ImageView> imageViews) {
|
||||||
|
if (viewFingerprintCacheByImageView.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HashSet<ImageView> activeViews = new HashSet<>(imageViews);
|
||||||
|
viewFingerprintCacheByImageView.keySet().removeIf(view -> !activeViews.contains(view));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IconFingerprint fingerprintForDrawable(Drawable drawable, String packageName) {
|
private IconFingerprint fingerprintForDrawable(Drawable drawable, String packageName) {
|
||||||
@@ -3708,6 +3899,19 @@ final class StockLayoutCanvasController {
|
|||||||
clockTimeBucketByRoot.remove(root);
|
clockTimeBucketByRoot.remove(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeUnlockedLayoutHosts(View root) {
|
||||||
|
ownedIconHostManager.removeHost(root, OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST);
|
||||||
|
ownedIconHostManager.removeHost(root, OwnedIconHostManager.TAG_UNLOCKED_NOTIFICATION_HOST);
|
||||||
|
ownedIconHostManager.removeHost(root, OwnedIconHostManager.TAG_UNLOCKED_CLOCK_HOST);
|
||||||
|
ownedIconHostManager.removeHost(root, OwnedIconHostManager.TAG_UNLOCKED_CARRIER_HOST);
|
||||||
|
ownedIconHostManager.removeHost(root, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST);
|
||||||
|
unlockedHostsByRoot.remove(root);
|
||||||
|
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
||||||
|
dirtyUnlockedLayoutRoots.remove(root);
|
||||||
|
settingsIdentityByRoot.remove(root);
|
||||||
|
clockTimeBucketByRoot.remove(root);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isDescendantOfNotificationShadeWindow(View view) {
|
private boolean isDescendantOfNotificationShadeWindow(View view) {
|
||||||
View current = view;
|
View current = view;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
@@ -3768,13 +3972,37 @@ final class StockLayoutCanvasController {
|
|||||||
|| activeScene == null
|
|| activeScene == null
|
||||||
|| !activeScene.isLockscreen()
|
|| !activeScene.isLockscreen()
|
||||||
|| activeScene.notificationDisplayStyle() != NotificationDisplayStyle.CARDS
|
|| activeScene.notificationDisplayStyle() != NotificationDisplayStyle.CARDS
|
||||||
|
|| !isNotificationShadeWindowRoot(root)
|
||||||
|| suppressForTransition) {
|
|| suppressForTransition) {
|
||||||
removeLockscreenCardsNotificationHost(root);
|
removeLockscreenCardsNotificationHost(root);
|
||||||
|
lockscreenCardsNotificationRenderGenerationByRoot.remove(root);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
View host = ownedIconHostManager.ensureLockscreenCardsNotificationHost(root);
|
View host = ownedIconHostManager.ensureLockscreenCardsNotificationHost(root);
|
||||||
if (host instanceof ViewGroup hostGroup) {
|
if (host instanceof ViewGroup hostGroup) {
|
||||||
lockscreenCardsNotificationStripRenderer.render(root, hostGroup, settings, activeScene);
|
if (host.getVisibility() != View.VISIBLE) {
|
||||||
|
host.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
setHostAlpha(host, 1f);
|
||||||
|
int settingsId = System.identityHashCode(settings);
|
||||||
|
int rootWidth = root.getWidth();
|
||||||
|
int rootHeight = root.getHeight();
|
||||||
|
int activeKeyGeneration = NotificationActiveKeyStore.generation();
|
||||||
|
int sourceSignature =
|
||||||
|
LockscreenCardsNotificationIconSourceStore.snapshotSourceSignatureForRoot(root);
|
||||||
|
int generation = settingsId;
|
||||||
|
generation = 31 * generation + rootWidth;
|
||||||
|
generation = 31 * generation + rootHeight;
|
||||||
|
generation = 31 * generation + activeKeyGeneration;
|
||||||
|
generation = 31 * generation + sourceSignature;
|
||||||
|
Integer previous = lockscreenCardsNotificationRenderGenerationByRoot.get(root);
|
||||||
|
boolean shouldRender = previous == null
|
||||||
|
|| previous != generation
|
||||||
|
|| hostGroup.getChildCount() <= 0;
|
||||||
|
if (shouldRender) {
|
||||||
|
lockscreenCardsNotificationRenderGenerationByRoot.put(root, generation);
|
||||||
|
lockscreenCardsNotificationStripRenderer.render(root, hostGroup, settings, activeScene);
|
||||||
|
}
|
||||||
host.bringToFront();
|
host.bringToFront();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3933,10 +4161,27 @@ final class StockLayoutCanvasController {
|
|||||||
|| className.contains("keyguardpattern");
|
|| className.contains("keyguardpattern");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void hideLockscreenCardsNotificationHost(View root) {
|
||||||
|
View host = root instanceof ViewGroup group
|
||||||
|
? findDirectOwnedHost(group, OwnedIconHostManager.TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST)
|
||||||
|
: null;
|
||||||
|
if (host == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (host.getVisibility() != View.INVISIBLE) {
|
||||||
|
host.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
setHostAlpha(host, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
private void removeLockscreenCardsNotificationHost(View root) {
|
private void removeLockscreenCardsNotificationHost(View root) {
|
||||||
View host = root instanceof ViewGroup group
|
View host = root instanceof ViewGroup group
|
||||||
? findDirectOwnedHost(group, OwnedIconHostManager.TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST)
|
? findDirectOwnedHost(group, OwnedIconHostManager.TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST)
|
||||||
: null;
|
: null;
|
||||||
|
if (host == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lockscreenCardsNotificationRenderGenerationByRoot.remove(root);
|
||||||
if (host instanceof ViewGroup hostGroup) {
|
if (host instanceof ViewGroup hostGroup) {
|
||||||
lockscreenCardsNotificationStripRenderer.clear(hostGroup);
|
lockscreenCardsNotificationStripRenderer.clear(hostGroup);
|
||||||
}
|
}
|
||||||
@@ -4136,6 +4381,33 @@ final class StockLayoutCanvasController {
|
|||||||
void visit(View view);
|
void visit(View view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface NotifCollectionAccessor {
|
||||||
|
Object get(Object collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record MethodNotifCollectionAccessor(Method method) implements NotifCollectionAccessor {
|
||||||
|
@Override
|
||||||
|
public Object get(Object collection) {
|
||||||
|
try {
|
||||||
|
return method.invoke(collection);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record FieldNotifCollectionAccessor(Field field) implements NotifCollectionAccessor {
|
||||||
|
@Override
|
||||||
|
public Object get(Object collection) {
|
||||||
|
try {
|
||||||
|
return field.get(collection);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final class HiddenViewState {
|
private static final class HiddenViewState {
|
||||||
final int visibility;
|
final int visibility;
|
||||||
final float alpha;
|
final float alpha;
|
||||||
@@ -4146,6 +4418,9 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record DrawableFingerprintCache(Drawable drawable, IconFingerprint fingerprint) {
|
||||||
|
}
|
||||||
|
|
||||||
private record IconFingerprint(String packageName, boolean[] mask, int filled) {
|
private record IconFingerprint(String packageName, boolean[] mask, int filled) {
|
||||||
int distance(IconFingerprint other) {
|
int distance(IconFingerprint other) {
|
||||||
if (other == null || other.mask == null || mask == null || other.mask.length != mask.length) {
|
if (other == null || other.mask == null || mask == null || other.mask.length != mask.length) {
|
||||||
|
|||||||
@@ -128,6 +128,15 @@ final class ClockLayout {
|
|||||||
TextView source,
|
TextView source,
|
||||||
LayoutPosition position,
|
LayoutPosition position,
|
||||||
int sourceBaselineY
|
int sourceBaselineY
|
||||||
|
) {
|
||||||
|
return simpleText(source, position, sourceBaselineY, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ClockLayout simpleText(
|
||||||
|
TextView source,
|
||||||
|
LayoutPosition position,
|
||||||
|
int sourceBaselineY,
|
||||||
|
int maxWidth
|
||||||
) {
|
) {
|
||||||
if (source == null || source.getText() == null || source.getText().length() == 0) {
|
if (source == null || source.getText() == null || source.getText().length() == 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -141,12 +150,13 @@ final class ClockLayout {
|
|||||||
if (measured.width <= 0 || measured.height <= 0) {
|
if (measured.width <= 0 || measured.height <= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
int width = Math.max(1, Math.min(measured.width, maxWidth));
|
||||||
int top = sourceBaselineY - measured.baseline;
|
int top = sourceBaselineY - measured.baseline;
|
||||||
ClockRow row = new ClockRow(
|
ClockRow row = new ClockRow(
|
||||||
source.getText(),
|
source.getText(),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
measured.width,
|
width,
|
||||||
measured.height,
|
measured.height,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@@ -161,7 +171,7 @@ final class ClockLayout {
|
|||||||
top,
|
top,
|
||||||
0,
|
0,
|
||||||
false,
|
false,
|
||||||
measured.width,
|
width,
|
||||||
measured.height);
|
measured.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package se.ajpanton.statusbartweak.runtime.render;
|
package se.ajpanton.statusbartweak.runtime.render;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -8,9 +9,12 @@ import android.widget.FrameLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
import se.ajpanton.statusbartweak.runtime.ViewIdNames;
|
||||||
|
|
||||||
final class ClockProxyRenderer {
|
final class ClockProxyRenderer {
|
||||||
private final WeakHashMap<ViewGroup, ArrayList<TextView>> rowsByHost = new WeakHashMap<>();
|
private final WeakHashMap<ViewGroup, ArrayList<TextView>> rowsByHost = new WeakHashMap<>();
|
||||||
|
|
||||||
@@ -50,6 +54,12 @@ final class ClockProxyRenderer {
|
|||||||
if (copyTextStyleIfChanged(placement.source, view)) {
|
if (copyTextStyleIfChanged(placement.source, view)) {
|
||||||
hostChanged = true;
|
hostChanged = true;
|
||||||
}
|
}
|
||||||
|
if (applyTextColorOverrideIfNeeded(view, placement.textColorOverride)) {
|
||||||
|
hostChanged = true;
|
||||||
|
}
|
||||||
|
if (applyCarrierMarqueeIfNeeded(placement.source, view)) {
|
||||||
|
hostChanged = true;
|
||||||
|
}
|
||||||
if (!sameTextAndColorSpans(view.getText(), row.text)) {
|
if (!sameTextAndColorSpans(view.getText(), row.text)) {
|
||||||
view.setText(row.text);
|
view.setText(row.text);
|
||||||
hostChanged = true;
|
hostChanged = true;
|
||||||
@@ -90,6 +100,35 @@ final class ClockProxyRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean applyCarrierMarqueeIfNeeded(TextView source, TextView target) {
|
||||||
|
boolean carrier = isCarrierTextView(source);
|
||||||
|
boolean changed = false;
|
||||||
|
TextUtils.TruncateAt desiredEllipsize = carrier ? TextUtils.TruncateAt.MARQUEE : null;
|
||||||
|
if (target.getEllipsize() != desiredEllipsize) {
|
||||||
|
target.setEllipsize(desiredEllipsize);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
int desiredRepeatLimit = carrier ? -1 : 0;
|
||||||
|
if (target.getMarqueeRepeatLimit() != desiredRepeatLimit) {
|
||||||
|
target.setMarqueeRepeatLimit(desiredRepeatLimit);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (target.isSelected() != carrier) {
|
||||||
|
target.setSelected(carrier);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isCarrierTextView(TextView view) {
|
||||||
|
if (view == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String idName = ViewIdNames.idName(view).toLowerCase(Locale.ROOT);
|
||||||
|
String className = view.getClass().getName().toLowerCase(Locale.ROOT);
|
||||||
|
return idName.contains("carrier") || className.contains("carrier");
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean sameTextAndColorSpans(CharSequence current, CharSequence desired) {
|
private static boolean sameTextAndColorSpans(CharSequence current, CharSequence desired) {
|
||||||
if (!String.valueOf(current).contentEquals(String.valueOf(desired))) {
|
if (!String.valueOf(current).contentEquals(String.valueOf(desired))) {
|
||||||
return false;
|
return false;
|
||||||
@@ -189,6 +228,17 @@ final class ClockProxyRenderer {
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean applyTextColorOverrideIfNeeded(TextView view, int color) {
|
||||||
|
if (view == null || (color >>> 24) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (view.getCurrentTextColor() == color) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
view.setTextColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean applyTextLayoutParams(
|
private static boolean applyTextLayoutParams(
|
||||||
ViewGroup host,
|
ViewGroup host,
|
||||||
TextView view,
|
TextView view,
|
||||||
|
|||||||
@@ -11,17 +11,29 @@ final class ClockPlacement {
|
|||||||
final String contentDescription;
|
final String contentDescription;
|
||||||
final ArrayList<ClockRow> rows;
|
final ArrayList<ClockRow> rows;
|
||||||
final Bounds bounds;
|
final Bounds bounds;
|
||||||
|
final int textColorOverride;
|
||||||
|
|
||||||
ClockPlacement(
|
ClockPlacement(
|
||||||
TextView source,
|
TextView source,
|
||||||
String contentDescription,
|
String contentDescription,
|
||||||
ArrayList<ClockRow> rows,
|
ArrayList<ClockRow> rows,
|
||||||
Bounds bounds
|
Bounds bounds
|
||||||
|
) {
|
||||||
|
this(source, contentDescription, rows, bounds, android.graphics.Color.TRANSPARENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClockPlacement(
|
||||||
|
TextView source,
|
||||||
|
String contentDescription,
|
||||||
|
ArrayList<ClockRow> rows,
|
||||||
|
Bounds bounds,
|
||||||
|
int textColorOverride
|
||||||
) {
|
) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.contentDescription = contentDescription;
|
this.contentDescription = contentDescription;
|
||||||
this.rows = rows;
|
this.rows = rows;
|
||||||
this.bounds = bounds;
|
this.bounds = bounds;
|
||||||
|
this.textColorOverride = textColorOverride;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
@@ -257,6 +257,35 @@ public final class LockscreenCardsNotificationIconSourceStore {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int snapshotSourceSignatureForRoot(View root) {
|
||||||
|
ArrayList<SnapshotSource> sources = snapshotSourcesForRoot(root);
|
||||||
|
int result = 17;
|
||||||
|
for (SnapshotSource source : sources) {
|
||||||
|
result = 31 * result + snapshotSourceSignature(root, source);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int snapshotSourceSignature(View root, SnapshotSource source) {
|
||||||
|
if (source == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int result = 17;
|
||||||
|
View view = source.view();
|
||||||
|
result = 31 * result + System.identityHashCode(view);
|
||||||
|
result = 31 * result + (source.keyHint() != null ? source.keyHint().hashCode() : 0);
|
||||||
|
result = 31 * result + (source.mode() != null ? source.mode().hashCode() : 0);
|
||||||
|
Bounds override = source.boundsOverride();
|
||||||
|
result = 31 * result + (override != null ? override.signature() : 0);
|
||||||
|
Bounds bounds = ViewGeometry.boundsRelativeTo(root, view);
|
||||||
|
result = 31 * result + (bounds != null ? bounds.signature() : 0);
|
||||||
|
if (view != null) {
|
||||||
|
result = 31 * result + view.getVisibility();
|
||||||
|
result = 31 * result + Float.floatToIntBits(view.getAlpha());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static ArrayList<SnapshotSource> snapshotSourcesForAodIconsRoot(View root) {
|
static ArrayList<SnapshotSource> snapshotSourcesForAodIconsRoot(View root) {
|
||||||
ArrayList<SnapshotSource> result = new ArrayList<>();
|
ArrayList<SnapshotSource> result = new ArrayList<>();
|
||||||
ViewGroup container = sourceContainerForAodIconsRoot(root);
|
ViewGroup container = sourceContainerForAodIconsRoot(root);
|
||||||
|
|||||||
@@ -180,7 +180,8 @@ record SnapshotRenderState(
|
|||||||
String heldSignalKey,
|
String heldSignalKey,
|
||||||
int heldBitmapId,
|
int heldBitmapId,
|
||||||
SnapshotMode sourceMode,
|
SnapshotMode sourceMode,
|
||||||
int sourceSignature
|
int sourceSignature,
|
||||||
|
int tintSignature
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.graphics.Bitmap;
|
|||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
@@ -45,9 +46,12 @@ final class SnapshotRenderer {
|
|||||||
new WeakHashMap<>();
|
new WeakHashMap<>();
|
||||||
private final WeakHashMap<View, SourceSignatureCache> sourceSignaturesByView =
|
private final WeakHashMap<View, SourceSignatureCache> sourceSignaturesByView =
|
||||||
new WeakHashMap<>();
|
new WeakHashMap<>();
|
||||||
|
private final WeakHashMap<View, ContentBoundsCache> contentBoundsByView =
|
||||||
|
new WeakHashMap<>();
|
||||||
private final LinkedHashMap<String, HeldSignal> heldSignals = new LinkedHashMap<>();
|
private final LinkedHashMap<String, HeldSignal> heldSignals = new LinkedHashMap<>();
|
||||||
|
|
||||||
private long holdSignalsUntilElapsedMs;
|
private long holdSignalsUntilElapsedMs;
|
||||||
|
private boolean statusBarTintTargetEnabled;
|
||||||
|
|
||||||
SnapshotRenderer(
|
SnapshotRenderer(
|
||||||
boolean holdSignalsDuringTransition,
|
boolean holdSignalsDuringTransition,
|
||||||
@@ -65,6 +69,14 @@ final class SnapshotRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setStatusBarTintTargetEnabled(boolean enabled) {
|
||||||
|
statusBarTintTargetEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
int statusBarTintColor() {
|
||||||
|
return statusBarTintTargetEnabled ? StatusBarTintTarget.color() : Color.TRANSPARENT;
|
||||||
|
}
|
||||||
|
|
||||||
ArrayList<SnapshotPlacement> rawPlacements(
|
ArrayList<SnapshotPlacement> rawPlacements(
|
||||||
View root,
|
View root,
|
||||||
ArrayList<SnapshotSource> sources
|
ArrayList<SnapshotSource> sources
|
||||||
@@ -78,6 +90,7 @@ final class SnapshotRenderer {
|
|||||||
boolean sortByX
|
boolean sortByX
|
||||||
) {
|
) {
|
||||||
ArrayList<SnapshotPlacement> placements = new ArrayList<>();
|
ArrayList<SnapshotPlacement> placements = new ArrayList<>();
|
||||||
|
LinkedHashMap<String, Integer> keyCounts = new LinkedHashMap<>();
|
||||||
int order = 0;
|
int order = 0;
|
||||||
for (SnapshotSource source : sources) {
|
for (SnapshotSource source : sources) {
|
||||||
Bounds bounds = source.boundsOverride() != null
|
Bounds bounds = source.boundsOverride() != null
|
||||||
@@ -87,7 +100,7 @@ final class SnapshotRenderer {
|
|||||||
if (source.mode() == SnapshotMode.NOTIFICATION_DRAWABLE) {
|
if (source.mode() == SnapshotMode.NOTIFICATION_DRAWABLE) {
|
||||||
bounds = notificationDrawableBounds(bounds);
|
bounds = notificationDrawableBounds(bounds);
|
||||||
}
|
}
|
||||||
String key = normalizedKeyFor(source, order);
|
String key = uniqueKey(normalizedKeyFor(source, order), keyCounts);
|
||||||
boolean signal = isSignalKey(key);
|
boolean signal = isSignalKey(key);
|
||||||
boolean trimContent = !signal && shouldTrimContent(source);
|
boolean trimContent = !signal && shouldTrimContent(source);
|
||||||
Bounds contentBounds = trimContent ? snapshotContentBounds(source, bounds) : null;
|
Bounds contentBounds = trimContent ? snapshotContentBounds(source, bounds) : null;
|
||||||
@@ -138,14 +151,44 @@ final class SnapshotRenderer {
|
|||||||
return placements;
|
return placements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String uniqueKey(String key, LinkedHashMap<String, Integer> keyCounts) {
|
||||||
|
if (key == null || keyCounts == null) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
int count = keyCounts.getOrDefault(key, 0);
|
||||||
|
keyCounts.put(key, count + 1);
|
||||||
|
return count == 0 ? key : key + "#" + count;
|
||||||
|
}
|
||||||
|
|
||||||
Bounds snapshotContentBounds(SnapshotSource source, Bounds bounds) {
|
Bounds snapshotContentBounds(SnapshotSource source, Bounds bounds) {
|
||||||
if (source == null || bounds == null || bounds.width <= 0 || bounds.height <= 0) {
|
if (source == null || bounds == null || bounds.width <= 0 || bounds.height <= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
int signature = sourceSignature(source);
|
||||||
|
View sourceView = source.view();
|
||||||
|
if (sourceView != null) {
|
||||||
|
ContentBoundsCache cached = contentBoundsByView.get(sourceView);
|
||||||
|
if (cached != null
|
||||||
|
&& cached.signature == signature
|
||||||
|
&& cached.width == bounds.width
|
||||||
|
&& cached.height == bounds.height
|
||||||
|
&& cached.mode == source.mode()) {
|
||||||
|
return cached.bounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
Bitmap bitmap = Bitmap.createBitmap(bounds.width, bounds.height, Bitmap.Config.ARGB_8888);
|
Bitmap bitmap = Bitmap.createBitmap(bounds.width, bounds.height, Bitmap.Config.ARGB_8888);
|
||||||
try {
|
try {
|
||||||
drawSnapshot(source, bitmap);
|
drawSnapshot(source, bitmap);
|
||||||
return bitmapContentBounds(bitmap);
|
Bounds contentBounds = bitmapContentBounds(bitmap);
|
||||||
|
if (sourceView != null && contentBounds != null) {
|
||||||
|
contentBoundsByView.put(sourceView, new ContentBoundsCache(
|
||||||
|
signature,
|
||||||
|
bounds.width,
|
||||||
|
bounds.height,
|
||||||
|
source.mode(),
|
||||||
|
contentBounds));
|
||||||
|
}
|
||||||
|
return contentBounds;
|
||||||
} finally {
|
} finally {
|
||||||
bitmap.recycle();
|
bitmap.recycle();
|
||||||
}
|
}
|
||||||
@@ -204,6 +247,8 @@ final class SnapshotRenderer {
|
|||||||
drawAppIcon(host.getContext(), placement.source(), bitmap);
|
drawAppIcon(host.getContext(), placement.source(), bitmap);
|
||||||
} else if (placement.source() != null) {
|
} else if (placement.source() != null) {
|
||||||
drawSnapshot(placement, bitmap);
|
drawSnapshot(placement, bitmap);
|
||||||
|
applyStatusBarTintTarget(placement.source(), bitmap);
|
||||||
|
normalizeSemiTransparentDarkIcon(bitmap);
|
||||||
if (placement.signal()) {
|
if (placement.signal()) {
|
||||||
saveHeldSignal(key, bounds, bitmap);
|
saveHeldSignal(key, bounds, bitmap);
|
||||||
}
|
}
|
||||||
@@ -516,7 +561,8 @@ final class SnapshotRenderer {
|
|||||||
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 ? sourceSignature(placement.source()) : 0);
|
placement.source() != null ? sourceSignature(placement.source()) : 0,
|
||||||
|
tintSignatureFor(placement.source()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int sourceSignature(SnapshotSource source) {
|
private int sourceSignature(SnapshotSource source) {
|
||||||
@@ -527,9 +573,14 @@ final class SnapshotRenderer {
|
|||||||
result = 31 * result + sourceTreeSignature(source.view());
|
result = 31 * result + sourceTreeSignature(source.view());
|
||||||
Bounds boundsOverride = source.boundsOverride();
|
Bounds boundsOverride = source.boundsOverride();
|
||||||
result = 31 * result + (boundsOverride != null ? boundsOverride.signature() : 0);
|
result = 31 * result + (boundsOverride != null ? boundsOverride.signature() : 0);
|
||||||
|
result = 31 * result + tintSignatureFor(source);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int tintSignatureFor(SnapshotSource source) {
|
||||||
|
return shouldApplyStatusBarTintTarget(source) ? StatusBarTintTarget.signature() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isStatusBarIconView(View view) {
|
private boolean isStatusBarIconView(View view) {
|
||||||
return view != null && view.getClass().getName().contains("StatusBarIconView");
|
return view != null && view.getClass().getName().contains("StatusBarIconView");
|
||||||
}
|
}
|
||||||
@@ -626,6 +677,15 @@ final class SnapshotRenderer {
|
|||||||
private record SourceSignatureCache(int stamp, int signature) {
|
private record SourceSignatureCache(int stamp, int signature) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record ContentBoundsCache(
|
||||||
|
int signature,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
SnapshotMode mode,
|
||||||
|
Bounds bounds
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
private void drawSnapshot(SnapshotSource source, Bitmap bitmap) {
|
private void drawSnapshot(SnapshotSource source, Bitmap bitmap) {
|
||||||
drawSnapshot(source, bitmap, 0);
|
drawSnapshot(source, bitmap, 0);
|
||||||
}
|
}
|
||||||
@@ -724,6 +784,100 @@ final class SnapshotRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyStatusBarTintTarget(SnapshotSource source, Bitmap bitmap) {
|
||||||
|
if (!shouldApplyStatusBarTintTarget(source)
|
||||||
|
|| bitmap == null
|
||||||
|
|| bitmap.isRecycled()
|
||||||
|
|| bitmap.getWidth() <= 0
|
||||||
|
|| bitmap.getHeight() <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (source.mode() == SnapshotMode.NOTIFICATION_DRAWABLE && !isTintableMonochrome(bitmap)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
canvas.drawColor(StatusBarTintTarget.color(), PorterDuff.Mode.SRC_IN);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldApplyStatusBarTintTarget(SnapshotSource source) {
|
||||||
|
return statusBarTintTargetEnabled
|
||||||
|
&& source != null
|
||||||
|
&& source.mode() != SnapshotMode.APP_ICON
|
||||||
|
&& source.mode() != SnapshotMode.STATUS_CHIP;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTintableMonochrome(Bitmap bitmap) {
|
||||||
|
int coloredPixels = 0;
|
||||||
|
int monochromePixels = 0;
|
||||||
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
|
for (int x = 0; x < bitmap.getWidth(); x++) {
|
||||||
|
int color = bitmap.getPixel(x, y);
|
||||||
|
int alpha = Color.alpha(color);
|
||||||
|
if (alpha == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int red = Color.red(color);
|
||||||
|
int green = Color.green(color);
|
||||||
|
int blue = Color.blue(color);
|
||||||
|
int spread = Math.max(red, Math.max(green, blue)) - Math.min(red, Math.min(green, blue));
|
||||||
|
if (spread <= 18) {
|
||||||
|
monochromePixels++;
|
||||||
|
} else {
|
||||||
|
coloredPixels++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int total = monochromePixels + coloredPixels;
|
||||||
|
if (total == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Anti-aliasing and compression can introduce tiny color noise, but real
|
||||||
|
// app/debug icons contain enough colored pixels to fail this threshold.
|
||||||
|
return coloredPixels <= Math.max(2, total / 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void normalizeSemiTransparentDarkIcon(Bitmap bitmap) {
|
||||||
|
if (bitmap == null || bitmap.isRecycled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int width = bitmap.getWidth();
|
||||||
|
int height = bitmap.getHeight();
|
||||||
|
int maxAlpha = 0;
|
||||||
|
int darkPixels = 0;
|
||||||
|
int nonDarkPixels = 0;
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
int color = bitmap.getPixel(x, y);
|
||||||
|
int alpha = Color.alpha(color);
|
||||||
|
if (alpha == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Color.red(color) <= 48 && Color.green(color) <= 48 && Color.blue(color) <= 48) {
|
||||||
|
darkPixels++;
|
||||||
|
maxAlpha = Math.max(maxAlpha, alpha);
|
||||||
|
} else {
|
||||||
|
nonDarkPixels++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (darkPixels == 0 || nonDarkPixels > darkPixels / 8 || maxAlpha < 64 || maxAlpha >= 250) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
int color = bitmap.getPixel(x, y);
|
||||||
|
int alpha = Color.alpha(color);
|
||||||
|
if (alpha == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Color.red(color) <= 48 && Color.green(color) <= 48 && Color.blue(color) <= 48) {
|
||||||
|
int scaledAlpha = Math.min(255, Math.round(alpha * 255f / maxAlpha));
|
||||||
|
bitmap.setPixel(x, y, Color.argb(scaledAlpha, 0, 0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Bounds effectiveSourceRenderBounds(
|
private Bounds effectiveSourceRenderBounds(
|
||||||
SnapshotPlacement placement,
|
SnapshotPlacement placement,
|
||||||
Bounds requestedBounds,
|
Bounds requestedBounds,
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package se.ajpanton.statusbartweak.runtime.render;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
|
||||||
|
public final class StatusBarTintTarget {
|
||||||
|
private static volatile int color = Color.WHITE;
|
||||||
|
private static volatile int generation;
|
||||||
|
|
||||||
|
private StatusBarTintTarget() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean setDarkIcons(boolean darkIcons) {
|
||||||
|
int nextColor = darkIcons ? Color.BLACK : Color.WHITE;
|
||||||
|
if (color == nextColor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
color = nextColor;
|
||||||
|
generation++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int color() {
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int signature() {
|
||||||
|
return 31 * generation + color;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+38
-2
@@ -42,6 +42,7 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
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<>();
|
private final WeakHashMap<View, Boolean> forcedChipRefreshByRoot = new WeakHashMap<>();
|
||||||
|
private boolean statusBarTintTargetEnabled;
|
||||||
|
|
||||||
public UnlockedIconSnapshotRenderController() {
|
public UnlockedIconSnapshotRenderController() {
|
||||||
sourceCollector = new UnlockedStockSourceCollector();
|
sourceCollector = new UnlockedStockSourceCollector();
|
||||||
@@ -75,6 +76,7 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
return new RenderResult(true, true, true);
|
return new RenderResult(true, true, true);
|
||||||
}
|
}
|
||||||
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
||||||
|
setStatusBarTintTargetEnabled(scene.isUnlocked());
|
||||||
layoutHostToRoot(root, clockHost);
|
layoutHostToRoot(root, clockHost);
|
||||||
layoutHostToRoot(root, carrierHost);
|
layoutHostToRoot(root, carrierHost);
|
||||||
layoutHostToRoot(root, chipHost);
|
layoutHostToRoot(root, chipHost);
|
||||||
@@ -209,6 +211,7 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
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);
|
||||||
|
layoutPlan = withCurrentClockAppearance(root, layoutPlan, collectedIcons, settings);
|
||||||
lastLayoutPlanByRoot.put(root, layoutPlan);
|
lastLayoutPlanByRoot.put(root, layoutPlan);
|
||||||
if (clockEnabledForScene(settings, scene) && layoutPlan.clockPlacement != null) {
|
if (clockEnabledForScene(settings, scene) && layoutPlan.clockPlacement != null) {
|
||||||
clockRenderer.render(clockHost, layoutPlan.clockPlacement);
|
clockRenderer.render(clockHost, layoutPlan.clockPlacement);
|
||||||
@@ -257,6 +260,7 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
||||||
|
setStatusBarTintTargetEnabled(true);
|
||||||
CollectedIcons collectedIcons = lastCollectedIconsByRoot.get(root);
|
CollectedIcons collectedIcons = lastCollectedIconsByRoot.get(root);
|
||||||
LayoutPlan updatedPlan = withCurrentDotColors(layoutPlan, collectedIcons);
|
LayoutPlan updatedPlan = withCurrentDotColors(layoutPlan, collectedIcons);
|
||||||
if (updatedPlan != layoutPlan) {
|
if (updatedPlan != layoutPlan) {
|
||||||
@@ -304,6 +308,7 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
if (!settings.layoutChipEnabledUnlocked) {
|
if (!settings.layoutChipEnabledUnlocked) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
setStatusBarTintTargetEnabled(true);
|
||||||
chipRenderer.renderPlacements(chipHost, layoutPlan.chipPlacements);
|
chipRenderer.renderPlacements(chipHost, layoutPlan.chipPlacements);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -325,6 +330,7 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
||||||
|
setStatusBarTintTargetEnabled(scene.isUnlocked());
|
||||||
if (!clockEnabledForScene(settings, scene)) {
|
if (!clockEnabledForScene(settings, scene)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -504,6 +510,10 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int sourceColor(SnapshotSource source) {
|
private int sourceColor(SnapshotSource source) {
|
||||||
|
int targetColor = statusBarTintTargetColor();
|
||||||
|
if (hasAlpha(targetColor)) {
|
||||||
|
return targetColor;
|
||||||
|
}
|
||||||
if (source == null || source.view() == null) {
|
if (source == null || source.view() == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -545,6 +555,10 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int clockReferenceColor(ClockPlacement placement, CollectedIcons icons) {
|
private int clockReferenceColor(ClockPlacement placement, CollectedIcons icons) {
|
||||||
|
int targetColor = statusBarTintTargetColor();
|
||||||
|
if (hasAlpha(targetColor)) {
|
||||||
|
return targetColor;
|
||||||
|
}
|
||||||
if (icons != null && icons.statusIcons != null) {
|
if (icons != null && icons.statusIcons != null) {
|
||||||
for (SnapshotSource source : icons.statusIcons) {
|
for (SnapshotSource source : icons.statusIcons) {
|
||||||
int color = sourceColor(source);
|
int color = sourceColor(source);
|
||||||
@@ -567,6 +581,21 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
return android.graphics.Color.WHITE;
|
return android.graphics.Color.WHITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setStatusBarTintTargetEnabled(boolean enabled) {
|
||||||
|
statusBarTintTargetEnabled = enabled;
|
||||||
|
statusRenderer.setStatusBarTintTargetEnabled(enabled);
|
||||||
|
notificationRenderer.setStatusBarTintTargetEnabled(enabled);
|
||||||
|
chipRenderer.setStatusBarTintTargetEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int statusBarTintTargetColor() {
|
||||||
|
return statusBarTintTargetEnabled ? StatusBarTintTarget.color() : android.graphics.Color.TRANSPARENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasAlpha(int color) {
|
||||||
|
return (color >>> 24) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
private int clockAppearanceSignature(
|
private int clockAppearanceSignature(
|
||||||
ClockPlacement placement,
|
ClockPlacement placement,
|
||||||
int referenceColor,
|
int referenceColor,
|
||||||
@@ -574,6 +603,7 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
) {
|
) {
|
||||||
int result = 17;
|
int result = 17;
|
||||||
result = 31 * result + referenceColor;
|
result = 31 * result + referenceColor;
|
||||||
|
result = 31 * result + (placement != null ? placement.textColorOverride : 0);
|
||||||
result = 31 * result + clockTextSettingsSignature(settings);
|
result = 31 * result + clockTextSettingsSignature(settings);
|
||||||
if (placement != null && placement.source != null) {
|
if (placement != null && placement.source != null) {
|
||||||
result = 31 * result + placement.source.getCurrentTextColor();
|
result = 31 * result + placement.source.getCurrentTextColor();
|
||||||
@@ -839,7 +869,8 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
source,
|
source,
|
||||||
model.contentDescription,
|
model.contentDescription,
|
||||||
rows,
|
rows,
|
||||||
previousPlacement.bounds);
|
previousPlacement.bounds,
|
||||||
|
statusBarTintTargetColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer clockSourceGeometrySignature(TextView source) {
|
private Integer clockSourceGeometrySignature(TextView source) {
|
||||||
@@ -894,7 +925,12 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
previousRow.rowIndex,
|
previousRow.rowIndex,
|
||||||
previousRow.segment));
|
previousRow.segment));
|
||||||
}
|
}
|
||||||
return new ClockPlacement(previous.source, currentText.contentDescription, rows, previous.bounds);
|
return new ClockPlacement(
|
||||||
|
previous.source,
|
||||||
|
currentText.contentDescription,
|
||||||
|
rows,
|
||||||
|
previous.bounds,
|
||||||
|
currentText.textColorOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClockRow matchingClockRow(ClockRow previousRow, ArrayList<ClockRow> currentRows) {
|
private ClockRow matchingClockRow(ClockRow previousRow, ArrayList<ClockRow> currentRows) {
|
||||||
|
|||||||
+10
-1
@@ -558,6 +558,10 @@ final class UnlockedLayoutBand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int sourceColor(SnapshotPlacement placement) {
|
private int sourceColor(SnapshotPlacement placement) {
|
||||||
|
int targetColor = renderer != null ? renderer.statusBarTintColor() : android.graphics.Color.TRANSPARENT;
|
||||||
|
if (hasAlpha(targetColor)) {
|
||||||
|
return targetColor;
|
||||||
|
}
|
||||||
if (placement == null || placement.source == null || placement.source.view() == null) {
|
if (placement == null || placement.source == null || placement.source.view() == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -567,6 +571,10 @@ final class UnlockedLayoutBand {
|
|||||||
return android.graphics.Color.alpha(color) != 0 ? color : 0;
|
return android.graphics.Color.alpha(color) != 0 ? color : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasAlpha(int color) {
|
||||||
|
return (color >>> 24) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
private SnapshotPlacement emptyIconContainerPlacement() {
|
private SnapshotPlacement emptyIconContainerPlacement() {
|
||||||
int height = Math.max(1, dotHeight(activePlacements()));
|
int height = Math.max(1, dotHeight(activePlacements()));
|
||||||
return new SnapshotPlacement(
|
return new SnapshotPlacement(
|
||||||
@@ -699,7 +707,8 @@ final class UnlockedLayoutBand {
|
|||||||
placement.source,
|
placement.source,
|
||||||
placement.contentDescription,
|
placement.contentDescription,
|
||||||
rows,
|
rows,
|
||||||
new Bounds(clipLeft, placement.bounds.top, Math.max(0, clipRight - clipLeft), placement.bounds.height));
|
new Bounds(clipLeft, placement.bounds.top, Math.max(0, clipRight - clipLeft), placement.bounds.height),
|
||||||
|
placement.textColorOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isTextKind() {
|
private boolean isTextKind() {
|
||||||
|
|||||||
+109
-5
@@ -87,6 +87,7 @@ final class UnlockedLayoutPlanner {
|
|||||||
Bounds aodStatusbarBounds = scene != null && scene.isAod()
|
Bounds aodStatusbarBounds = scene != null && scene.isAod()
|
||||||
? aodStatusbarBounds(root, settings, aodNotificationBounds, aodStatusAnchorBounds)
|
? aodStatusbarBounds(root, settings, aodNotificationBounds, aodStatusAnchorBounds)
|
||||||
: null;
|
: null;
|
||||||
|
LayoutEdges edges = layoutEdges(root, settings, anchors, scene, aodStatusAnchorBounds);
|
||||||
int statusbarHeight = statusbarHeight(root, anchors, scene, aodStatusContentBounds);
|
int statusbarHeight = statusbarHeight(root, anchors, scene, aodStatusContentBounds);
|
||||||
if (clockEnabledForScene(settings, scene) && clockLayout != null && clockLayout.width() > 0) {
|
if (clockEnabledForScene(settings, scene) && clockLayout != null && clockLayout.width() > 0) {
|
||||||
bands.add(UnlockedLayoutBand.clock(
|
bands.add(UnlockedLayoutBand.clock(
|
||||||
@@ -95,7 +96,14 @@ final class UnlockedLayoutPlanner {
|
|||||||
positionFor(settings.clockPosition),
|
positionFor(settings.clockPosition),
|
||||||
sideFor(settings.clockMiddleCutoutSide)));
|
sideFor(settings.clockMiddleCutoutSide)));
|
||||||
}
|
}
|
||||||
ClockLayout carrierLayout = carrierLayout(root, collectedIcons, settings, scene);
|
ClockLayout carrierLayout = carrierLayout(
|
||||||
|
root,
|
||||||
|
collectedIcons,
|
||||||
|
settings,
|
||||||
|
scene,
|
||||||
|
edges,
|
||||||
|
layoutCutoutBounds,
|
||||||
|
clockLayout);
|
||||||
if (carrierLayout != null && carrierLayout.width() > 0) {
|
if (carrierLayout != null && carrierLayout.width() > 0) {
|
||||||
bands.add(UnlockedLayoutBand.carrier(
|
bands.add(UnlockedLayoutBand.carrier(
|
||||||
carrierHost,
|
carrierHost,
|
||||||
@@ -257,7 +265,6 @@ final class UnlockedLayoutPlanner {
|
|||||||
ArrayList<LayoutItemKind> itemOrder = orderedKinds(settings.layoutItemOrder);
|
ArrayList<LayoutItemKind> itemOrder = orderedKinds(settings.layoutItemOrder);
|
||||||
splitClockIfNeeded(root, bands, itemOrder, clockCutoutBounds, settings);
|
splitClockIfNeeded(root, bands, itemOrder, clockCutoutBounds, settings);
|
||||||
sortBandsForItemOrder(bands, itemOrder);
|
sortBandsForItemOrder(bands, itemOrder);
|
||||||
LayoutEdges edges = layoutEdges(root, settings, anchors, scene, aodStatusAnchorBounds);
|
|
||||||
int itemPadding = Math.max(0, settings.layoutItemPaddingPx);
|
int itemPadding = Math.max(0, settings.layoutItemPaddingPx);
|
||||||
solveWithPackedLayout(root, edges, bands, layoutCutoutBounds, settings, itemPadding, scene);
|
solveWithPackedLayout(root, edges, bands, layoutCutoutBounds, settings, itemPadding, scene);
|
||||||
|
|
||||||
@@ -634,7 +641,10 @@ final class UnlockedLayoutPlanner {
|
|||||||
View root,
|
View root,
|
||||||
CollectedIcons collectedIcons,
|
CollectedIcons collectedIcons,
|
||||||
SbtSettings settings,
|
SbtSettings settings,
|
||||||
SceneKey scene
|
SceneKey scene,
|
||||||
|
LayoutEdges edges,
|
||||||
|
Bounds cutoutBounds,
|
||||||
|
ClockLayout clockLayout
|
||||||
) {
|
) {
|
||||||
if (root == null
|
if (root == null
|
||||||
|| collectedIcons == null
|
|| collectedIcons == null
|
||||||
@@ -666,7 +676,96 @@ final class UnlockedLayoutPlanner {
|
|||||||
return ClockLayout.simpleText(
|
return ClockLayout.simpleText(
|
||||||
collectedIcons.carrierView,
|
collectedIcons.carrierView,
|
||||||
positionFor(settings.layoutCarrierPosition),
|
positionFor(settings.layoutCarrierPosition),
|
||||||
anchorBounds.top + baseline - verticalOffset);
|
anchorBounds.top + baseline - verticalOffset,
|
||||||
|
carrierMaxWidth(
|
||||||
|
root,
|
||||||
|
settings,
|
||||||
|
edges,
|
||||||
|
cutoutBounds,
|
||||||
|
positionFor(settings.layoutCarrierPosition),
|
||||||
|
sideFor(settings.layoutCarrierMiddleSide),
|
||||||
|
clockLayout));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int carrierMaxWidth(
|
||||||
|
View root,
|
||||||
|
SbtSettings settings,
|
||||||
|
LayoutEdges edges,
|
||||||
|
Bounds cutoutBounds,
|
||||||
|
LayoutPosition position,
|
||||||
|
AnchorSide middleSide,
|
||||||
|
ClockLayout clockLayout
|
||||||
|
) {
|
||||||
|
int rootWidth = root != null ? root.getWidth() : 0;
|
||||||
|
if (rootWidth <= 0 || edges == null) {
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
int left = Math.max(0, edges.left());
|
||||||
|
int right = Math.min(rootWidth, edges.right());
|
||||||
|
if (right <= left) {
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
boolean useRightSide = position == LayoutPosition.RIGHT
|
||||||
|
|| position == LayoutPosition.MIDDLE && middleSide == AnchorSide.RIGHT;
|
||||||
|
boolean usableCutout = cutoutBounds != null
|
||||||
|
&& cutoutBounds.width > 0
|
||||||
|
&& cutoutBounds.left > left
|
||||||
|
&& cutoutBounds.left < right;
|
||||||
|
if (useRightSide) {
|
||||||
|
int minLeft = usableCutout
|
||||||
|
? Math.min(right, cutoutBounds.left + cutoutBounds.width)
|
||||||
|
: left;
|
||||||
|
return carrierMaxWidthWithClockReserve(
|
||||||
|
Math.max(1, right - minLeft),
|
||||||
|
settings,
|
||||||
|
position,
|
||||||
|
middleSide,
|
||||||
|
clockLayout,
|
||||||
|
AnchorSide.RIGHT);
|
||||||
|
}
|
||||||
|
int maxRight = usableCutout ? Math.max(left, cutoutBounds.left) : right;
|
||||||
|
return carrierMaxWidthWithClockReserve(
|
||||||
|
Math.max(1, maxRight - left),
|
||||||
|
settings,
|
||||||
|
position,
|
||||||
|
middleSide,
|
||||||
|
clockLayout,
|
||||||
|
AnchorSide.LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int carrierMaxWidthWithClockReserve(
|
||||||
|
int sideWidth,
|
||||||
|
SbtSettings settings,
|
||||||
|
LayoutPosition carrierPosition,
|
||||||
|
AnchorSide carrierMiddleSide,
|
||||||
|
ClockLayout clockLayout,
|
||||||
|
AnchorSide side
|
||||||
|
) {
|
||||||
|
if (sideWidth <= 0 || settings == null || clockLayout == null || clockLayout.width() <= 0) {
|
||||||
|
return Math.max(1, sideWidth);
|
||||||
|
}
|
||||||
|
LayoutPosition clockPosition = positionFor(settings.clockPosition);
|
||||||
|
AnchorSide clockMiddleSide = sideFor(settings.clockMiddleCutoutSide);
|
||||||
|
if (!occupiesSide(clockPosition, clockMiddleSide, side)
|
||||||
|
|| !occupiesSide(carrierPosition, carrierMiddleSide, side)) {
|
||||||
|
return Math.max(1, sideWidth);
|
||||||
|
}
|
||||||
|
int reserved = clockLayout.width() + Math.max(0, settings.layoutItemPaddingPx);
|
||||||
|
return Math.max(1, sideWidth - reserved);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean occupiesSide(
|
||||||
|
LayoutPosition position,
|
||||||
|
AnchorSide middleSide,
|
||||||
|
AnchorSide side
|
||||||
|
) {
|
||||||
|
if (position == LayoutPosition.LEFT) {
|
||||||
|
return side == AnchorSide.LEFT;
|
||||||
|
}
|
||||||
|
if (position == LayoutPosition.RIGHT) {
|
||||||
|
return side == AnchorSide.RIGHT;
|
||||||
|
}
|
||||||
|
return middleSide == side;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int statusCountForScene(SbtSettings settings, SceneKey scene) {
|
private int statusCountForScene(SbtSettings settings, SceneKey scene) {
|
||||||
@@ -1539,7 +1638,12 @@ final class UnlockedLayoutPlanner {
|
|||||||
row.segment));
|
row.segment));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ClockPlacement(first.source, first.contentDescription, rows, union);
|
return new ClockPlacement(
|
||||||
|
first.source,
|
||||||
|
first.contentDescription,
|
||||||
|
rows,
|
||||||
|
union,
|
||||||
|
first.textColorOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bounds cutoutBounds(Rect cutout, int cutoutGap) {
|
static Bounds cutoutBounds(Rect cutout, int cutoutGap) {
|
||||||
|
|||||||
Reference in New Issue
Block a user