From ab28b7d8a39ca77dd1b67ebda2d2ff94a7feb404 Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Wed, 10 Jun 2026 12:16:32 +0000 Subject: [PATCH] Refine runtime layout transitions --- .../battery/BatteryBarController.java | 24 ++ .../clock/DrawerClockTextController.java | 82 +++++- .../features/clock/StockClockController.java | 26 +- .../LockscreenCardsNotificationRowFilter.java | 22 ++ .../layout/StockLayoutCanvasController.java | 252 ++++++++++++++++-- .../runtime/render/ClockLayout.java | 8 +- ...creenCardsNotificationIconSourceStore.java | 119 ++++++++- .../runtime/render/SnapshotRenderer.java | 12 +- .../UnlockedIconSnapshotRenderController.java | 165 ++++++++++-- .../runtime/render/UnlockedLayoutPlanner.java | 76 +++++- 10 files changed, 695 insertions(+), 91 deletions(-) diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarController.java index f7f2dfb..d138a5f 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/battery/BatteryBarController.java @@ -57,6 +57,7 @@ final class BatteryBarController { private final Set pendingOverlayRetries = Collections.newSetFromMap(new WeakHashMap<>()); private final Map lastSignatures = new WeakHashMap<>(); private final Map lastOverlaySignatures = new WeakHashMap<>(); + private final Map lastRootTransformSignatures = new WeakHashMap<>(); private int systemBarAppearance; private int systemBarBehavior; private int systemBarRequestedVisibleTypes = WindowInsets.Type.statusBars(); @@ -171,6 +172,11 @@ final class BatteryBarController { } private void applyBatteryBarAfterRootTransform(View root) { + int transformSignature = rootTransformSignature(root); + Integer previousSignature = lastRootTransformSignatures.put(root, transformSignature); + if (previousSignature != null && previousSignature == transformSignature) { + return; + } if (isHiddenKeyguardRoot(root)) { removeBatteryBarView(root); return; @@ -255,9 +261,23 @@ final class BatteryBarController { } roots.remove(root); pendingOverlayRetries.remove(root); + lastRootTransformSignatures.remove(root); removeBatteryBarView(root); } + private int rootTransformSignature(View root) { + if (root == null) { + return 0; + } + int result = 17; + result = 31 * result + root.getVisibility(); + result = 31 * result + Float.floatToIntBits(root.getY()); + result = 31 * result + Float.floatToIntBits(root.getTranslationY()); + result = 31 * result + root.getWidth(); + result = 31 * result + root.getHeight(); + return result; + } + private void registerReceiverIfNeeded(Context context) { if (context == null || receiverRegistered) { return; @@ -340,6 +360,10 @@ final class BatteryBarController { } private void applyBatteryBar(View root) { + applyBatteryBarInternal(root); + } + + private void applyBatteryBarInternal(View root) { if (!(root instanceof ViewGroup rootGroup)) { removeBatteryBarView(root); return; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/DrawerClockTextController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/DrawerClockTextController.java index fe13806..b234fb9 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/DrawerClockTextController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/DrawerClockTextController.java @@ -24,6 +24,8 @@ import se.ajpanton.statusbartweak.runtime.ViewIdNames; import se.ajpanton.statusbartweak.runtime.features.RuntimeContext; import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport; import se.ajpanton.statusbartweak.runtime.hooks.XposedHookSupport; +import se.ajpanton.statusbartweak.runtime.mode.SceneKey; +import se.ajpanton.statusbartweak.runtime.mode.SurfaceMode; import se.ajpanton.statusbartweak.runtime.settings.BroadcastReceiverSupport; import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache; import se.ajpanton.statusbartweak.shell.settings.SbtSettings; @@ -83,6 +85,11 @@ final class DrawerClockTextController { private void scanAndApply(View root) { SbtSettings settings = RuntimeSettingsCache.get(root.getContext()); + SceneKey scene = runtimeContext.getModeStateRepository().getActiveScene(); + if (scene != null && scene.isAod()) { + restoreTrackedDescendants(root); + return; + } boolean clockEnabled = isEnabled(settings, Role.CLOCK); boolean dateEnabled = isEnabled(settings, Role.DATE); if (!clockEnabled && !dateEnabled) { @@ -95,10 +102,16 @@ final class DrawerClockTextController { registerReceiverIfNeeded(root.getContext()); untrackDisabledRoles(clockEnabled, dateEnabled); - CandidateSelection candidates = cachedCandidates(root, clockEnabled, dateEnabled); + SurfaceMode surfaceMode = scene != null ? scene.surfaceMode() : null; + if (hasValidTrackedCandidates(root, clockEnabled, dateEnabled, surfaceMode)) { + return; + } + + CandidateSelection candidates = cachedCandidates(root, clockEnabled, dateEnabled, surfaceMode); if (candidates == null) { candidates = findCandidates(root); candidateCacheByRoot.put(root, new CandidateCache( + surfaceMode, candidates.clock != null ? candidates.clock.view : null, candidates.date != null ? candidates.date.view : null)); } @@ -126,6 +139,18 @@ final class DrawerClockTextController { } } + private int restoreTrackedDescendants(View root) { + int restored = 0; + for (TextView textView : new ArrayList<>(trackedRoles.keySet())) { + if (textView == null || root == null || isDescendantOf(textView, root)) { + restore(textView); + untrack(textView); + restored++; + } + } + return restored; + } + private void untrackDisabledRoles(boolean clockEnabled, boolean dateEnabled) { for (TextView textView : new ArrayList<>(trackedRoles.keySet())) { Role role = trackedRoles.get(textView); @@ -136,6 +161,44 @@ final class DrawerClockTextController { } } + private boolean hasValidTrackedCandidates( + View root, + boolean clockEnabled, + boolean dateEnabled, + SurfaceMode surfaceMode + ) { + if (clockEnabled && !hasValidTrackedRole(root, Role.CLOCK, surfaceMode)) { + return false; + } + return !dateEnabled || hasValidTrackedRole(root, Role.DATE, surfaceMode); + } + + private boolean hasValidTrackedRole(View root, Role role, SurfaceMode surfaceMode) { + CandidateCache cache = candidateCacheByRoot.get(root); + if (cache == null || cache.surfaceMode != surfaceMode) { + return false; + } + WeakReference expectedReference = role == Role.CLOCK + ? cache.clockView + : cache.dateView; + TextView expectedView = expectedReference != null ? expectedReference.get() : null; + if (expectedView == null) { + return false; + } + for (Map.Entry entry : trackedRoles.entrySet()) { + TextView textView = entry.getKey(); + if (entry.getValue() == role + && textView == expectedView + && textView.isAttachedToWindow() + && isDescendantOf(textView, root) + && !isStatusbarClock(textView) + && ownedTexts.contains(textView)) { + return true; + } + } + return false; + } + private void track(TextView textView, Role role) { if (textView == null || role == null) { return; @@ -159,6 +222,12 @@ final class DrawerClockTextController { if (role == null) { return; } + SceneKey scene = runtimeContext.getModeStateRepository().getActiveScene(); + if (scene != null && scene.isAod()) { + restore(textView); + untrack(textView); + return; + } if (stockTextJustChanged) { captureSnapshot(textView); } @@ -416,12 +485,17 @@ final class DrawerClockTextController { private CandidateSelection cachedCandidates( View root, boolean clockEnabled, - boolean dateEnabled + boolean dateEnabled, + SurfaceMode surfaceMode ) { CandidateCache cache = candidateCacheByRoot.get(root); if (cache == null) { return null; } + if (cache.surfaceMode != surfaceMode) { + candidateCacheByRoot.remove(root); + return null; + } Candidate clock = candidateFromCache(root, cache.clockView, Role.CLOCK, clockEnabled); Candidate date = candidateFromCache(root, cache.dateView, Role.DATE, dateEnabled); if (clockEnabled && clock == null || dateEnabled && date == null) { @@ -554,10 +628,12 @@ final class DrawerClockTextController { } private static final class CandidateCache { + final SurfaceMode surfaceMode; final WeakReference clockView; final WeakReference dateView; - CandidateCache(TextView clockView, TextView dateView) { + CandidateCache(SurfaceMode surfaceMode, TextView clockView, TextView dateView) { + this.surfaceMode = surfaceMode; this.clockView = new WeakReference<>(clockView); this.dateView = new WeakReference<>(dateView); } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/StockClockController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/StockClockController.java index 5cb2450..95ad12f 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/StockClockController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/features/clock/StockClockController.java @@ -153,7 +153,7 @@ final class StockClockController { } runtimeContext.getModeStateRepository().addLayoutEnabledListener(enabled -> { if (enabled) { - removeAllRowOverlays(); + restoreAllOwnedClocks(); } else { refreshAllClocks(); } @@ -218,14 +218,18 @@ final class StockClockController { } } + private void restoreAllOwnedClocks() { + for (TextView clockView : new ArrayList<>(trackedClocks)) { + restoreOwnedClock(clockView); + } + removeAllRowOverlays(); + } + private void applyClockText(TextView clockView) { SbtSettings settings = RuntimeSettingsCache.get(clockView.getContext()); - if (!hasCustomClockTextEnabled(settings)) { - stopTicker(clockView); - if (ownedClocks.contains(clockView)) { - restoreStockText(clockView); - ownedClocks.remove(clockView); - } + boolean layoutEnabled = settings.clockEnabled; + if (layoutEnabled || !hasCustomClockTextEnabled(settings)) { + restoreOwnedClock(clockView); return; } captureCurrentStockSnapshot(clockView); @@ -255,6 +259,14 @@ final class StockClockController { } } + private void restoreOwnedClock(TextView clockView) { + stopTicker(clockView); + if (ownedClocks.contains(clockView)) { + restoreStockText(clockView); + ownedClocks.remove(clockView); + } + } + private ArrayList layoutRowsFromModel( ClockLayoutTextFactory.Model model ) { diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/LockscreenCardsNotificationRowFilter.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/LockscreenCardsNotificationRowFilter.java index b793abb..b151a39 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/LockscreenCardsNotificationRowFilter.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/LockscreenCardsNotificationRowFilter.java @@ -53,6 +53,28 @@ final class LockscreenCardsNotificationRowFilter { return changed; } + boolean applyRow(View row, boolean allowAfterKeyguardUnlock) { + if (!isNotificationRow(row)) { + return false; + } + Context context = row.getContext(); + boolean active = isActive(context, allowAfterKeyguardUnlock); + boolean changed; + if (active && shouldHideRow(context, row, allowAfterKeyguardUnlock)) { + changed = hideRow(row); + } else { + changed = restoreRow(row); + } + if (changed) { + requestLayout(row); + } + return changed; + } + + void restoreRemovedRow(View row) { + restoreRow(row); + } + void restoreAll() { if (hiddenRows.isEmpty()) { return; diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java index e662b4f..0861aba 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/layout/StockLayoutCanvasController.java @@ -138,12 +138,15 @@ final class StockLayoutCanvasController { Collections.newSetFromMap(new WeakHashMap<>()); private final Set pendingUnlockedAppearanceRefreshRoots = Collections.newSetFromMap(new WeakHashMap<>()); + private final Set pendingUnlockedClockTextRefreshRoots = + Collections.newSetFromMap(new WeakHashMap<>()); private final Set pendingDrawerAppearanceRefreshRoots = Collections.newSetFromMap(new WeakHashMap<>()); private final Set dirtyUnlockedLayoutRoots = Collections.newSetFromMap(new WeakHashMap<>()); private final Set pendingDrawerStatusChipRefreshRoots = Collections.newSetFromMap(new WeakHashMap<>()); + private final Map unlockedClockTickersByRoot = new WeakHashMap<>(); private final Set hookedPluginClassLoaders = Collections.newSetFromMap(new IdentityHashMap<>()); private final Set pendingPluginClassLoaders = @@ -479,8 +482,9 @@ final class StockLayoutCanvasController { "onViewAdded", chain -> { Object result = chain.proceed(); - if (chain.getThisObject() instanceof ViewGroup stack) { - applyLockscreenCardsNotificationRowFilter(stack, false); + List args = chain.getArgs(); + if (!args.isEmpty() && args.get(0) instanceof View row) { + applyLockscreenCardsNotificationRowFilterRow(row, false); } return result; }); @@ -490,8 +494,9 @@ final class StockLayoutCanvasController { "onViewRemoved", chain -> { Object result = chain.proceed(); - if (chain.getThisObject() instanceof ViewGroup stack) { - applyLockscreenCardsNotificationRowFilter(stack, false); + List args = chain.getArgs(); + if (!args.isEmpty() && args.get(0) instanceof View row) { + lockscreenCardsNotificationRowFilter.restoreRemovedRow(row); } return result; }); @@ -1780,6 +1785,7 @@ final class StockLayoutCanvasController { return; } SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene(); + ensureUnlockedClockTickerState(root, settings, activeScene); if (!clockTextRefreshEnabledForScene(settings, activeScene)) { return; } @@ -1804,6 +1810,80 @@ final class StockLayoutCanvasController { return System.currentTimeMillis() / divisor; } + private void ensureUnlockedClockTickerState(View root, SbtSettings settings, SceneKey scene) { + if (!clockNeedsSecondTicker(root, settings, scene)) { + stopUnlockedClockTicker(root); + return; + } + Runnable existing = unlockedClockTickersByRoot.get(root); + if (existing != null) { + return; + } + Runnable ticker = new Runnable() { + @Override + public void run() { + if (!clockNeedsSecondTicker(root, RuntimeSettingsCache.get(root.getContext()), + runtimeContext.getModeStateRepository().getActiveScene())) { + stopUnlockedClockTicker(root); + return; + } + scheduleUnlockedClockTextRefreshForRoot(root); + scheduleNextUnlockedClockTick(root, this); + } + }; + unlockedClockTickersByRoot.put(root, ticker); + scheduleNextUnlockedClockTick(root, ticker); + } + + private boolean clockNeedsSecondTicker(View root, SbtSettings settings, SceneKey scene) { + if (root == null + || !root.isAttachedToWindow() + || !isUnlockedStatusBarRoot(root) + || settings == null + || !settings.clockEnabled + || !clockTextRefreshEnabledForScene(settings, scene)) { + return false; + } + return clockUsesSeconds(settings); + } + + private boolean clockUsesSeconds(SbtSettings settings) { + if (settings == null) { + return false; + } + String pattern = settings.clockCustomFormat != null ? settings.clockCustomFormat : ""; + return settings.clockShowSeconds + || (settings.clockCustomFormatEnabled && pattern.contains("s")); + } + + private void scheduleNextUnlockedClockTick(View root, Runnable ticker) { + if (root == null || ticker == null) { + return; + } + long now = System.currentTimeMillis(); + long next = now - (now % 1000L) + 1000L; + root.postDelayed(ticker, Math.max(1L, next - now)); + } + + private void stopUnlockedClockTicker(View root) { + if (root == null) { + return; + } + Runnable ticker = unlockedClockTickersByRoot.remove(root); + if (ticker != null) { + root.removeCallbacks(ticker); + } + } + + private void clearUnlockedClockRefreshState(View root) { + if (root == null) { + return; + } + stopUnlockedClockTicker(root); + pendingUnlockedClockTextRefreshRoots.remove(root); + clockTimeBucketByRoot.remove(root); + } + private void scheduleUnlockedAppearanceRefreshForAllRoots() { scheduleUnlockedAppearanceRefreshForAllRoots(false); } @@ -1830,8 +1910,15 @@ final class StockLayoutCanvasController { if (previous == null || current == null || previous.equals(current)) { return; } - if (previous.isLockscreen() && !current.isLockscreen()) { + if (current.isUnlocked() && !previous.isUnlocked()) { darkIconDispatcherGuard.restoreForcedStates(); + trackedLockedStatusBarIconSurfaces.clear(); + } else if (previous.isLockscreen() && current.isAod()) { + darkIconDispatcherGuard.restoreForcedStates(); + hideTrackedLockedStatusBarIconSurfaces(null); + } else if (previous.isLockscreen() && !current.isLockscreen()) { + darkIconDispatcherGuard.restoreForcedStates(); + restoreTrackedLockedStatusBarIconSurfaces(null); } boolean lockscreenEntry = current.isLockscreen(); if (lockscreenEntry) { @@ -1847,6 +1934,7 @@ final class StockLayoutCanvasController { if (root == null || !root.isAttachedToWindow()) { rootGeometries.remove(root); unlockedHostsByRoot.remove(root); + clearUnlockedClockRefreshState(root); continue; } Runnable refresh = () -> refreshSceneRoot(root); @@ -1866,6 +1954,7 @@ final class StockLayoutCanvasController { if (root == null || !root.isAttachedToWindow()) { rootGeometries.remove(root); unlockedHostsByRoot.remove(root); + clearUnlockedClockRefreshState(root); return; } unlockedIconRenderController.clearRoot(root); @@ -1926,11 +2015,11 @@ final class StockLayoutCanvasController { if (root == null || !root.isAttachedToWindow() || !isUnlockedStatusBarRoot(root) - || !pendingUnlockedAppearanceRefreshRoots.add(root)) { + || !pendingUnlockedClockTextRefreshRoots.add(root)) { return; } root.post(() -> { - pendingUnlockedAppearanceRefreshRoots.remove(root); + pendingUnlockedClockTextRefreshRoots.remove(root); refreshUnlockedClockTextNow(root); }); } @@ -2077,11 +2166,19 @@ final class StockLayoutCanvasController { } if (!layoutRoot) { if (activeScene != null + && activeScene.isUnlocked() + && isNotificationShadeWindowRoot(root)) { + hideUnlockedShadeShelfIconSurfaces(root); + removeSharedLayoutHosts(root); + } else if (activeScene != null && activeScene.isLockscreen() && canContainLockedStatusBarIconSurfaces(root)) { removeUnlockedLayoutHosts(root); removeAodCardsNotificationHost(root); - renderLockscreenCardsNotificationStripIfNeeded(root, settings, activeScene); + if (activeScene.notificationDisplayStyle() != NotificationDisplayStyle.CARDS + && hasLockscreenCardsNotificationHost(root)) { + removeLockscreenCardsNotificationHost(root); + } hideOrDiscoverLockedStatusBarIconSurfaces(root); } else { removeSharedLayoutHosts(root); @@ -2100,9 +2197,6 @@ final class StockLayoutCanvasController { && (unlockedScene || activeScene != null && activeScene.isLockscreen() || aodScene)) { - if (!unlockedScene) { - restoreTrackedLockedStatusBarIconSurfaces(root); - } boolean shouldRender = !unlockedIconRenderController.hasLayoutPlan(root) || rootDirty; if (shouldRender && shouldDeferUnlockedRenderForDrawer(root)) { @@ -2225,6 +2319,13 @@ final class StockLayoutCanvasController { || findDirectOwnedHost(group, OwnedIconHostManager.TAG_AOD_CARDS_NOTIFICATION_HOST) != null; } + private boolean hasLockscreenCardsNotificationHost(View root) { + return root instanceof ViewGroup group + && findDirectOwnedHost( + group, + OwnedIconHostManager.TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST) != null; + } + private void applyDebugOverlay( View root, boolean layoutEnabled, @@ -2346,6 +2447,51 @@ final class StockLayoutCanvasController { }); } + private void hideUnlockedShadeShelfIconSurfaces(View root) { + if (root == null) { + return; + } + boolean foundTracked = hideTrackedUnlockedShadeShelfIconSurfaces(root); + if (foundTracked) { + return; + } + walkTree(root, view -> { + if (isUnlockedShadeShelfIconSurface(view)) { + trackedUnlockedStockSurfaces.add(view); + hideView(view, false); + } + }); + } + + private boolean hideTrackedUnlockedShadeShelfIconSurfaces(View root) { + if (trackedUnlockedStockSurfaces.isEmpty()) { + return false; + } + boolean foundIconSurfaceInRoot = false; + ArrayDeque staleViews = new ArrayDeque<>(); + for (View view : trackedUnlockedStockSurfaces) { + if (view == null || !view.isAttachedToWindow()) { + staleViews.add(view); + continue; + } + if (!isDescendantOf(view, root)) { + continue; + } + if (!isUnlockedShadeShelfIconSurface(view)) { + continue; + } + foundIconSurfaceInRoot |= isCardsNotificationIconSurface(view); + hideView(view, false); + } + removeStaleViews(trackedUnlockedStockSurfaces, staleViews); + return foundIconSurfaceInRoot; + } + + private boolean isUnlockedShadeShelfIconSurface(View view) { + return isCardsNotificationIconSurface(view) + || isCardsNotificationShelfBackgroundSurface(view); + } + private boolean hasTrackedUnlockedStockSurfaces(View root) { if (trackedUnlockedStockSurfaces.isEmpty()) { return false; @@ -2748,14 +2894,17 @@ final class StockLayoutCanvasController { return false; } - private void trackAndHideLockedStatusBarIconSurfaces(View root) { + private int trackAndHideLockedStatusBarIconSurfaces(View root) { + final int[] found = {0}; walkTree(root, view -> { if (isLockedStatusBarIconSurface(view)) { rememberLockedCarrierTextSource(view); trackedLockedStatusBarIconSurfaces.add(view); hideView(view, isCarrierView(view)); + found[0]++; } }); + return found[0]; } private void hideOrDiscoverLockedStatusBarIconSurfaces(View root) { @@ -2776,13 +2925,18 @@ final class StockLayoutCanvasController { staleViews.add(view); continue; } + if (root == null || isDescendantOf(view, root)) { + foundInRoot = true; + if (isAlreadySuppressedHiddenView(view)) { + continue; + } + } if (!isLockedStatusBarIconSurface(view)) { restoreView(view); staleViews.add(view); continue; } if (root == null || isDescendantOf(view, root)) { - foundInRoot = true; hideView(view, isCarrierView(view)); } } @@ -2790,6 +2944,13 @@ final class StockLayoutCanvasController { return foundInRoot; } + private boolean isAlreadySuppressedHiddenView(View view) { + return view != null + && hiddenViewStates.containsKey(view) + && view.getAlpha() == 0f + && (view.getVisibility() == View.GONE || view.getVisibility() == View.VISIBLE); + } + private boolean shouldSuppressConflictingShadeHeaderAlpha(View view, float alpha) { return view != null && alpha != view.getAlpha() @@ -2928,11 +3089,18 @@ final class StockLayoutCanvasController { if (trackedLockedStatusBarIconSurfaces.isEmpty()) { return; } + ArrayDeque restoredViews = new ArrayDeque<>(); for (View view : trackedLockedStatusBarIconSurfaces) { - if (view != null && view.isAttachedToWindow() && (root == null || isDescendantOf(view, root))) { + if (view == null || !view.isAttachedToWindow()) { + restoredViews.add(view); + continue; + } + if (root == null || isDescendantOf(view, root)) { restoreView(view); + restoredViews.add(view); } } + removeStaleViews(trackedLockedStatusBarIconSurfaces, restoredViews); } private boolean hasTrackedStatusChipSources(View root) { @@ -3297,7 +3465,7 @@ final class StockLayoutCanvasController { aodBeforeDrawSignatureByRoot.remove(root); lockscreenCardsRowFilterSignatures.remove(root); lockscreenCardsNotificationRenderGenerationByRoot.remove(root); - clockTimeBucketByRoot.remove(root); + clearUnlockedClockRefreshState(root); removeDebugOverlay(root); root.requestLayout(); root.invalidate(); @@ -4046,7 +4214,7 @@ final class StockLayoutCanvasController { dirtyUnlockedLayoutRoots.remove(root); settingsIdentityByRoot.remove(root); lockscreenCardsRowFilterSignatures.remove(root); - clockTimeBucketByRoot.remove(root); + clearUnlockedClockRefreshState(root); if (!isUnlockedStatusBarRoot(root) || !layoutOffCleanedRoots.add(root)) { unlockedHostsByRoot.remove(root); rootGeometries.remove(root); @@ -4117,7 +4285,7 @@ final class StockLayoutCanvasController { pendingUnlockedAppearanceRefreshRoots.remove(root); dirtyUnlockedLayoutRoots.remove(root); settingsIdentityByRoot.remove(root); - clockTimeBucketByRoot.remove(root); + clearUnlockedClockRefreshState(root); } private void removeUnlockedLayoutHosts(View root) { @@ -4130,7 +4298,7 @@ final class StockLayoutCanvasController { pendingUnlockedAppearanceRefreshRoots.remove(root); dirtyUnlockedLayoutRoots.remove(root); settingsIdentityByRoot.remove(root); - clockTimeBucketByRoot.remove(root); + clearUnlockedClockRefreshState(root); } private boolean isDescendantOfNotificationShadeWindow(View view) { @@ -4185,16 +4353,23 @@ final class StockLayoutCanvasController { SbtSettings settings, SceneKey activeScene ) { - View visibleBouncerSurface = findVisibleBouncerSurface(root); - boolean suppressForTransition = runtimeContext.getModeStateRepository().isSystemUiShadeLocked() - || visibleBouncerSurface != null; if (root == null || settings == null || activeScene == null || !activeScene.isLockscreen() || activeScene.notificationDisplayStyle() != NotificationDisplayStyle.CARDS - || !isNotificationShadeWindowRoot(root) - || suppressForTransition) { + || !isNotificationShadeWindowRoot(root)) { + removeLockscreenCardsNotificationHost(root); + lockscreenCardsNotificationRenderGenerationByRoot.remove(root); + return; + } + if (runtimeContext.getModeStateRepository().isSystemUiShadeLocked()) { + removeLockscreenCardsNotificationHost(root); + lockscreenCardsNotificationRenderGenerationByRoot.remove(root); + return; + } + View visibleBouncerSurface = findVisibleBouncerSurface(root); + if (visibleBouncerSurface != null) { removeLockscreenCardsNotificationHost(root); lockscreenCardsNotificationRenderGenerationByRoot.remove(root); return; @@ -4210,7 +4385,7 @@ final class StockLayoutCanvasController { int rootHeight = root.getHeight(); int activeKeyGeneration = NotificationActiveKeyStore.generation(); int sourceSignature = - LockscreenCardsNotificationIconSourceStore.snapshotSourceSignatureForRoot(root); + LockscreenCardsNotificationIconSourceStore.lightweightSourceSignatureForRoot(root); int generation = settingsId; generation = 31 * generation + rootWidth; generation = 31 * generation + rootHeight; @@ -4224,7 +4399,7 @@ final class StockLayoutCanvasController { lockscreenCardsNotificationRenderGenerationByRoot.put(root, generation); lockscreenCardsNotificationStripRenderer.render(root, hostGroup, settings, activeScene); } - host.bringToFront(); + bringToFrontIfNeeded(host); } } @@ -4271,10 +4446,21 @@ final class StockLayoutCanvasController { lockscreenCardsNotificationStripRenderer.render(root, hostGroup, settings, activeScene); } setHostAlpha(host, aodVisualAlphaForRoot(root)); - host.bringToFront(); + bringToFrontIfNeeded(host); } } + private void bringToFrontIfNeeded(View view) { + if (view == null || !(view.getParent() instanceof ViewGroup group)) { + return; + } + int childCount = group.getChildCount(); + if (childCount <= 0 || group.getChildAt(childCount - 1) == view) { + return; + } + view.bringToFront(); + } + private void applyLockscreenCardsNotificationRowFilter(ViewGroup stack, boolean allowAfterKeyguardUnlock) { if (stack == null) { return; @@ -4286,6 +4472,17 @@ final class StockLayoutCanvasController { lockscreenCardsNotificationRowFilter.apply(stack, allowAfterKeyguardUnlock); } + private void applyLockscreenCardsNotificationRowFilterRow(View row, boolean allowAfterKeyguardUnlock) { + if (row == null) { + return; + } + if (runtimeContext.getModeStateRepository().isSystemUiShadeLocked()) { + lockscreenCardsNotificationRowFilter.restoreRemovedRow(row); + return; + } + lockscreenCardsNotificationRowFilter.applyRow(row, allowAfterKeyguardUnlock); + } + private boolean applyLockscreenCardsNotificationRowFilterInTreeIfNeeded( View root, SbtSettings settings, @@ -4311,7 +4508,6 @@ final class StockLayoutCanvasController { + (runtimeContext.getModeStateRepository().isSystemUiShadeLocked() ? 1 : 0); result = 31 * result + root.getWidth(); result = 31 * result + root.getHeight(); - result = 31 * result + (root instanceof ViewGroup group ? group.getChildCount() : 0); return result; } @@ -4471,7 +4667,7 @@ final class StockLayoutCanvasController { dirtyUnlockedLayoutRoots.remove(root); settingsIdentityByRoot.remove(root); lockscreenCardsRowFilterSignatures.remove(root); - clockTimeBucketByRoot.remove(root); + clearUnlockedClockRefreshState(root); return null; } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockLayout.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockLayout.java index 229f16b..2147f3e 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockLayout.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/ClockLayout.java @@ -63,7 +63,6 @@ final class ClockLayout { int baseHeightSteps, int autoGapSteps, int statusbarHeight, - int visualBaselineY, int verticalOffsetSteps ) { if (source == null || model == null || model.rows.isEmpty()) { @@ -102,10 +101,8 @@ final class ClockLayout { } measuredRows = positionRowsFromLogicalSteps( measuredRows, - Math.max(1, baseHeightSteps), Math.max(1, autoGapSteps), Math.max(1, statusbarHeight), - visualBaselineY, verticalOffsetSteps); RowAlignment alignment = alignRows(measuredRows, position); @@ -216,10 +213,8 @@ final class ClockLayout { private static ArrayList positionRowsFromLogicalSteps( ArrayList measuredRows, - int baseHeightSteps, int autoGapSteps, int statusbarHeight, - int visualBaselineY, int verticalOffsetSteps ) { ArrayList positionedRows = new ArrayList<>(measuredRows); @@ -232,8 +227,7 @@ final class ClockLayout { if (rowIndex == measuredRows.size() - 1) { topSteps = 100 - Math.max(1, row.logicalHeightSteps) - verticalOffsetSteps; int heightPx = Math.max(1, stepsToPx(Math.max(1, row.logicalHeightSteps), statusbarHeight)); - int baselineY = visualBaselineY - stepsToPx(verticalOffsetSteps, statusbarHeight); - bottomPx = baselineY - row.baseline + heightPx; + bottomPx = Math.max(1, statusbarHeight) - stepsToPx(verticalOffsetSteps, statusbarHeight); } else { MeasuredClockRow nextRow = measuredRows.get(rowIndex + 1); int gapSteps = resolveGapSteps(nextRow.source.gapBeforePx, autoGapSteps); diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationIconSourceStore.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationIconSourceStore.java index ada268b..38fdba2 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationIconSourceStore.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/LockscreenCardsNotificationIconSourceStore.java @@ -27,6 +27,7 @@ public final class LockscreenCardsNotificationIconSourceStore { new WeakHashMap<>(); private static final ArrayList AOD_NOTIFICATION_PACKAGES = new ArrayList<>(); private static final int VIRTUAL_ICON_SIZE_PX = 39; + private static int generation; private LockscreenCardsNotificationIconSourceStore() { } @@ -34,7 +35,9 @@ public final class LockscreenCardsNotificationIconSourceStore { public static void put(ViewGroup container, ArrayList sources) { if (container == null || sources == null || sources.isEmpty()) { if (container != null) { - SOURCES_BY_CONTAINER.remove(container); + if (SOURCES_BY_CONTAINER.remove(container) != null) { + bumpGeneration(); + } } return; } @@ -47,7 +50,12 @@ public final class LockscreenCardsNotificationIconSourceStore { attached.add(source); } if (attached.isEmpty()) { - SOURCES_BY_CONTAINER.remove(container); + if (SOURCES_BY_CONTAINER.remove(container) != null) { + bumpGeneration(); + } + } else if (!sameSourceEntries(SOURCES_BY_CONTAINER.get(container), attached)) { + SOURCES_BY_CONTAINER.put(container, attached); + bumpGeneration(); } else { SOURCES_BY_CONTAINER.put(container, attached); } @@ -56,7 +64,11 @@ public final class LockscreenCardsNotificationIconSourceStore { public static boolean putPackages(ViewGroup container, ArrayList packages) { if (container == null || packages == null || packages.isEmpty()) { if (container != null) { - return PACKAGES_BY_CONTAINER.remove(container) != null; + boolean changed = PACKAGES_BY_CONTAINER.remove(container) != null; + if (changed) { + bumpGeneration(); + } + return changed; } return false; } @@ -71,13 +83,18 @@ public final class LockscreenCardsNotificationIconSourceStore { } } if (!hasValid) { - return PACKAGES_BY_CONTAINER.remove(container) != null; + boolean changed = PACKAGES_BY_CONTAINER.remove(container) != null; + if (changed) { + bumpGeneration(); + } + return changed; } else { ArrayList previous = PACKAGES_BY_CONTAINER.get(container); if (previous != null && previous.equals(valid)) { return false; } PACKAGES_BY_CONTAINER.put(container, valid); + bumpGeneration(); return true; } } @@ -85,7 +102,11 @@ public final class LockscreenCardsNotificationIconSourceStore { public static boolean putAodDrawables(ViewGroup container, ArrayList views) { if (container == null || views == null || views.isEmpty()) { if (container != null) { - return AOD_DRAWABLES_BY_CONTAINER.remove(container) != null; + boolean changed = AOD_DRAWABLES_BY_CONTAINER.remove(container) != null; + if (changed) { + bumpGeneration(); + } + return changed; } return false; } @@ -100,18 +121,28 @@ public final class LockscreenCardsNotificationIconSourceStore { valid.add(view); } if (valid.isEmpty()) { - return AOD_DRAWABLES_BY_CONTAINER.remove(container) != null; + boolean changed = AOD_DRAWABLES_BY_CONTAINER.remove(container) != null; + if (changed) { + bumpGeneration(); + } + return changed; } else { ArrayList previous = AOD_DRAWABLES_BY_CONTAINER.get(container); if (sameViews(previous, valid)) { return false; } AOD_DRAWABLES_BY_CONTAINER.put(container, valid); + bumpGeneration(); return true; } } public static void clearAodSources() { + if (!PACKAGES_BY_CONTAINER.isEmpty() + || !AOD_DRAWABLES_BY_CONTAINER.isEmpty() + || !AOD_NOTIFICATION_PACKAGES.isEmpty()) { + bumpGeneration(); + } PACKAGES_BY_CONTAINER.clear(); AOD_DRAWABLES_BY_CONTAINER.clear(); AOD_NOTIFICATION_PACKAGES.clear(); @@ -121,20 +152,76 @@ public final class LockscreenCardsNotificationIconSourceStore { if (container == null) { return; } - PACKAGES_BY_CONTAINER.remove(container); - AOD_DRAWABLES_BY_CONTAINER.remove(container); + boolean changed = PACKAGES_BY_CONTAINER.remove(container) != null; + changed |= AOD_DRAWABLES_BY_CONTAINER.remove(container) != null; + if (changed) { + bumpGeneration(); + } } public static void putAodNotificationPackages(ArrayList packages) { + ArrayList previous = new ArrayList<>(AOD_NOTIFICATION_PACKAGES); AOD_NOTIFICATION_PACKAGES.clear(); - if (packages == null || packages.isEmpty()) { - return; - } - for (String pkg : packages) { - if (AppIconRules.isValidPackageName(pkg) && !AOD_NOTIFICATION_PACKAGES.contains(pkg)) { - AOD_NOTIFICATION_PACKAGES.add(pkg); + if (packages != null && !packages.isEmpty()) { + for (String pkg : packages) { + if (AppIconRules.isValidPackageName(pkg) && !AOD_NOTIFICATION_PACKAGES.contains(pkg)) { + AOD_NOTIFICATION_PACKAGES.add(pkg); + } } } + if (!previous.equals(AOD_NOTIFICATION_PACKAGES)) { + bumpGeneration(); + } + } + + public static int generation() { + return generation; + } + + private static void bumpGeneration() { + generation++; + } + + private static boolean sameSourceEntries( + ArrayList previous, + ArrayList current + ) { + if (previous == current) { + return true; + } + if (previous == null || current == null || previous.size() != current.size()) { + return false; + } + for (int i = 0; i < previous.size(); i++) { + SourceEntry oldEntry = previous.get(i); + SourceEntry newEntry = current.get(i); + if (oldEntry == newEntry) { + continue; + } + if (oldEntry == null || newEntry == null) { + return false; + } + if (oldEntry.view != newEntry.view) { + return false; + } + if (oldEntry.key == null ? newEntry.key != null : !oldEntry.key.equals(newEntry.key)) { + return false; + } + } + return true; + } + + public static int lightweightSourceSignatureForRoot(View root) { + int result = 17; + result = 31 * result + generation(); + ViewGroup container = sourceContainerForRoot(root); + Bounds bounds = sourceBoundsRelativeTo(root, container); + if (isZeroSizeDotContainer(container, bounds)) { + bounds = new Bounds(bounds.left, bounds.top, VIRTUAL_ICON_SIZE_PX, VIRTUAL_ICON_SIZE_PX); + } + result = 31 * result + (bounds != null ? bounds.signature() : 0); + result = 31 * result + (sourceContainerHasHiddenAncestor(root, container) ? 1 : 0); + return result; } private static boolean sameViews(ArrayList previous, ArrayList current) { @@ -348,6 +435,10 @@ public final class LockscreenCardsNotificationIconSourceStore { public static boolean sourceContainerHasHiddenAncestorForRoot(View root) { ViewGroup container = sourceContainerForRoot(root); + return sourceContainerHasHiddenAncestor(root, container); + } + + private static boolean sourceContainerHasHiddenAncestor(View root, ViewGroup container) { if (container == null) { return false; } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java index 1e6ac9e..8de3d60 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/SnapshotRenderer.java @@ -579,11 +579,21 @@ final class SnapshotRenderer { int result = 17; result = 31 * result + sourceTreeSignature(source.view()); Bounds boundsOverride = source.boundsOverride(); - result = 31 * result + (boundsOverride != null ? boundsOverride.signature() : 0); + result = 31 * result + boundsSizeSignature(boundsOverride); result = 31 * result + tintSignatureFor(source); return result; } + private int boundsSizeSignature(Bounds bounds) { + if (bounds == null) { + return 0; + } + int result = 17; + result = 31 * result + bounds.width; + result = 31 * result + bounds.height; + return result; + } + private int tintSignatureFor(SnapshotSource source) { return shouldApplyStatusBarTintTarget(source) ? StatusBarTintTarget.signature() : 0; } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java index fd4c0b1..25d480f 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedIconSnapshotRenderController.java @@ -113,7 +113,14 @@ public final class UnlockedIconSnapshotRenderController { false, false, false, - !scene.isLockscreen() || lastLayoutPlanByRoot.get(root) != null); + !scene.isLockscreen() || lastLayoutPlanByRoot.get(root) != null, + lockscreenIconDebugSummary( + root, + notificationHost, + scene, + cachedPlan, + lastCollectedIconsByRoot.get(root), + "cacheHit")); } lastLayoutSignatureByRoot.put(root, signature); Integer currentClockSourceGeometry = clockSourceGeometrySignature( @@ -191,7 +198,18 @@ public final class UnlockedIconSnapshotRenderController { if (scene.isLockscreen() && clockEnabledForScene(settings, scene) && currentClockLayout == null) { clearLockscreenHosts(clockHost, carrierHost, chipHost, statusHost, notificationHost); lastLayoutPlanByRoot.remove(root); - return new RenderResult(false, stockSourcesChanged, chipSourcesChanged, false); + return new RenderResult( + false, + stockSourcesChanged, + chipSourcesChanged, + false, + lockscreenIconDebugSummary( + root, + notificationHost, + scene, + null, + collectedIcons, + "missingClock")); } LayoutPlan layoutPlan = layoutPlanner.solve( root, @@ -237,7 +255,18 @@ public final class UnlockedIconSnapshotRenderController { } else { notificationRenderer.clear(notificationHost); } - return new RenderResult(true, stockSourcesChanged, chipSourcesChanged); + return new RenderResult( + true, + stockSourcesChanged, + chipSourcesChanged, + true, + lockscreenIconDebugSummary( + root, + notificationHost, + scene, + layoutPlan, + collectedIcons, + "solved")); } public void refreshUnlockedAppearance( @@ -722,7 +751,6 @@ public final class UnlockedIconSnapshotRenderController { int textSizeSteps = clockTextSizeSteps(settings, scene); float textSizePx = clockTextSizePx(statusbarHeight, textSizeSteps); int autoGapSteps = clockAutoGapSteps(source, statusbarHeight, textSizeSteps); - int visualBaselineY = clockVisualBaselineY(source, sourceBounds, statusbarHeight); ClockLayout layout = ClockLayout.measure( source, model, @@ -735,7 +763,6 @@ public final class UnlockedIconSnapshotRenderController { textSizeSteps, autoGapSteps, statusbarHeight, - visualBaselineY, settings.clockVerticalOffsetPx); return layout; } @@ -748,17 +775,6 @@ public final class UnlockedIconSnapshotRenderController { return Math.max(1, Math.round(lineHeight * 100f / statusbarHeight)); } - private int clockVisualBaselineY(TextView source, Bounds sourceBounds, int fallbackBaselineY) { - if (source == null || sourceBounds == null) { - return Math.max(1, fallbackBaselineY); - } - int baseline = source.getBaseline(); - if (baseline <= 0) { - return Math.max(1, sourceBounds.top + sourceBounds.height); - } - return Math.max(1, sourceBounds.top + baseline); - } - private int clockTextSizeSteps(SbtSettings settings, SceneKey scene) { return scene != null && scene.isLockscreen() ? settings.clockLockTextSizeSteps @@ -1039,14 +1055,127 @@ public final class UnlockedIconSnapshotRenderController { return result; } + private String lockscreenIconDebugSummary( + View root, + ViewGroup notificationHost, + SceneKey scene, + LayoutPlan plan, + CollectedIcons icons, + String phase + ) { + if (scene == null + || !scene.isLockscreen() + || scene.notificationDisplayStyle() != NotificationDisplayStyle.ICONS) { + return null; + } + ArrayList notificationPlacements = + plan != null ? plan.notificationPlacements : null; + return "phase=" + phase + + " root=" + viewBounds(root) + + " hostChildren=" + (notificationHost != null ? notificationHost.getChildCount() : -1) + + " sourceNotifs=" + sizeOf(icons != null ? icons.notificationIcons : null) + + " placementCount=" + sizeOf(notificationPlacements) + + " renderableCount=" + renderablePlacementCount(notificationPlacements) + + " clock=" + boundsText(plan != null && plan.clockPlacement != null + ? plan.clockPlacement.bounds + : null) + + " carrier=" + boundsText(plan != null && plan.carrierPlacement != null + ? plan.carrierPlacement.bounds + : null) + + " notifs=" + placementSummary(notificationPlacements); + } + + private int sizeOf(ArrayList values) { + return values != null ? values.size() : 0; + } + + private int renderablePlacementCount(ArrayList placements) { + if (placements == null) { + return 0; + } + int count = 0; + for (SnapshotPlacement placement : placements) { + if (isRenderablePlacement(placement)) { + count++; + } + } + return count; + } + + private boolean isRenderablePlacement(SnapshotPlacement placement) { + return placement != null + && (placement.source != null + || placement.overflowDot + || placement.heldSignal != null); + } + + private String placementSummary(ArrayList placements) { + if (placements == null || placements.isEmpty()) { + return "[]"; + } + StringBuilder out = new StringBuilder("["); + int limit = Math.min(placements.size(), 8); + for (int i = 0; i < limit; i++) { + if (i > 0) { + out.append(';'); + } + SnapshotPlacement placement = placements.get(i); + out.append(i).append('{'); + if (placement == null) { + out.append("null"); + } else { + out.append("key=").append(placement.key) + .append(",b=").append(boundsText(placement.bounds)) + .append(",source=").append(placement.source != null) + .append(",dot=").append(placement.overflowDot) + .append(",held=").append(placement.heldSignal != null) + .append(",renderable=").append(isRenderablePlacement(placement)); + } + out.append('}'); + } + if (placements.size() > limit) { + out.append(";..."); + } + out.append(']'); + return out.toString(); + } + + private String viewBounds(View view) { + if (view == null) { + return "null"; + } + return view.getWidth() + "x" + view.getHeight() + + "+" + view.getLeft() + + "," + view.getTop(); + } + + private String boundsText(Bounds bounds) { + if (bounds == null) { + return "null"; + } + return bounds.width + "x" + bounds.height + + "+" + bounds.left + + "," + bounds.top; + } + public record RenderResult( boolean layoutChanged, boolean stockSourcesChanged, boolean chipSourcesChanged, - boolean shouldOwnLockscreen + boolean shouldOwnLockscreen, + String debugSummary ) { public RenderResult(boolean layoutChanged, boolean stockSourcesChanged, boolean chipSourcesChanged) { - this(layoutChanged, stockSourcesChanged, chipSourcesChanged, true); + this(layoutChanged, stockSourcesChanged, chipSourcesChanged, true, null); + } + + public RenderResult( + boolean layoutChanged, + boolean stockSourcesChanged, + boolean chipSourcesChanged, + boolean shouldOwnLockscreen + ) { + this(layoutChanged, stockSourcesChanged, chipSourcesChanged, shouldOwnLockscreen, null); } } diff --git a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java index c945d45..1a17278 100644 --- a/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java +++ b/app/src/main/java/se/ajpanton/statusbartweak/runtime/render/UnlockedLayoutPlanner.java @@ -103,7 +103,8 @@ final class UnlockedLayoutPlanner { scene, edges, layoutCutoutBounds, - clockLayout); + clockLayout, + statusbarHeight); if (carrierLayout != null && carrierLayout.width() > 0) { bands.add(UnlockedLayoutBand.carrier( carrierHost, @@ -649,7 +650,8 @@ final class UnlockedLayoutPlanner { SceneKey scene, LayoutEdges edges, Bounds cutoutBounds, - ClockLayout clockLayout + ClockLayout clockLayout, + int statusbarHeight ) { if (root == null || collectedIcons == null @@ -687,9 +689,11 @@ final class UnlockedLayoutPlanner { settings, edges, cutoutBounds, + scene, positionFor(settings.layoutCarrierPosition), sideFor(settings.layoutCarrierMiddleSide), - clockLayout)); + clockLayout, + statusbarHeight)); } private int carrierMaxWidth( @@ -697,9 +701,11 @@ final class UnlockedLayoutPlanner { SbtSettings settings, LayoutEdges edges, Bounds cutoutBounds, + SceneKey scene, LayoutPosition position, AnchorSide middleSide, - ClockLayout clockLayout + ClockLayout clockLayout, + int statusbarHeight ) { int rootWidth = root != null ? root.getWidth() : 0; if (rootWidth <= 0 || edges == null) { @@ -723,42 +729,86 @@ final class UnlockedLayoutPlanner { return carrierMaxWidthWithClockReserve( Math.max(1, right - minLeft), settings, + scene, position, middleSide, clockLayout, - AnchorSide.RIGHT); + AnchorSide.RIGHT, + statusbarHeight); } int maxRight = usableCutout ? Math.max(left, cutoutBounds.left) : right; return carrierMaxWidthWithClockReserve( Math.max(1, maxRight - left), settings, + scene, position, middleSide, clockLayout, - AnchorSide.LEFT); + AnchorSide.LEFT, + statusbarHeight); } private int carrierMaxWidthWithClockReserve( int sideWidth, SbtSettings settings, + SceneKey scene, LayoutPosition carrierPosition, AnchorSide carrierMiddleSide, ClockLayout clockLayout, - AnchorSide side + AnchorSide side, + int statusbarHeight ) { - if (sideWidth <= 0 || settings == null || clockLayout == null || clockLayout.width() <= 0) { + if (sideWidth <= 0 || settings == null) { return Math.max(1, sideWidth); } - LayoutPosition clockPosition = positionFor(settings.clockPosition); - AnchorSide clockMiddleSide = sideFor(settings.clockMiddleCutoutSide); - if (!occupiesSide(clockPosition, clockMiddleSide, side) - || !occupiesSide(carrierPosition, carrierMiddleSide, side)) { + if (!occupiesSide(carrierPosition, carrierMiddleSide, side)) { return Math.max(1, sideWidth); } - int reserved = clockLayout.width() + Math.max(0, settings.layoutItemPaddingPx); + int reserved = notificationDotReserveForSide(settings, scene, side, statusbarHeight); + if (clockLayout != null && clockLayout.width() > 0) { + LayoutPosition clockPosition = positionFor(settings.clockPosition); + AnchorSide clockMiddleSide = sideFor(settings.clockMiddleCutoutSide); + if (occupiesSide(clockPosition, clockMiddleSide, side)) { + reserved += clockLayout.width() + Math.max(0, settings.layoutItemPaddingPx); + } + } return Math.max(1, sideWidth - reserved); } + private int notificationDotReserveForSide( + SbtSettings settings, + SceneKey scene, + AnchorSide side, + int statusbarHeight + ) { + if (settings == null + || scene == null + || !scene.isLockscreen() + || !settings.layoutNotifEnabledLockscreen + || (scene.notificationDisplayStyle() != NotificationDisplayStyle.ICONS + && scene.notificationDisplayStyle() != NotificationDisplayStyle.DOT)) { + return 0; + } + int count = notificationCountForScene(settings, scene); + if (count <= 0) { + return 0; + } + String[] positions = notificationPositionsForScene(settings, scene); + String[] middleSides = notificationMiddleSidesForScene(settings, scene); + for (int i = 0; i < count; i++) { + LayoutPosition position = positionFor(stringAt(positions, i, settings.layoutNotifPosition)); + AnchorSide middleSide = sideFor(stringAt(middleSides, i, settings.layoutNotifMiddleSide)); + if (occupiesSide(position, middleSide, side)) { + int iconHeight = iconHeightPx( + statusbarHeight, + notificationIconHeightSteps(settings, scene)); + int dotWidth = Math.max(1, Math.round(Math.max(1, iconHeight) * 0.75f)); + return dotWidth + Math.max(0, settings.layoutItemPaddingPx); + } + } + return 0; + } + private boolean occupiesSide( LayoutPosition position, AnchorSide middleSide,