11 Commits
13 changed files with 561 additions and 80 deletions
@@ -0,0 +1,27 @@
package se.ajpanton.statusbartweak.runtime;
public final class RuntimeMutationGuard {
private static final ThreadLocal<Integer> SNAPSHOT_RENDER_DEPTH =
ThreadLocal.withInitial(() -> 0);
private RuntimeMutationGuard() {
}
public static void enterSnapshotRenderMutation() {
SNAPSHOT_RENDER_DEPTH.set(SNAPSHOT_RENDER_DEPTH.get() + 1);
}
public static void exitSnapshotRenderMutation() {
int depth = SNAPSHOT_RENDER_DEPTH.get() - 1;
if (depth <= 0) {
SNAPSHOT_RENDER_DEPTH.remove();
} else {
SNAPSHOT_RENDER_DEPTH.set(depth);
}
}
public static boolean isSnapshotRenderMutation() {
Integer depth = SNAPSHOT_RENDER_DEPTH.get();
return depth != null && depth > 0;
}
}
@@ -434,7 +434,7 @@ final class BatteryBarController {
) {
BatteryBarView barView = ensureBatteryBarView(rootGroup);
layoutBatteryBarView(rootGroup, barView);
barView.update(rootGroup, settings, geometry, batteryLevel, plugged, color);
barView.update(rootGroup, rootGroup, settings, geometry, batteryLevel, plugged, color);
barView.bringToFront();
barView.invalidate();
lastSignatures.put(rootGroup, buildSignature(rootGroup, settings, color, geometry, scenario));
@@ -461,7 +461,7 @@ final class BatteryBarController {
return;
}
pendingOverlayRetries.remove(root);
overlay.view.update(overlay.host, settings, geometry, batteryLevel, plugged, color);
overlay.view.update(overlay.host, root, settings, geometry, batteryLevel, plugged, color);
if (!signature.equals(lastOverlaySignatures.get(root))) {
overlay.updateLayout(bounds.width, bounds.height);
lastOverlaySignatures.put(root, signature);
@@ -1039,6 +1039,7 @@ final class BatteryBarController {
private final Path drawPath = new Path();
private final PathMeasure pathMeasure = new PathMeasure();
private ViewGroup host;
private View roundedCornerSource;
private SbtSettings settings;
private BatteryBarGeometry geometry;
private int batteryLevel = -1;
@@ -1053,6 +1054,7 @@ final class BatteryBarController {
void update(
ViewGroup host,
View roundedCornerSource,
SbtSettings settings,
BatteryBarGeometry geometry,
int batteryLevel,
@@ -1060,6 +1062,7 @@ final class BatteryBarController {
int color
) {
this.host = host;
this.roundedCornerSource = roundedCornerSource;
this.settings = settings;
this.geometry = geometry;
this.batteryLevel = batteryLevel;
@@ -1226,7 +1229,12 @@ final class BatteryBarController {
}
private float resolveTopCornerRadius(float fallback) {
WindowInsets insets = getRootWindowInsets();
WindowInsets insets = roundedCornerSource != null
? roundedCornerSource.getRootWindowInsets()
: null;
if (insets == null) {
insets = getRootWindowInsets();
}
if (insets == null) {
return fallback;
}
@@ -153,7 +153,7 @@ final class StockClockController {
}
runtimeContext.getModeStateRepository().addLayoutEnabledListener(enabled -> {
if (enabled) {
restoreAllOwnedClocks();
suspendOwnedClockTickers();
} else {
refreshAllClocks();
}
@@ -212,23 +212,22 @@ final class StockClockController {
}
}
private void removeAllRowOverlays() {
for (TextView clockView : new ArrayList<>(trackedClocks)) {
removeRowOverlay(clockView);
private void suspendOwnedClockTickers() {
for (TextView clockView : new ArrayList<>(ownedClocks)) {
stopTicker(clockView);
}
}
private void restoreAllOwnedClocks() {
for (TextView clockView : new ArrayList<>(trackedClocks)) {
restoreOwnedClock(clockView);
}
removeAllRowOverlays();
}
private void applyClockText(TextView clockView) {
SbtSettings settings = RuntimeSettingsCache.get(clockView.getContext());
boolean layoutEnabled = settings.clockEnabled;
if (layoutEnabled || !hasCustomClockTextEnabled(settings)) {
if (layoutEnabled) {
if (ownedClocks.contains(clockView)) {
stopTicker(clockView);
}
return;
}
if (!hasCustomClockTextEnabled(settings)) {
restoreOwnedClock(clockView);
return;
}
@@ -237,6 +237,9 @@ final class StockUnlockedNotificationIconsController {
}
private boolean shouldManageContainer(View view) {
if (!isFeatureEnabled(view.getContext())) {
return false;
}
if (!isUnlockedScene()) {
return false;
}
@@ -10,8 +10,10 @@ import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.os.PowerManager;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.ImageView;
import android.widget.TextView;
@@ -34,6 +36,7 @@ import java.util.WeakHashMap;
import java.util.function.Function;
import se.ajpanton.statusbartweak.runtime.NotificationKeyReflection;
import se.ajpanton.statusbartweak.runtime.RuntimeMutationGuard;
import se.ajpanton.statusbartweak.runtime.ViewIdNames;
import se.ajpanton.statusbartweak.runtime.features.RuntimeContext;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
@@ -136,6 +139,8 @@ final class StockLayoutCanvasController {
Collections.newSetFromMap(new WeakHashMap<>());
private final Set<View> trackedLockedStatusBarIconSurfaces =
Collections.newSetFromMap(new WeakHashMap<>());
private final Set<View> lockedStatusBarIconSurfaceReadyRoots =
Collections.newSetFromMap(new WeakHashMap<>());
private final Set<View> pendingUnlockedAppearanceRefreshRoots =
Collections.newSetFromMap(new WeakHashMap<>());
private final Set<View> pendingUnlockedClockTextRefreshRoots =
@@ -185,6 +190,7 @@ final class StockLayoutCanvasController {
private int notificationDrawerCloseGeneration;
private boolean notificationDrawerClosePending;
private int aodVisualAlphaGeneration;
private int systemBarRequestedVisibleTypes = WindowInsets.Type.statusBars();
StockLayoutCanvasController(RuntimeContext runtimeContext) {
this.runtimeContext = runtimeContext;
@@ -213,10 +219,44 @@ final class StockLayoutCanvasController {
statusIconModelTracker.installHooks(classLoader);
installShadeExpansionHooks(classLoader);
installShadeHeaderAnimationGuardHook(classLoader);
installSystemBarAttributeListener(classLoader);
installViewRootHook(classLoader);
hooksInstalled = true;
}
private void installSystemBarAttributeListener(ClassLoader classLoader) {
Class<?> commandQueueClass = ReflectionSupport.findClassIfExists(
"com.android.systemui.statusbar.CommandQueue",
classLoader);
if (commandQueueClass == null) {
return;
}
XposedHookSupport.hookAllMethodsIfExists(
runtimeContext.getFramework(),
commandQueueClass,
"onSystemBarAttributesChanged",
chain -> {
Object result = chain.proceed();
updateSystemBarRequestedVisibleTypes(chain.getArgs());
return result;
});
}
private void updateSystemBarRequestedVisibleTypes(List<Object> args) {
if (args == null || args.size() <= 5) {
return;
}
Integer displayId = asInteger(args.get(0));
Integer requestedVisibleTypes = asInteger(args.get(5));
if (displayId == null || displayId != 0 || requestedVisibleTypes == null) {
return;
}
if (systemBarRequestedVisibleTypes == requestedVisibleTypes) {
return;
}
systemBarRequestedVisibleTypes = requestedVisibleTypes;
}
private void installNotificationCollectionHooks(ClassLoader classLoader) {
for (String className : List.of(
"com.android.systemui.statusbar.notification.collection.NotifCollection",
@@ -405,6 +445,9 @@ final class StockLayoutCanvasController {
Object target = chain.getThisObject();
Object alphaArg = firstArg(chain.getArgs());
if (target instanceof View view && alphaArg instanceof Float alpha) {
if (shouldSuppressPrivacyOverlayStatusBarAlpha(view, alpha)) {
return null;
}
if (shouldSuppressConflictingShadeHeaderAlpha(view, alpha)) {
return null;
}
@@ -421,7 +464,11 @@ final class StockLayoutCanvasController {
rememberAodPluginVisualAlpha(view, alpha);
}
}
return chain.proceed();
Object result = chain.proceed();
if (target instanceof View view) {
syncUnlockedHostsFromStockStatusBar(view);
}
return result;
});
XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), viewClass, "setVisibility", chain -> {
Object target = chain.getThisObject();
@@ -437,6 +484,7 @@ final class StockLayoutCanvasController {
Object result = chain.proceed();
if (target instanceof View view && visibilityArg instanceof Integer visibility) {
scheduleStatusChipRefreshIfContentChanged(view);
syncUnlockedHostsFromStockStatusBar(view);
}
return result;
});
@@ -448,7 +496,25 @@ final class StockLayoutCanvasController {
return null;
}
}
return chain.proceed();
Object result = chain.proceed();
if (target instanceof View view) {
syncUnlockedHostsFromStockStatusBar(view);
}
return result;
});
XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), viewClass, "setTranslationX", chain -> {
Object target = chain.getThisObject();
Object translationArg = firstArg(chain.getArgs());
if (target instanceof View view && translationArg instanceof Float translationX) {
if (shouldSuppressPrivacyOverlayStatusBarTranslationX(view, translationX)) {
return null;
}
}
Object result = chain.proceed();
if (target instanceof View view) {
syncUnlockedHostsFromStockStatusBar(view);
}
return result;
});
XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), viewClass, "layout", chain -> {
Object target = chain.getThisObject();
@@ -465,15 +531,21 @@ final class StockLayoutCanvasController {
right,
bottom);
if (override != null) {
return chain.proceed(new Object[]{
Object result = chain.proceed(new Object[]{
override.left,
override.top,
override.right,
override.bottom
});
syncUnlockedHostsFromStockStatusBar(view);
return result;
}
}
return chain.proceed();
Object result = chain.proceed();
if (target instanceof View view) {
syncUnlockedHostsFromStockStatusBar(view);
}
return result;
});
}
@@ -1491,6 +1563,9 @@ final class StockLayoutCanvasController {
}
private void markStatusChipLifecycleDirtyIfNeeded(View view, View fallbackRoot) {
if (RuntimeMutationGuard.isSnapshotRenderMutation()) {
return;
}
if (view == null || !isStatusChipCandidate(view)) {
View root = view != null ? unlockedStatusBarRootFor(view) : fallbackRoot;
if (deferDrawerStatusChipRefresh(root)) {
@@ -1919,12 +1994,15 @@ final class StockLayoutCanvasController {
if (current.isUnlocked() && !previous.isUnlocked()) {
darkIconDispatcherGuard.restoreForcedStates();
trackedLockedStatusBarIconSurfaces.clear();
lockedStatusBarIconSurfaceReadyRoots.clear();
} else if (previous.isLockscreen() && current.isAod()) {
darkIconDispatcherGuard.restoreForcedStates();
hideTrackedLockedStatusBarIconSurfaces(null);
lockedStatusBarIconSurfaceReadyRoots.clear();
} else if (previous.isLockscreen() && !current.isLockscreen()) {
darkIconDispatcherGuard.restoreForcedStates();
restoreTrackedLockedStatusBarIconSurfaces(null);
lockedStatusBarIconSurfaceReadyRoots.clear();
}
boolean lockscreenEntry = current.isLockscreen();
if (lockscreenEntry) {
@@ -1965,6 +2043,7 @@ final class StockLayoutCanvasController {
}
unlockedIconRenderController.clearRoot(root);
dirtyUnlockedLayoutRoots.add(root);
lockedStatusBarIconSurfaceReadyRoots.remove(root);
if (isViewThread(root)) {
applyToRoot(root);
} else {
@@ -2113,6 +2192,83 @@ final class StockLayoutCanvasController {
debugBoundsOverlayController.bringToFront(root);
}
private void syncUnlockedHostsFromStockStatusBar(View view) {
if (!isPhoneStatusBarRoot(view)) {
return;
}
View root = unlockedStatusBarRootWithUnlockedHostsFor(view);
if (root == null) {
return;
}
syncUnlockedHostsToStockStatusBar(
root,
runtimeContext.getModeStateRepository().getActiveScene());
}
private void syncUnlockedHostsToStockStatusBar(View root, SceneKey activeScene) {
if (root == null || !hasDirectUnlockedLayoutHost(root)) {
return;
}
if (activeScene == null || !activeScene.isUnlocked()) {
ownedIconHostManager.setUnlockedHostsTransform(root, 1f, 0f, 0f);
return;
}
View stockStatusBar = phoneStatusBarRootIn(root);
if (stockStatusBar == null || stockStatusBar == root) {
ownedIconHostManager.setUnlockedHostsTransform(root, 1f, 0f, 0f);
return;
}
float alpha = stockStatusBar.getVisibility() == View.VISIBLE
? clampAlpha(stockStatusBar.getAlpha())
: 0f;
float translationX = stockStatusBar.getLeft() + stockStatusBar.getTranslationX();
float translationY = stockStatusBar.getTop() + stockStatusBar.getTranslationY();
ownedIconHostManager.setUnlockedHostsTransform(
root,
alpha,
translationX,
translationY);
}
private View unlockedStatusBarRootWithUnlockedHostsFor(View view) {
View current = view;
while (current != null) {
if (isUnlockedStatusBarRoot(current) && hasDirectUnlockedLayoutHost(current)) {
return current;
}
Object parent = current.getParent();
current = parent instanceof View parentView ? parentView : null;
}
View root = view.getRootView();
return isUnlockedStatusBarRoot(root) && hasDirectUnlockedLayoutHost(root) ? root : null;
}
private View phoneStatusBarRootIn(View root) {
if (root == null) {
return null;
}
if (isPhoneStatusBarRoot(root)) {
return root;
}
if (!(root instanceof ViewGroup group)) {
return null;
}
ArrayDeque<View> queue = new ArrayDeque<>();
queue.add(group);
while (!queue.isEmpty()) {
View current = queue.removeFirst();
if (current != root && isPhoneStatusBarRoot(current)) {
return current;
}
if (current instanceof ViewGroup childGroup) {
for (int i = 0; i < childGroup.getChildCount(); i++) {
queue.addLast(childGroup.getChildAt(i));
}
}
}
return null;
}
private void applyToRootInternal(View root) {
if (root == null) {
return;
@@ -2225,11 +2381,14 @@ final class StockLayoutCanvasController {
activeScene,
rootDirty,
lockedCarrierTextSource());
boolean shouldShowUnlockedHosts = unlockedScene || aodScene || renderResult.shouldOwnLockscreen();
boolean shouldShowUnlockedHosts = unlockedScene
|| aodScene
|| (!unlockedScene && renderResult.shouldOwnLockscreen());
ownedIconHostManager.setUnlockedHostsVisible(root, shouldShowUnlockedHosts);
if (shouldShowUnlockedHosts) {
bringUnlockedHostsAndDebugOverlayToFront(root);
}
syncUnlockedHostsToStockStatusBar(root, activeScene);
syncAodOwnedHostVisualState(root, unlockedHosts);
}
}
@@ -2329,6 +2488,17 @@ final class StockLayoutCanvasController {
|| findDirectOwnedHost(group, OwnedIconHostManager.TAG_AOD_CARDS_NOTIFICATION_HOST) != null;
}
private boolean hasDirectUnlockedLayoutHost(View root) {
if (!(root instanceof ViewGroup group)) {
return false;
}
return findDirectOwnedHost(group, OwnedIconHostManager.TAG_UNLOCKED_STATUS_HOST) != null
|| findDirectOwnedHost(group, OwnedIconHostManager.TAG_UNLOCKED_NOTIFICATION_HOST) != null
|| findDirectOwnedHost(group, OwnedIconHostManager.TAG_UNLOCKED_CLOCK_HOST) != null
|| findDirectOwnedHost(group, OwnedIconHostManager.TAG_UNLOCKED_CARRIER_HOST) != null
|| findDirectOwnedHost(group, OwnedIconHostManager.TAG_UNLOCKED_CHIP_HOST) != null;
}
private boolean hasLockscreenCardsNotificationHost(View root) {
return root instanceof ViewGroup group
&& findDirectOwnedHost(
@@ -2437,8 +2607,7 @@ final class StockLayoutCanvasController {
}
if (!isTrackableStatusChipSurface(view)
&& !isTrackedHiddenStatusChipSource(root, view)) {
hiddenViewStates.remove(view);
trackedUnlockedStockSurfaces.remove(view);
forgetTrackedStatusChipState(view);
staleViews.add(view);
continue;
}
@@ -2918,9 +3087,16 @@ final class StockLayoutCanvasController {
}
private void hideOrDiscoverLockedStatusBarIconSurfaces(View root) {
hideTrackedLockedStatusBarIconSurfaces(root);
if (root != null) {
trackAndHideLockedStatusBarIconSurfaces(root);
if (root != null && lockedStatusBarIconSurfaceReadyRoots.contains(root)) {
return;
}
boolean hasTrackedSurfacesInRoot = hideTrackedLockedStatusBarIconSurfaces(root);
int discovered = 0;
if (!hasTrackedSurfacesInRoot && root != null) {
discovered = trackAndHideLockedStatusBarIconSurfaces(root);
}
if (root != null && (hasTrackedSurfacesInRoot || discovered > 0)) {
lockedStatusBarIconSurfaceReadyRoots.add(root);
}
}
@@ -2928,30 +3104,29 @@ final class StockLayoutCanvasController {
if (trackedLockedStatusBarIconSurfaces.isEmpty()) {
return false;
}
boolean foundInRoot = false;
boolean foundActiveInRoot = false;
ArrayDeque<View> staleViews = new ArrayDeque<>();
for (View view : trackedLockedStatusBarIconSurfaces) {
if (view == null || !view.isAttachedToWindow()) {
staleViews.add(view);
continue;
}
if (root == null || isDescendantOf(view, root)) {
foundInRoot = true;
if (isAlreadySuppressedHiddenView(view)) {
continue;
}
}
boolean inRoot = root == null || isDescendantOf(view, root);
if (!isLockedStatusBarIconSurface(view)) {
restoreView(view);
staleViews.add(view);
continue;
}
if (root == null || isDescendantOf(view, root)) {
if (inRoot) {
foundActiveInRoot = true;
if (isAlreadySuppressedHiddenView(view)) {
continue;
}
hideView(view, isCarrierView(view));
}
}
removeStaleViews(trackedLockedStatusBarIconSurfaces, staleViews);
return foundInRoot;
return foundActiveInRoot;
}
private boolean isAlreadySuppressedHiddenView(View view) {
@@ -2989,6 +3164,7 @@ final class StockLayoutCanvasController {
private boolean isTrackedStatusChipWriteTarget(View view) {
if (view == null
|| ownHiddenViewAlphaWriteDepth > 0
|| RuntimeMutationGuard.isSnapshotRenderMutation()
|| !trackedStatusChipSources.contains(view)
|| !hiddenViewStates.containsKey(view)
|| !view.isAttachedToWindow()
@@ -3008,6 +3184,7 @@ final class StockLayoutCanvasController {
private boolean isStatusChipShellWriteTarget(View view) {
if (view == null
|| ownHiddenViewAlphaWriteDepth > 0
|| RuntimeMutationGuard.isSnapshotRenderMutation()
|| !view.isAttachedToWindow()
|| !isLayoutEnabled(view.getContext())
|| !isStatusChipShell(view)) {
@@ -3111,6 +3288,11 @@ final class StockLayoutCanvasController {
}
}
removeStaleViews(trackedLockedStatusBarIconSurfaces, restoredViews);
if (root != null) {
lockedStatusBarIconSurfaceReadyRoots.remove(root);
} else {
lockedStatusBarIconSurfaceReadyRoots.clear();
}
}
private boolean hasTrackedStatusChipSources(View root) {
@@ -3344,6 +3526,71 @@ final class StockLayoutCanvasController {
return true;
}
private boolean shouldSuppressPrivacyOverlayStatusBarAlpha(View view, float alpha) {
if (alpha >= 0.99f
|| ownHiddenViewAlphaWriteDepth > 0
|| !isPhoneStatusBarRoot(view)
|| !isLayoutEnabled(view.getContext())
|| ongoingPrivacyChipInRoot(view) == null) {
return false;
}
if (alpha <= 0f && !statusBarsRequestedVisible()) {
return false;
}
return true;
}
private boolean shouldSuppressPrivacyOverlayStatusBarTranslationX(View view, float translationX) {
if (Math.abs(translationX) < 0.5f
|| ownHiddenViewAlphaWriteDepth > 0
|| !isPhoneStatusBarRoot(view)
|| !isLayoutEnabled(view.getContext())
|| ongoingPrivacyChipInRoot(view) == null) {
return false;
}
return true;
}
private boolean statusBarsRequestedVisible() {
return (systemBarRequestedVisibleTypes & WindowInsets.Type.statusBars()) != 0;
}
private static Integer asInteger(Object value) {
return value instanceof Integer integer ? integer : null;
}
private boolean isPhoneStatusBarRoot(View view) {
return view != null
&& view.getClass().getName().contains("PhoneStatusBarView");
}
private boolean isOngoingPrivacyChip(View view) {
return view != null
&& view.getClass().getName().contains("OngoingPrivacyChip")
&& "privacy_chip".equals(ViewIdNames.idName(view));
}
private View ongoingPrivacyChipInRoot(View view) {
View root = view != null ? view.getRootView() : null;
if (!(root instanceof ViewGroup group)) {
return null;
}
ArrayDeque<View> queue = new ArrayDeque<>();
queue.add(group);
while (!queue.isEmpty()) {
View current = queue.removeFirst();
if (isOngoingPrivacyChip(current)) {
return current;
}
if (current instanceof ViewGroup childGroup) {
for (int i = 0; i < childGroup.getChildCount(); i++) {
queue.addLast(childGroup.getChildAt(i));
}
}
}
return null;
}
private boolean isStatusChipSource(View view) {
if (view == null || view.getVisibility() != View.VISIBLE) {
return false;
@@ -3384,6 +3631,9 @@ final class StockLayoutCanvasController {
if (view == null || ownedIconHostManager.isOwnedHost(view)) {
return false;
}
if (view.getVisibility() != View.VISIBLE) {
return false;
}
String idName = ViewIdNames.idName(view);
if (!"ongoing_activity_capsule".equals(idName)) {
return false;
@@ -3412,9 +3662,26 @@ final class StockLayoutCanvasController {
|| !isStatusChipCandidate(view)) {
return false;
}
if (isCallStatusChip(view)
&& view.getVisibility() != View.VISIBLE
&& !isPhoneCallActive(view)) {
return false;
}
return hasVisibleStatusChipContent(view);
}
private boolean isPhoneCallActive(View view) {
if (view == null || view.getContext() == null) {
return false;
}
try {
TelecomManager telecomManager = view.getContext().getSystemService(TelecomManager.class);
return telecomManager != null && telecomManager.isInCall();
} catch (RuntimeException ignored) {
return false;
}
}
private boolean hasVisibleStatusChipContent(View source) {
if (!(source instanceof ViewGroup group)) {
return false;
@@ -4230,11 +4497,19 @@ final class StockLayoutCanvasController {
|| isStatusChipShell(view)) {
return false;
}
hiddenViewStates.remove(view);
forgetTrackedStatusChip(view);
return true;
}
private void forgetTrackedStatusChip(View view) {
forgetTrackedStatusChipState(view);
trackedStatusChipSources.remove(view);
}
private void forgetTrackedStatusChipState(View view) {
hiddenViewStates.remove(view);
trackedUnlockedStockSurfaces.remove(view);
statusChipLifecycleLayoutSignatureByView.remove(view);
return true;
}
private void cleanupLayoutOffRoot(View root, SceneKey activeScene) {
@@ -4244,6 +4519,7 @@ final class StockLayoutCanvasController {
}
clearTrackedStatusChipState();
trackedLockedStatusBarIconSurfaces.clear();
lockedStatusBarIconSurfaceReadyRoots.clear();
trackedUnlockedStockSurfaces.clear();
confirmedAodPluginViews.clear();
aodPluginVisualAlphaByView.clear();
@@ -119,6 +119,17 @@ public final class OwnedIconHostManager {
}
}
public void setUnlockedHostsTransform(
View root,
float alpha,
float translationX,
float translationY
) {
for (String tag : UNLOCKED_HOST_ORDER) {
setHostTransform(root, tag, alpha, translationX, translationY);
}
}
public void bringUnlockedHostsToFront(View root) {
if (!(root instanceof ViewGroup parent)) {
return;
@@ -169,6 +180,31 @@ public final class OwnedIconHostManager {
}
}
private void setHostTransform(
View root,
String tag,
float alpha,
float translationX,
float translationY
) {
if (!(root instanceof ViewGroup parent) || tag == null) {
return;
}
FrameLayout host = findDirectHost(parent, tag);
if (host == null) {
return;
}
if (host.getAlpha() != alpha) {
host.setAlpha(alpha);
}
if (host.getTranslationX() != translationX) {
host.setTranslationX(translationX);
}
if (host.getTranslationY() != translationY) {
host.setTranslationY(translationY);
}
}
private ArrayList<View> unlockedHosts(ViewGroup parent) {
ArrayList<View> hosts = new ArrayList<>();
for (String tag : UNLOCKED_HOST_ORDER) {
@@ -11,6 +11,7 @@ import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.BatteryManager;
@@ -35,6 +36,7 @@ import java.util.Objects;
import java.util.WeakHashMap;
import se.ajpanton.statusbartweak.runtime.NotificationKeyReflection;
import se.ajpanton.statusbartweak.runtime.RuntimeMutationGuard;
import se.ajpanton.statusbartweak.runtime.ViewIdNames;
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds;
@@ -238,9 +240,24 @@ final class SnapshotRenderer {
proxy.setTag(key);
proxyChanged = true;
}
SnapshotRenderState desiredState = renderStateFor(placement);
Drawable animatedDrawable = animatedNotificationDrawable(placement);
if (animatedDrawable != null) {
SnapshotRenderState currentState = statesByProxy.get(proxy);
boolean drawableChanged = !(proxy.getDrawable() instanceof AnimationDrawable)
|| !canReuseAnimatedProxy(currentState, desiredState);
if (drawableChanged) {
Drawable proxyDrawable = newDrawableInstance(animatedDrawable);
proxy.setImageDrawable(proxyDrawable);
proxyChanged = true;
}
startAnimationIfNeeded(proxy.getDrawable());
if (!desiredState.equals(currentState)) {
statesByProxy.put(proxy, desiredState);
}
} else {
BitmapResult bitmapResult = bitmapFor(proxy, bounds);
Bitmap bitmap = bitmapResult.bitmap();
SnapshotRenderState desiredState = renderStateFor(placement);
boolean bitmapChanged = bitmapResult.created()
|| !desiredState.equals(statesByProxy.get(proxy));
if (bitmapChanged) {
@@ -268,6 +285,7 @@ final class SnapshotRenderer {
proxy.setImageBitmap(bitmap);
proxyChanged = true;
}
}
if (proxy.getAlpha() != 1f) {
proxy.setAlpha(1f);
proxyChanged = true;
@@ -304,6 +322,52 @@ final class SnapshotRenderer {
return renderedBounds;
}
private boolean canReuseAnimatedProxy(
SnapshotRenderState currentState,
SnapshotRenderState desiredState
) {
return currentState != null
&& desiredState != null
&& Objects.equals(currentState.key(), desiredState.key())
&& currentState.width() == desiredState.width()
&& currentState.height() == desiredState.height()
&& currentState.signal() == desiredState.signal()
&& currentState.overflowDot() == desiredState.overflowDot()
&& currentState.overflowDotColor() == desiredState.overflowDotColor()
&& currentState.sourceOffsetX() == desiredState.sourceOffsetX()
&& currentState.sourceRenderBoundsSignature() == desiredState.sourceRenderBoundsSignature()
&& Objects.equals(currentState.heldSignalKey(), desiredState.heldSignalKey())
&& currentState.heldBitmapId() == desiredState.heldBitmapId()
&& currentState.sourceMode() == desiredState.sourceMode()
&& currentState.tintSignature() == desiredState.tintSignature();
}
private Drawable animatedNotificationDrawable(SnapshotPlacement placement) {
if (placement == null
|| placement.source() == null
|| placement.source().mode() != SnapshotMode.NOTIFICATION_DRAWABLE
|| !(placement.source().view() instanceof ImageView imageView)) {
return null;
}
Drawable drawable = imageView.getDrawable();
return drawable instanceof AnimationDrawable ? drawable : null;
}
private Drawable newDrawableInstance(Drawable drawable) {
Drawable.ConstantState constantState = drawable != null ? drawable.getConstantState() : null;
if (constantState != null) {
return constantState.newDrawable().mutate();
}
return drawable != null ? drawable.mutate() : null;
}
private void startAnimationIfNeeded(Drawable drawable) {
if (drawable instanceof AnimationDrawable animationDrawable && !animationDrawable.isRunning()) {
animationDrawable.setVisible(true, true);
animationDrawable.start();
}
}
void clear(ViewGroup host) {
LinkedHashMap<String, ImageView> proxies = proxiesByHost.remove(host);
if (proxies == null) {
@@ -748,6 +812,8 @@ final class SnapshotRenderer {
bitmap.getHeight())) {
return;
}
RuntimeMutationGuard.enterSnapshotRenderMutation();
try {
sourceView.setVisibility(View.VISIBLE);
sourceView.setAlpha(1f);
if (source.mode() == SnapshotMode.STATUS_CHIP) {
@@ -756,8 +822,16 @@ final class SnapshotRenderer {
sourceView.draw(canvas);
}
} finally {
RuntimeMutationGuard.exitSnapshotRenderMutation();
}
} finally {
RuntimeMutationGuard.enterSnapshotRenderMutation();
try {
sourceView.setAlpha(oldAlpha);
sourceView.setVisibility(oldVisibility);
} finally {
RuntimeMutationGuard.exitSnapshotRenderMutation();
}
}
}
@@ -1183,7 +1257,9 @@ final class SnapshotRenderer {
view.getRight(),
view.getBottom(),
view.getMeasuredWidth(),
view.getMeasuredHeight()));
view.getMeasuredHeight(),
view.getVisibility(),
view.getAlpha()));
if (view instanceof ViewGroup group) {
for (int i = 0; i < group.getChildCount(); i++) {
captureFrameState(group.getChildAt(i), state);
@@ -1207,6 +1283,12 @@ final class SnapshotRenderer {
View.MeasureSpec.makeMeasureSpec(frame.measuredHeight, View.MeasureSpec.EXACTLY));
}
view.layout(frame.left, frame.top, frame.right, frame.bottom);
if (view.getAlpha() != frame.alpha) {
view.setAlpha(frame.alpha);
}
if (view.getVisibility() != frame.visibility) {
view.setVisibility(frame.visibility);
}
}
}
@@ -1221,7 +1303,9 @@ final class SnapshotRenderer {
int right,
int bottom,
int measuredWidth,
int measuredHeight
int measuredHeight,
int visibility,
float alpha
) {
}
@@ -21,6 +21,10 @@ final class UnlockedChipPlacementController {
}
void clear() {
resetMissingChipFallback();
}
private void resetMissingChipFallback() {
heldPlacements = null;
lastCollisionBounds = null;
lastRootWidth = 0;
@@ -47,10 +51,11 @@ final class UnlockedChipPlacementController {
return rawPlacements;
}
ArrayList<SnapshotPlacement> placements = isAnyChipHidingEnabled(settings)
boolean hasMetricSources = metricSources != null && !metricSources.isEmpty();
ArrayList<SnapshotPlacement> placements = isAnyChipHidingEnabled(settings) || !hasMetricSources
? new ArrayList<>()
: heldPlacements(root);
if (placements.isEmpty()) {
if (placements.isEmpty() && hasMetricSources) {
placements = emptyPlacement(root, metricSources);
}
return placements;
@@ -88,7 +88,13 @@ final class UnlockedLayoutPlanner {
Bounds aodStatusbarBounds = scene != null && scene.isAod()
? aodStatusbarBounds(root, settings, aodNotificationBounds, aodStatusAnchorBounds)
: null;
LayoutEdges edges = layoutEdges(root, settings, anchors, scene, aodStatusAnchorBounds);
LayoutEdges edges = layoutEdges(
root,
settings,
anchors,
collectedIcons,
scene,
aodStatusAnchorBounds);
int statusbarHeight = statusbarHeight(root, anchors, scene, aodStatusContentBounds);
if (clockEnabledForScene(settings, scene) && clockLayout != null && clockLayout.width() > 0) {
bands.add(UnlockedLayoutBand.clock(
@@ -1393,6 +1399,7 @@ final class UnlockedLayoutPlanner {
View root,
SbtSettings settings,
RootAnchors anchors,
CollectedIcons collectedIcons,
SceneKey scene,
Bounds aodStatusAnchorBounds
) {
@@ -1419,11 +1426,29 @@ final class UnlockedLayoutPlanner {
if (settings.layoutPaddingRightAddStock) {
right -= stockInsets.right;
}
if (scene != null && scene.isUnlocked() && hasStatusChipSources(collectedIcons)) {
LayoutEdges recentEdges = recentNonAodEdgesByRootWidth.get(rootWidth);
if (isReusableUnlockedEdge(left, recentEdges)) {
left = recentEdges.left();
}
}
LayoutEdges edges = new LayoutEdges(left, right);
rememberNonAodEdges(rootWidth, scene, edges);
return edges;
}
private boolean hasStatusChipSources(CollectedIcons icons) {
return icons != null
&& ((icons.statusChips != null && !icons.statusChips.isEmpty())
|| (icons.statusChipMetrics != null && !icons.statusChipMetrics.isEmpty()));
}
private boolean isReusableUnlockedEdge(int currentLeft, LayoutEdges recentEdges) {
return recentEdges != null
&& recentEdges.right() > recentEdges.left()
&& Math.abs(currentLeft - recentEdges.left()) <= 8;
}
private LayoutEdges stableAodEdges(int rootWidth, LayoutEdges aodEdges) {
if (rootWidth <= 0) {
return aodEdges;
@@ -1188,6 +1188,7 @@ final class UnlockedStockSourceCollector {
View view = source.view();
if (view == null
|| view == root
|| view.getVisibility() != View.VISIBLE
|| !view.isAttachedToWindow()
|| !ViewGeometry.isDescendantOf(view, root)
|| ReflectionSupport.getBooleanField(view, "sbtStatusChipForcedHidden", false)
@@ -1301,7 +1302,7 @@ final class UnlockedStockSourceCollector {
}
private boolean isStatusChipMetricCandidate(View root, View view, boolean allowHidden) {
if (view == null || view == root || root == null) {
if (view == null || view == root || root == null || view.getVisibility() != View.VISIBLE) {
return false;
}
if (!allowHidden && view.getAlpha() <= 0f && !hasRenderableStatusChipContent(view)) {
@@ -17,6 +17,7 @@ import android.view.inputmethod.EditorInfo;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
@@ -859,15 +860,30 @@ public final class LayoutFragment extends Fragment {
}
private int resolveLeftSideId(RadioGroup group) {
int id = group.getChildCount() > 0 ? group.getChildAt(0).getId() : View.NO_ID;
int id = sideButtonId(group, 0);
return id != View.NO_ID ? id : 0;
}
private int resolveRightSideId(RadioGroup group) {
int id = group.getChildCount() > 1 ? group.getChildAt(1).getId() : View.NO_ID;
int id = sideButtonId(group, 1);
return id != View.NO_ID ? id : resolveLeftSideId(group);
}
private int sideButtonId(RadioGroup group, int radioIndex) {
int seen = 0;
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (!(child instanceof RadioButton)) {
continue;
}
if (seen == radioIndex) {
return child.getId();
}
seen++;
}
return View.NO_ID;
}
private void setNumericText(EditText input, int value) {
if (input == null) {
return;
@@ -10,6 +10,7 @@ import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.TextView;
@@ -775,7 +776,7 @@ abstract class LockedSceneLayoutFragment extends Fragment {
private int firstSideId(RadioGroup group, int rightId) {
for (int i = 0; i < group.getChildCount(); i++) {
View child = group.getChildAt(i);
if (child.getId() != rightId) {
if (child instanceof RadioButton && child.getId() != rightId) {
return child.getId();
}
}
+1 -1
View File
@@ -1 +1 @@
version=0.3
version=0.5