Optimize statusbar rendering paths
This commit is contained in:
+320
-45
@@ -1,5 +1,6 @@
|
||||
package se.ajpanton.statusbartweak.runtime.layout;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -22,6 +23,7 @@ import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
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.NotificationUnreadTracker;
|
||||
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.RenderResult;
|
||||
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> settingsIdentityByRoot = 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, Integer> aodCardsNotificationRenderGenerationByRoot = new WeakHashMap<>();
|
||||
private final Map<View, AodSourceGeometry> aodSourceGeometryByView = new WeakHashMap<>();
|
||||
private final Map<View, Integer> aodNotificationWidgetSteadyWidths = new WeakHashMap<>();
|
||||
private final Map<View, Integer> aodNotificationIconOnlyAreaSteadyLefts = 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 =
|
||||
Collections.newSetFromMap(new WeakHashMap<>());
|
||||
private final Map<View, Float> aodPluginVisualAlphaByView = new WeakHashMap<>();
|
||||
@@ -440,6 +450,12 @@ final class StockLayoutCanvasController {
|
||||
if (darkIconDispatcherClass != null) {
|
||||
darkIconDispatcherGuard.install(darkIconDispatcherClass);
|
||||
}
|
||||
Class<?> lightBarTransitionsControllerClass = ReflectionSupport.findClassIfExists(
|
||||
"com.android.systemui.statusbar.phone.LightBarTransitionsController",
|
||||
classLoader);
|
||||
if (lightBarTransitionsControllerClass != null) {
|
||||
hookLightBarTintTarget(lightBarTransitionsControllerClass);
|
||||
}
|
||||
|
||||
Class<?> statusBarIconViewClass = ReflectionSupport.findClassIfExists(
|
||||
"com.android.systemui.statusbar.StatusBarIconView",
|
||||
@@ -540,6 +556,32 @@ final class StockLayoutCanvasController {
|
||||
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) {
|
||||
XposedHookSupport.hookAllMethodsIfExists(
|
||||
runtimeContext.getFramework(),
|
||||
@@ -857,6 +899,8 @@ final class StockLayoutCanvasController {
|
||||
if (notifications.isEmpty()) {
|
||||
latestAodNotifications = new ArrayList<>();
|
||||
latestAodIconImageViews = new ArrayList<>();
|
||||
notificationFingerprintByCacheKey.clear();
|
||||
viewFingerprintCacheByImageView.clear();
|
||||
NotificationActiveKeyStore.replace(Collections.emptySet());
|
||||
NotificationUnreadTracker.retainActiveKeys(Collections.emptySet());
|
||||
LockscreenCardsNotificationIconSourceStore.clearAodSources();
|
||||
@@ -880,6 +924,7 @@ final class StockLayoutCanvasController {
|
||||
changed |= NotificationUnreadTracker.retainActiveKeys(activeKeys);
|
||||
LockscreenCardsNotificationIconSourceStore.putAodNotificationPackages(packages);
|
||||
latestAodNotifications = notifications;
|
||||
pruneNotificationFingerprintCache(notifications);
|
||||
if (changed) {
|
||||
markKnownAodNotificationRootsDirty();
|
||||
}
|
||||
@@ -891,38 +936,95 @@ final class StockLayoutCanvasController {
|
||||
if (collection == null) {
|
||||
return notifications;
|
||||
}
|
||||
for (String methodName : NOTIF_COLLECTION_METHOD_NAMES) {
|
||||
Object value = ReflectionSupport.invokeMethod(collection, methodName);
|
||||
appendNotificationsFromNotifCollectionValue(notifications, value);
|
||||
for (NotifCollectionAccessor accessor : notifCollectionAccessors(collection.getClass())) {
|
||||
appendNotificationsFromNotifCollectionValue(notifications, accessor.get(collection));
|
||||
if (!notifications.isEmpty()) {
|
||||
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;
|
||||
}
|
||||
|
||||
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(
|
||||
ArrayList<StatusBarNotification> notifications,
|
||||
Object value
|
||||
@@ -1185,7 +1287,7 @@ final class StockLayoutCanvasController {
|
||||
if (hasVisibleConfirmedAodPluginView()) {
|
||||
hideConfirmedAodPluginViews();
|
||||
}
|
||||
removeLockscreenCardsNotificationHost(root);
|
||||
hideLockscreenCardsNotificationHost(root);
|
||||
if (activeScene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS
|
||||
&& isAodLayoutRoot(root)) {
|
||||
renderAodCardsNotificationStripIfNeeded(root, settings, activeScene);
|
||||
@@ -1229,8 +1331,8 @@ final class StockLayoutCanvasController {
|
||||
if (activeScene != null && activeScene.isUnlocked()) {
|
||||
if (isKnownRootGeometryChanged(root)) {
|
||||
ownedIconHostManager.setUnlockedHostsVisible(root, false);
|
||||
removeDebugOverlay(root);
|
||||
}
|
||||
removeDebugOverlay(root);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1515,8 +1617,12 @@ final class StockLayoutCanvasController {
|
||||
}
|
||||
|
||||
private void scheduleUnlockedAppearanceRefreshForAllRoots() {
|
||||
scheduleUnlockedAppearanceRefreshForAllRoots(false);
|
||||
}
|
||||
|
||||
private void scheduleUnlockedAppearanceRefreshForAllRoots(boolean highPriority) {
|
||||
for (View root : new ArrayList<>(unlockedHostsByRoot.keySet())) {
|
||||
scheduleUnlockedAppearanceRefreshForRoot(root);
|
||||
scheduleUnlockedAppearanceRefreshForRoot(root, highPriority);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1586,16 +1692,29 @@ final class StockLayoutCanvasController {
|
||||
}
|
||||
|
||||
private void scheduleUnlockedAppearanceRefreshForRoot(View root) {
|
||||
scheduleUnlockedAppearanceRefreshForRoot(root, false);
|
||||
}
|
||||
|
||||
private void scheduleUnlockedAppearanceRefreshForRoot(View root, boolean highPriority) {
|
||||
if (root == null
|
||||
|| !root.isAttachedToWindow()
|
||||
|| !isUnlockedStatusBarRoot(root)
|
||||
|| !pendingUnlockedAppearanceRefreshRoots.add(root)) {
|
||||
|| !isUnlockedStatusBarRoot(root)) {
|
||||
return;
|
||||
}
|
||||
root.post(() -> {
|
||||
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
||||
if (!pendingUnlockedAppearanceRefreshRoots.add(root) && !highPriority) {
|
||||
return;
|
||||
}
|
||||
Runnable refresh = () -> {
|
||||
if (!pendingUnlockedAppearanceRefreshRoots.remove(root)) {
|
||||
return;
|
||||
}
|
||||
refreshUnlockedAppearanceNow(root);
|
||||
});
|
||||
};
|
||||
if (highPriority && root.getHandler() != null) {
|
||||
root.getHandler().postAtFrontOfQueue(refresh);
|
||||
} else {
|
||||
root.post(refresh);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean clockTextRefreshEnabledForScene(SbtSettings settings, SceneKey scene) {
|
||||
@@ -1762,13 +1881,15 @@ final class StockLayoutCanvasController {
|
||||
return;
|
||||
}
|
||||
if (!layoutRoot) {
|
||||
removeSharedLayoutHosts(root);
|
||||
if (!unlockedScene && canContainLockedStatusBarIconSurfaces(root)) {
|
||||
if (activeScene != null
|
||||
&& activeScene.isLockscreen()
|
||||
&& canContainLockedStatusBarIconSurfaces(root)) {
|
||||
removeUnlockedLayoutHosts(root);
|
||||
removeAodCardsNotificationHost(root);
|
||||
renderLockscreenCardsNotificationStripIfNeeded(root, settings, activeScene);
|
||||
hideOrDiscoverLockedStatusBarIconSurfaces(root);
|
||||
} else {
|
||||
removeLockscreenCardsNotificationHost(root);
|
||||
removeAodCardsNotificationHost(root);
|
||||
removeSharedLayoutHosts(root);
|
||||
}
|
||||
removeDebugOverlay(root);
|
||||
return;
|
||||
@@ -2562,8 +2683,7 @@ final class StockLayoutCanvasController {
|
||||
// writes conflicting values both while tracking expansion and when it clears
|
||||
// its state after collapse.
|
||||
return !runtimeContext.getModeStateRepository().isSystemUiShadeLocked()
|
||||
&& (stackContains("LegacyQsExpandAnimator", "setQsExpansionPosition")
|
||||
|| stackContains("LegacyQsExpandAnimator", "clearAnimationState"));
|
||||
&& stackContainsConflictingShadeHeaderAnimator();
|
||||
}
|
||||
|
||||
private boolean isSplitShadeStatusBarCandidate(View view) {
|
||||
@@ -2579,16 +2699,18 @@ final class StockLayoutCanvasController {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private boolean stackContains(String classNamePart, String methodName) {
|
||||
private boolean stackContainsConflictingShadeHeaderAnimator() {
|
||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||||
for (StackTraceElement element : stack) {
|
||||
if (element == null) {
|
||||
continue;
|
||||
}
|
||||
String className = element.getClassName();
|
||||
String methodName = element.getMethodName();
|
||||
if (className != null
|
||||
&& className.contains(classNamePart)
|
||||
&& methodName.equals(element.getMethodName())) {
|
||||
&& className.contains("LegacyQsExpandAnimator")
|
||||
&& ("setQsExpansionPosition".equals(methodName)
|
||||
|| "clearAnimationState".equals(methodName))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2966,6 +3088,7 @@ final class StockLayoutCanvasController {
|
||||
dirtyUnlockedLayoutRoots.remove(root);
|
||||
settingsIdentityByRoot.remove(root);
|
||||
lockscreenCardsRowFilterSignatures.remove(root);
|
||||
lockscreenCardsNotificationRenderGenerationByRoot.remove(root);
|
||||
clockTimeBucketByRoot.remove(root);
|
||||
removeDebugOverlay(root);
|
||||
root.requestLayout();
|
||||
@@ -3190,6 +3313,7 @@ final class StockLayoutCanvasController {
|
||||
ArrayList<ImageView> imageViews = imageViewsFromArgs(args);
|
||||
if (!imageViews.isEmpty()) {
|
||||
latestAodIconImageViews = imageViews;
|
||||
pruneViewFingerprintCache(imageViews);
|
||||
if (!runtimeContext.getModeStateRepository().isLayoutEnabled()) {
|
||||
return;
|
||||
}
|
||||
@@ -3201,6 +3325,10 @@ final class StockLayoutCanvasController {
|
||||
if (!(containerView instanceof ViewGroup container)) {
|
||||
return;
|
||||
}
|
||||
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||
if (activeScene == null || activeScene.isUnlocked()) {
|
||||
return;
|
||||
}
|
||||
if (!isLayoutEnabled(container.getContext())) {
|
||||
knownAodNotificationContainers.remove(container);
|
||||
LockscreenCardsNotificationIconSourceStore.clearAodSources(container);
|
||||
@@ -3218,6 +3346,10 @@ final class StockLayoutCanvasController {
|
||||
if (knownAodNotificationContainers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||
if (activeScene == null || activeScene.isUnlocked()) {
|
||||
return;
|
||||
}
|
||||
for (ViewGroup container : knownAodNotificationContainers) {
|
||||
if (container == null || !container.isAttachedToWindow()) {
|
||||
continue;
|
||||
@@ -3345,9 +3477,18 @@ final class StockLayoutCanvasController {
|
||||
|| !AppIconRules.isValidPackageName(sbn.getPackageName())) {
|
||||
return null;
|
||||
}
|
||||
String cacheKey = notificationFingerprintCacheKey(sbn);
|
||||
IconFingerprint cached = notificationFingerprintByCacheKey.get(cacheKey);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
@@ -3357,7 +3498,57 @@ final class StockLayoutCanvasController {
|
||||
if (imageView == null || imageView.getDrawable() == 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) {
|
||||
@@ -3708,6 +3899,19 @@ final class StockLayoutCanvasController {
|
||||
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) {
|
||||
View current = view;
|
||||
while (current != null) {
|
||||
@@ -3768,13 +3972,37 @@ final class StockLayoutCanvasController {
|
||||
|| activeScene == null
|
||||
|| !activeScene.isLockscreen()
|
||||
|| activeScene.notificationDisplayStyle() != NotificationDisplayStyle.CARDS
|
||||
|| !isNotificationShadeWindowRoot(root)
|
||||
|| suppressForTransition) {
|
||||
removeLockscreenCardsNotificationHost(root);
|
||||
lockscreenCardsNotificationRenderGenerationByRoot.remove(root);
|
||||
return;
|
||||
}
|
||||
View host = ownedIconHostManager.ensureLockscreenCardsNotificationHost(root);
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -3933,10 +4161,27 @@ final class StockLayoutCanvasController {
|
||||
|| 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) {
|
||||
View host = root instanceof ViewGroup group
|
||||
? findDirectOwnedHost(group, OwnedIconHostManager.TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST)
|
||||
: null;
|
||||
if (host == null) {
|
||||
return;
|
||||
}
|
||||
lockscreenCardsNotificationRenderGenerationByRoot.remove(root);
|
||||
if (host instanceof ViewGroup hostGroup) {
|
||||
lockscreenCardsNotificationStripRenderer.clear(hostGroup);
|
||||
}
|
||||
@@ -4136,6 +4381,33 @@ final class StockLayoutCanvasController {
|
||||
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 {
|
||||
final int visibility;
|
||||
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) {
|
||||
int distance(IconFingerprint other) {
|
||||
if (other == null || other.mask == null || mask == null || other.mask.length != mask.length) {
|
||||
|
||||
@@ -128,6 +128,15 @@ final class ClockLayout {
|
||||
TextView source,
|
||||
LayoutPosition position,
|
||||
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) {
|
||||
return null;
|
||||
@@ -141,12 +150,13 @@ final class ClockLayout {
|
||||
if (measured.width <= 0 || measured.height <= 0) {
|
||||
return null;
|
||||
}
|
||||
int width = Math.max(1, Math.min(measured.width, maxWidth));
|
||||
int top = sourceBaselineY - measured.baseline;
|
||||
ClockRow row = new ClockRow(
|
||||
source.getText(),
|
||||
0,
|
||||
0,
|
||||
measured.width,
|
||||
width,
|
||||
measured.height,
|
||||
0,
|
||||
0,
|
||||
@@ -161,7 +171,7 @@ final class ClockLayout {
|
||||
top,
|
||||
0,
|
||||
false,
|
||||
measured.width,
|
||||
width,
|
||||
measured.height);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package se.ajpanton.statusbartweak.runtime.render;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.View;
|
||||
@@ -8,9 +9,12 @@ import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import se.ajpanton.statusbartweak.runtime.ViewIdNames;
|
||||
|
||||
final class ClockProxyRenderer {
|
||||
private final WeakHashMap<ViewGroup, ArrayList<TextView>> rowsByHost = new WeakHashMap<>();
|
||||
|
||||
@@ -50,6 +54,12 @@ final class ClockProxyRenderer {
|
||||
if (copyTextStyleIfChanged(placement.source, view)) {
|
||||
hostChanged = true;
|
||||
}
|
||||
if (applyTextColorOverrideIfNeeded(view, placement.textColorOverride)) {
|
||||
hostChanged = true;
|
||||
}
|
||||
if (applyCarrierMarqueeIfNeeded(placement.source, view)) {
|
||||
hostChanged = true;
|
||||
}
|
||||
if (!sameTextAndColorSpans(view.getText(), row.text)) {
|
||||
view.setText(row.text);
|
||||
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) {
|
||||
if (!String.valueOf(current).contentEquals(String.valueOf(desired))) {
|
||||
return false;
|
||||
@@ -189,6 +228,17 @@ final class ClockProxyRenderer {
|
||||
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(
|
||||
ViewGroup host,
|
||||
TextView view,
|
||||
|
||||
@@ -11,17 +11,29 @@ final class ClockPlacement {
|
||||
final String contentDescription;
|
||||
final ArrayList<ClockRow> rows;
|
||||
final Bounds bounds;
|
||||
final int textColorOverride;
|
||||
|
||||
ClockPlacement(
|
||||
TextView source,
|
||||
String contentDescription,
|
||||
ArrayList<ClockRow> rows,
|
||||
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.contentDescription = contentDescription;
|
||||
this.rows = rows;
|
||||
this.bounds = bounds;
|
||||
this.textColorOverride = textColorOverride;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+29
@@ -257,6 +257,35 @@ public final class LockscreenCardsNotificationIconSourceStore {
|
||||
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) {
|
||||
ArrayList<SnapshotSource> result = new ArrayList<>();
|
||||
ViewGroup container = sourceContainerForAodIconsRoot(root);
|
||||
|
||||
@@ -180,7 +180,8 @@ record SnapshotRenderState(
|
||||
String heldSignalKey,
|
||||
int heldBitmapId,
|
||||
SnapshotMode sourceMode,
|
||||
int sourceSignature
|
||||
int sourceSignature,
|
||||
int tintSignature
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -45,9 +46,12 @@ final class SnapshotRenderer {
|
||||
new WeakHashMap<>();
|
||||
private final WeakHashMap<View, SourceSignatureCache> sourceSignaturesByView =
|
||||
new WeakHashMap<>();
|
||||
private final WeakHashMap<View, ContentBoundsCache> contentBoundsByView =
|
||||
new WeakHashMap<>();
|
||||
private final LinkedHashMap<String, HeldSignal> heldSignals = new LinkedHashMap<>();
|
||||
|
||||
private long holdSignalsUntilElapsedMs;
|
||||
private boolean statusBarTintTargetEnabled;
|
||||
|
||||
SnapshotRenderer(
|
||||
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(
|
||||
View root,
|
||||
ArrayList<SnapshotSource> sources
|
||||
@@ -78,6 +90,7 @@ final class SnapshotRenderer {
|
||||
boolean sortByX
|
||||
) {
|
||||
ArrayList<SnapshotPlacement> placements = new ArrayList<>();
|
||||
LinkedHashMap<String, Integer> keyCounts = new LinkedHashMap<>();
|
||||
int order = 0;
|
||||
for (SnapshotSource source : sources) {
|
||||
Bounds bounds = source.boundsOverride() != null
|
||||
@@ -87,7 +100,7 @@ final class SnapshotRenderer {
|
||||
if (source.mode() == SnapshotMode.NOTIFICATION_DRAWABLE) {
|
||||
bounds = notificationDrawableBounds(bounds);
|
||||
}
|
||||
String key = normalizedKeyFor(source, order);
|
||||
String key = uniqueKey(normalizedKeyFor(source, order), keyCounts);
|
||||
boolean signal = isSignalKey(key);
|
||||
boolean trimContent = !signal && shouldTrimContent(source);
|
||||
Bounds contentBounds = trimContent ? snapshotContentBounds(source, bounds) : null;
|
||||
@@ -138,14 +151,44 @@ final class SnapshotRenderer {
|
||||
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) {
|
||||
if (source == null || bounds == null || bounds.width <= 0 || bounds.height <= 0) {
|
||||
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);
|
||||
try {
|
||||
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 {
|
||||
bitmap.recycle();
|
||||
}
|
||||
@@ -204,6 +247,8 @@ final class SnapshotRenderer {
|
||||
drawAppIcon(host.getContext(), placement.source(), bitmap);
|
||||
} else if (placement.source() != null) {
|
||||
drawSnapshot(placement, bitmap);
|
||||
applyStatusBarTintTarget(placement.source(), bitmap);
|
||||
normalizeSemiTransparentDarkIcon(bitmap);
|
||||
if (placement.signal()) {
|
||||
saveHeldSignal(key, bounds, bitmap);
|
||||
}
|
||||
@@ -516,7 +561,8 @@ final class SnapshotRenderer {
|
||||
placement.heldSignal() != null ? placement.heldSignal().key : "",
|
||||
placement.heldSignal() != null ? System.identityHashCode(placement.heldSignal().bitmap) : 0,
|
||||
placement.source() != null ? placement.source().mode() : null,
|
||||
placement.source() != null ? sourceSignature(placement.source()) : 0);
|
||||
placement.source() != null ? sourceSignature(placement.source()) : 0,
|
||||
tintSignatureFor(placement.source()));
|
||||
}
|
||||
|
||||
private int sourceSignature(SnapshotSource source) {
|
||||
@@ -527,9 +573,14 @@ final class SnapshotRenderer {
|
||||
result = 31 * result + sourceTreeSignature(source.view());
|
||||
Bounds boundsOverride = source.boundsOverride();
|
||||
result = 31 * result + (boundsOverride != null ? boundsOverride.signature() : 0);
|
||||
result = 31 * result + tintSignatureFor(source);
|
||||
return result;
|
||||
}
|
||||
|
||||
private int tintSignatureFor(SnapshotSource source) {
|
||||
return shouldApplyStatusBarTintTarget(source) ? StatusBarTintTarget.signature() : 0;
|
||||
}
|
||||
|
||||
private boolean isStatusBarIconView(View view) {
|
||||
return view != null && view.getClass().getName().contains("StatusBarIconView");
|
||||
}
|
||||
@@ -626,6 +677,15 @@ final class SnapshotRenderer {
|
||||
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) {
|
||||
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(
|
||||
SnapshotPlacement placement,
|
||||
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> lastClockAppearanceByRoot = new WeakHashMap<>();
|
||||
private final WeakHashMap<View, Boolean> forcedChipRefreshByRoot = new WeakHashMap<>();
|
||||
private boolean statusBarTintTargetEnabled;
|
||||
|
||||
public UnlockedIconSnapshotRenderController() {
|
||||
sourceCollector = new UnlockedStockSourceCollector();
|
||||
@@ -75,6 +76,7 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
return new RenderResult(true, true, true);
|
||||
}
|
||||
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
||||
setStatusBarTintTargetEnabled(scene.isUnlocked());
|
||||
layoutHostToRoot(root, clockHost);
|
||||
layoutHostToRoot(root, carrierHost);
|
||||
layoutHostToRoot(root, chipHost);
|
||||
@@ -209,6 +211,7 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
lastClockGeometryByRoot.put(root, currentClockGeometry != null ? currentClockGeometry : 0);
|
||||
lastClockSourceGeometryByRoot.put(root, currentClockSourceGeometry);
|
||||
layoutPlan = withCurrentDotColors(layoutPlan, collectedIcons);
|
||||
layoutPlan = withCurrentClockAppearance(root, layoutPlan, collectedIcons, settings);
|
||||
lastLayoutPlanByRoot.put(root, layoutPlan);
|
||||
if (clockEnabledForScene(settings, scene) && layoutPlan.clockPlacement != null) {
|
||||
clockRenderer.render(clockHost, layoutPlan.clockPlacement);
|
||||
@@ -257,6 +260,7 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
return;
|
||||
}
|
||||
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
||||
setStatusBarTintTargetEnabled(true);
|
||||
CollectedIcons collectedIcons = lastCollectedIconsByRoot.get(root);
|
||||
LayoutPlan updatedPlan = withCurrentDotColors(layoutPlan, collectedIcons);
|
||||
if (updatedPlan != layoutPlan) {
|
||||
@@ -304,6 +308,7 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
if (!settings.layoutChipEnabledUnlocked) {
|
||||
return false;
|
||||
}
|
||||
setStatusBarTintTargetEnabled(true);
|
||||
chipRenderer.renderPlacements(chipHost, layoutPlan.chipPlacements);
|
||||
return true;
|
||||
}
|
||||
@@ -325,6 +330,7 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
return false;
|
||||
}
|
||||
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
||||
setStatusBarTintTargetEnabled(scene.isUnlocked());
|
||||
if (!clockEnabledForScene(settings, scene)) {
|
||||
return false;
|
||||
}
|
||||
@@ -504,6 +510,10 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
}
|
||||
|
||||
private int sourceColor(SnapshotSource source) {
|
||||
int targetColor = statusBarTintTargetColor();
|
||||
if (hasAlpha(targetColor)) {
|
||||
return targetColor;
|
||||
}
|
||||
if (source == null || source.view() == null) {
|
||||
return 0;
|
||||
}
|
||||
@@ -545,6 +555,10 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
}
|
||||
|
||||
private int clockReferenceColor(ClockPlacement placement, CollectedIcons icons) {
|
||||
int targetColor = statusBarTintTargetColor();
|
||||
if (hasAlpha(targetColor)) {
|
||||
return targetColor;
|
||||
}
|
||||
if (icons != null && icons.statusIcons != null) {
|
||||
for (SnapshotSource source : icons.statusIcons) {
|
||||
int color = sourceColor(source);
|
||||
@@ -567,6 +581,21 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
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(
|
||||
ClockPlacement placement,
|
||||
int referenceColor,
|
||||
@@ -574,6 +603,7 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
) {
|
||||
int result = 17;
|
||||
result = 31 * result + referenceColor;
|
||||
result = 31 * result + (placement != null ? placement.textColorOverride : 0);
|
||||
result = 31 * result + clockTextSettingsSignature(settings);
|
||||
if (placement != null && placement.source != null) {
|
||||
result = 31 * result + placement.source.getCurrentTextColor();
|
||||
@@ -839,7 +869,8 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
source,
|
||||
model.contentDescription,
|
||||
rows,
|
||||
previousPlacement.bounds);
|
||||
previousPlacement.bounds,
|
||||
statusBarTintTargetColor());
|
||||
}
|
||||
|
||||
private Integer clockSourceGeometrySignature(TextView source) {
|
||||
@@ -894,7 +925,12 @@ public final class UnlockedIconSnapshotRenderController {
|
||||
previousRow.rowIndex,
|
||||
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) {
|
||||
|
||||
+10
-1
@@ -558,6 +558,10 @@ final class UnlockedLayoutBand {
|
||||
}
|
||||
|
||||
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) {
|
||||
return 0;
|
||||
}
|
||||
@@ -567,6 +571,10 @@ final class UnlockedLayoutBand {
|
||||
return android.graphics.Color.alpha(color) != 0 ? color : 0;
|
||||
}
|
||||
|
||||
private boolean hasAlpha(int color) {
|
||||
return (color >>> 24) != 0;
|
||||
}
|
||||
|
||||
private SnapshotPlacement emptyIconContainerPlacement() {
|
||||
int height = Math.max(1, dotHeight(activePlacements()));
|
||||
return new SnapshotPlacement(
|
||||
@@ -699,7 +707,8 @@ final class UnlockedLayoutBand {
|
||||
placement.source,
|
||||
placement.contentDescription,
|
||||
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() {
|
||||
|
||||
+109
-5
@@ -87,6 +87,7 @@ final class UnlockedLayoutPlanner {
|
||||
Bounds aodStatusbarBounds = scene != null && scene.isAod()
|
||||
? aodStatusbarBounds(root, settings, aodNotificationBounds, aodStatusAnchorBounds)
|
||||
: null;
|
||||
LayoutEdges edges = layoutEdges(root, settings, anchors, scene, aodStatusAnchorBounds);
|
||||
int statusbarHeight = statusbarHeight(root, anchors, scene, aodStatusContentBounds);
|
||||
if (clockEnabledForScene(settings, scene) && clockLayout != null && clockLayout.width() > 0) {
|
||||
bands.add(UnlockedLayoutBand.clock(
|
||||
@@ -95,7 +96,14 @@ final class UnlockedLayoutPlanner {
|
||||
positionFor(settings.clockPosition),
|
||||
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) {
|
||||
bands.add(UnlockedLayoutBand.carrier(
|
||||
carrierHost,
|
||||
@@ -257,7 +265,6 @@ final class UnlockedLayoutPlanner {
|
||||
ArrayList<LayoutItemKind> itemOrder = orderedKinds(settings.layoutItemOrder);
|
||||
splitClockIfNeeded(root, bands, itemOrder, clockCutoutBounds, settings);
|
||||
sortBandsForItemOrder(bands, itemOrder);
|
||||
LayoutEdges edges = layoutEdges(root, settings, anchors, scene, aodStatusAnchorBounds);
|
||||
int itemPadding = Math.max(0, settings.layoutItemPaddingPx);
|
||||
solveWithPackedLayout(root, edges, bands, layoutCutoutBounds, settings, itemPadding, scene);
|
||||
|
||||
@@ -634,7 +641,10 @@ final class UnlockedLayoutPlanner {
|
||||
View root,
|
||||
CollectedIcons collectedIcons,
|
||||
SbtSettings settings,
|
||||
SceneKey scene
|
||||
SceneKey scene,
|
||||
LayoutEdges edges,
|
||||
Bounds cutoutBounds,
|
||||
ClockLayout clockLayout
|
||||
) {
|
||||
if (root == null
|
||||
|| collectedIcons == null
|
||||
@@ -666,7 +676,96 @@ final class UnlockedLayoutPlanner {
|
||||
return ClockLayout.simpleText(
|
||||
collectedIcons.carrierView,
|
||||
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) {
|
||||
@@ -1539,7 +1638,12 @@ final class UnlockedLayoutPlanner {
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user