Finish lockscreen cards follow-ups
This commit is contained in:
+416
@@ -0,0 +1,416 @@
|
||||
package se.ajpanton.statusbartweak.runtime.layout;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import se.ajpanton.statusbartweak.runtime.hooks.ReflectionSupport;
|
||||
import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle;
|
||||
import se.ajpanton.statusbartweak.runtime.mode.SystemNotificationDisplayStyleDetector;
|
||||
import se.ajpanton.statusbartweak.runtime.settings.RuntimeSettingsCache;
|
||||
import se.ajpanton.statusbartweak.shell.settings.AppIconRules;
|
||||
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
|
||||
|
||||
final class LockscreenCardsNotificationRowFilter {
|
||||
private static final String MODE_LOCK = "LOCK";
|
||||
|
||||
private final Map<View, HiddenRowState> hiddenRows = new WeakHashMap<>();
|
||||
|
||||
boolean apply(ViewGroup stack) {
|
||||
return apply(stack, false);
|
||||
}
|
||||
|
||||
boolean apply(ViewGroup stack, boolean allowAfterKeyguardUnlock) {
|
||||
if (stack == null) {
|
||||
return false;
|
||||
}
|
||||
Context context = stack.getContext();
|
||||
boolean active = isActive(context, allowAfterKeyguardUnlock);
|
||||
boolean changed = false;
|
||||
int childCount = stack.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = stack.getChildAt(i);
|
||||
if (!isNotificationRow(child)) {
|
||||
continue;
|
||||
}
|
||||
if (active && shouldHideRow(context, child, allowAfterKeyguardUnlock)) {
|
||||
changed |= hideRow(child);
|
||||
} else {
|
||||
changed |= restoreRow(child);
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
requestLayout(stack);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
void restoreAll() {
|
||||
if (hiddenRows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ArrayList<View> rows = new ArrayList<>(hiddenRows.keySet());
|
||||
for (View row : rows) {
|
||||
restoreRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
void suppressHiddenRows() {
|
||||
if (hiddenRows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ArrayList<View> rows = new ArrayList<>(hiddenRows.keySet());
|
||||
for (View row : rows) {
|
||||
if (row == null || !row.isAttachedToWindow() || !isNotificationRow(row)) {
|
||||
hiddenRows.remove(row);
|
||||
continue;
|
||||
}
|
||||
hideRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isActive(Context context) {
|
||||
return isActive(context, false);
|
||||
}
|
||||
|
||||
private boolean isActive(Context context, boolean allowAfterKeyguardUnlock) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
SbtSettings settings = RuntimeSettingsCache.get(context);
|
||||
if (settings == null || !settings.clockEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (settings.appIconBlockedModes.isEmpty() && settings.appIconExceptionRules.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (SystemNotificationDisplayStyleDetector.read(context) != NotificationDisplayStyle.CARDS) {
|
||||
return false;
|
||||
}
|
||||
KeyguardManager keyguard = context.getSystemService(KeyguardManager.class);
|
||||
if (!allowAfterKeyguardUnlock && (keyguard == null || !keyguard.isKeyguardLocked())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean shouldHideEntry(Context context, Object entry) {
|
||||
return shouldHideEntry(context, entry, false);
|
||||
}
|
||||
|
||||
private boolean shouldHideEntry(Context context, Object entry, boolean allowAfterKeyguardUnlock) {
|
||||
if (!isActive(context, allowAfterKeyguardUnlock)) {
|
||||
return false;
|
||||
}
|
||||
SbtSettings settings = RuntimeSettingsCache.get(context);
|
||||
String packageName = packageFor(entry);
|
||||
if (!AppIconRules.isValidPackageName(packageName)) {
|
||||
return false;
|
||||
}
|
||||
String text = matchText(null, entry);
|
||||
AppIconRules.ExceptionRule exceptionRule =
|
||||
matchingExceptionRule(text, settings.appIconExceptionRules.get(packageName));
|
||||
return exceptionRule != null
|
||||
? exceptionRule.blocksMode(MODE_LOCK)
|
||||
: AppIconRules.isBlocked(settings.appIconBlockedModes, packageName, MODE_LOCK);
|
||||
}
|
||||
|
||||
private boolean shouldHideRow(Context context, View row, boolean allowAfterKeyguardUnlock) {
|
||||
Object entry = entryForRow(row);
|
||||
if (shouldHideEntry(context, entry, allowAfterKeyguardUnlock)) {
|
||||
return true;
|
||||
}
|
||||
SbtSettings settings = RuntimeSettingsCache.get(context);
|
||||
String packageName = packageFor(row);
|
||||
if (!AppIconRules.isValidPackageName(packageName)) {
|
||||
return false;
|
||||
}
|
||||
String text = matchText(row, entry);
|
||||
AppIconRules.ExceptionRule exceptionRule =
|
||||
matchingExceptionRule(text, settings.appIconExceptionRules.get(packageName));
|
||||
if (exceptionRule != null) {
|
||||
return exceptionRule.blocksMode(MODE_LOCK);
|
||||
}
|
||||
return AppIconRules.isBlocked(settings.appIconBlockedModes, packageName, MODE_LOCK);
|
||||
}
|
||||
|
||||
private boolean hideRow(View row) {
|
||||
if (row == null) {
|
||||
return false;
|
||||
}
|
||||
if (row.getVisibility() == View.GONE && row.getAlpha() == 0f) {
|
||||
return false;
|
||||
}
|
||||
hiddenRows.computeIfAbsent(row, ignored -> new HiddenRowState(View.VISIBLE, 1f));
|
||||
boolean changed = false;
|
||||
if (row.getVisibility() != View.GONE) {
|
||||
row.setVisibility(View.GONE);
|
||||
changed = true;
|
||||
}
|
||||
if (row.getAlpha() != 0f) {
|
||||
row.setAlpha(0f);
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private boolean restoreRow(View row) {
|
||||
if (row == null) {
|
||||
return false;
|
||||
}
|
||||
HiddenRowState state = hiddenRows.remove(row);
|
||||
if (state == null) {
|
||||
return false;
|
||||
}
|
||||
boolean changed = false;
|
||||
if (row.getVisibility() != state.visibility) {
|
||||
row.setVisibility(state.visibility);
|
||||
changed = true;
|
||||
}
|
||||
if (row.getAlpha() != state.alpha) {
|
||||
row.setAlpha(state.alpha);
|
||||
changed = true;
|
||||
}
|
||||
changed |= normalizeRowState(row);
|
||||
requestLayout(row);
|
||||
return changed;
|
||||
}
|
||||
|
||||
private boolean normalizeRowState(View row) {
|
||||
boolean changed = false;
|
||||
changed |= setBooleanField(row, "mWillBeGone", false);
|
||||
changed |= setBooleanField(row, "mInShelf", false);
|
||||
changed |= setBooleanField(row, "mTransformingInShelf", false);
|
||||
Object viewState = ReflectionSupport.getFieldValue(row, "mViewState");
|
||||
if (viewState != null) {
|
||||
changed |= setBooleanField(viewState, "hidden", false);
|
||||
changed |= setBooleanField(viewState, "gone", false);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private boolean setBooleanField(Object target, String fieldName, boolean value) {
|
||||
Object current = ReflectionSupport.getFieldValue(target, fieldName);
|
||||
if (!(current instanceof Boolean) || (Boolean) current == value) {
|
||||
return false;
|
||||
}
|
||||
ReflectionSupport.setFieldValue(target, fieldName, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void requestLayout(View view) {
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
view.requestLayout();
|
||||
view.invalidate();
|
||||
Object parent = view.getParent();
|
||||
if (parent instanceof View parentView) {
|
||||
parentView.requestLayout();
|
||||
parentView.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isNotificationRow(View view) {
|
||||
return view != null && view.getClass().getName().contains("ExpandableNotificationRow");
|
||||
}
|
||||
|
||||
private Object entryForRow(View row) {
|
||||
Object value = ReflectionSupport.invokeMethod(row, "getEntry", new Class<?>[0]);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
value = ReflectionSupport.getFieldValue(row, "mEntry");
|
||||
return value != null ? value : ReflectionSupport.getFieldValue(row, "entry");
|
||||
}
|
||||
|
||||
private String packageFor(Object target) {
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
StatusBarNotification sbn = statusBarNotification(target);
|
||||
if (sbn != null && AppIconRules.isValidPackageName(sbn.getPackageName())) {
|
||||
return sbn.getPackageName();
|
||||
}
|
||||
String key = notificationKey(target);
|
||||
String fromKey = packageFromNotificationKey(key);
|
||||
if (AppIconRules.isValidPackageName(fromKey)) {
|
||||
return fromKey;
|
||||
}
|
||||
Object packageName = ReflectionSupport.invokeMethod(target, "getPackageName", new Class<?>[0]);
|
||||
if (packageName instanceof String string && AppIconRules.isValidPackageName(string)) {
|
||||
return string;
|
||||
}
|
||||
packageName = ReflectionSupport.getFieldValue(target, "packageName");
|
||||
if (packageName instanceof String string && AppIconRules.isValidPackageName(string)) {
|
||||
return string;
|
||||
}
|
||||
packageName = ReflectionSupport.getFieldValue(target, "mPackageName");
|
||||
if (packageName instanceof String string && AppIconRules.isValidPackageName(string)) {
|
||||
return string;
|
||||
}
|
||||
if (target instanceof View view) {
|
||||
Object entry = entryForRow(view);
|
||||
String fromEntry = packageFor(entry);
|
||||
if (AppIconRules.isValidPackageName(fromEntry)) {
|
||||
return fromEntry;
|
||||
}
|
||||
CharSequence description = view.getContentDescription();
|
||||
if (description != null) {
|
||||
String fromDescription = packageFromNotificationKey(description.toString());
|
||||
if (AppIconRules.isValidPackageName(fromDescription)) {
|
||||
return fromDescription;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String notificationKey(Object target) {
|
||||
Object key = ReflectionSupport.invokeMethod(target, "getKey", new Class<?>[0]);
|
||||
if (key instanceof String string && !string.isEmpty()) {
|
||||
return string;
|
||||
}
|
||||
key = ReflectionSupport.invokeMethod(target, "getNotificationKey", new Class<?>[0]);
|
||||
if (key instanceof String string && !string.isEmpty()) {
|
||||
return string;
|
||||
}
|
||||
key = ReflectionSupport.getFieldValue(target, "mKey");
|
||||
if (key instanceof String string && !string.isEmpty()) {
|
||||
return string;
|
||||
}
|
||||
key = ReflectionSupport.getFieldValue(target, "key");
|
||||
return key instanceof String string && !string.isEmpty() ? string : null;
|
||||
}
|
||||
|
||||
private StatusBarNotification statusBarNotification(Object target) {
|
||||
if (target instanceof StatusBarNotification sbn) {
|
||||
return sbn;
|
||||
}
|
||||
Object value = ReflectionSupport.invokeMethod(target, "getSbn", new Class<?>[0]);
|
||||
if (value instanceof StatusBarNotification sbn) {
|
||||
return sbn;
|
||||
}
|
||||
for (String field : new String[]{"mSbn", "sbn", "mStatusBarNotification", "statusBarNotification"}) {
|
||||
value = ReflectionSupport.getFieldValue(target, field);
|
||||
if (value instanceof StatusBarNotification sbn) {
|
||||
return sbn;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String packageFromNotificationKey(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
String normalized = key.trim();
|
||||
int suffix = normalized.indexOf('@');
|
||||
if (suffix >= 0) {
|
||||
normalized = normalized.substring(0, suffix);
|
||||
}
|
||||
int slash = normalized.indexOf('/');
|
||||
if (slash >= 0) {
|
||||
normalized = normalized.substring(0, slash);
|
||||
}
|
||||
return AppIconRules.isValidPackageName(normalized) ? normalized : null;
|
||||
}
|
||||
|
||||
private String matchText(View row, Object entry) {
|
||||
StringBuilder out = new StringBuilder();
|
||||
append(out, notificationKey(entry));
|
||||
append(out, notificationKey(row));
|
||||
appendNotification(out, statusBarNotification(entry));
|
||||
appendNotification(out, statusBarNotification(row));
|
||||
if (row != null) {
|
||||
append(out, row.getContentDescription());
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
private void appendNotification(StringBuilder out, StatusBarNotification sbn) {
|
||||
if (sbn == null) {
|
||||
return;
|
||||
}
|
||||
append(out, sbn.getKey());
|
||||
append(out, sbn.getPackageName());
|
||||
Notification notification = sbn.getNotification();
|
||||
Bundle extras = notification != null ? notification.extras : null;
|
||||
if (extras == null) {
|
||||
return;
|
||||
}
|
||||
append(out, extras.getCharSequence(Notification.EXTRA_TITLE));
|
||||
append(out, extras.getCharSequence(Notification.EXTRA_TITLE_BIG));
|
||||
append(out, extras.getCharSequence(Notification.EXTRA_TEXT));
|
||||
append(out, extras.getCharSequence(Notification.EXTRA_BIG_TEXT));
|
||||
append(out, extras.getCharSequence(Notification.EXTRA_SUB_TEXT));
|
||||
append(out, extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT));
|
||||
append(out, extras.getCharSequence(Notification.EXTRA_INFO_TEXT));
|
||||
CharSequence[] lines = extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
|
||||
if (lines != null) {
|
||||
for (CharSequence line : lines) {
|
||||
append(out, line);
|
||||
}
|
||||
}
|
||||
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
|
||||
if (messages != null) {
|
||||
for (Parcelable message : messages) {
|
||||
append(out, message != null ? message.toString() : null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void append(StringBuilder out, CharSequence text) {
|
||||
if (out == null || text == null || text.length() == 0) {
|
||||
return;
|
||||
}
|
||||
if (out.length() > 0) {
|
||||
out.append('\n');
|
||||
}
|
||||
out.append(text);
|
||||
}
|
||||
|
||||
private AppIconRules.ExceptionRule matchingExceptionRule(
|
||||
String text,
|
||||
ArrayList<AppIconRules.ExceptionRule> rules
|
||||
) {
|
||||
if (rules == null || rules.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String candidateText = text != null ? text : "";
|
||||
for (AppIconRules.ExceptionRule rule : rules) {
|
||||
if (rule == null || rule.regex == null || rule.regex.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (Pattern.compile(rule.regex).matcher(candidateText).find()) {
|
||||
return rule;
|
||||
}
|
||||
} catch (PatternSyntaxException ignored) {
|
||||
// Invalid user regexes are ignored instead of blocking every card.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class HiddenRowState {
|
||||
final int visibility;
|
||||
final float alpha;
|
||||
|
||||
HiddenRowState(int visibility, float alpha) {
|
||||
this.visibility = visibility;
|
||||
this.alpha = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
+264
-4
@@ -3,6 +3,7 @@ package se.ajpanton.statusbartweak.runtime.layout;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -69,6 +70,7 @@ final class StockLayoutCanvasController {
|
||||
private final Map<View, RootGeometry> rootGeometries = new WeakHashMap<>();
|
||||
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, Long> clockTimeBucketByRoot = new WeakHashMap<>();
|
||||
private final Set<View> confirmedAodPluginViews =
|
||||
Collections.newSetFromMap(new WeakHashMap<>());
|
||||
@@ -92,6 +94,8 @@ final class StockLayoutCanvasController {
|
||||
new DebugBoundsOverlayController();
|
||||
private final LockscreenCardsNotificationStripRenderer lockscreenCardsNotificationStripRenderer =
|
||||
new LockscreenCardsNotificationStripRenderer();
|
||||
private final LockscreenCardsNotificationRowFilter lockscreenCardsNotificationRowFilter =
|
||||
new LockscreenCardsNotificationRowFilter();
|
||||
private final UnlockedIconSnapshotRenderController unlockedIconRenderController;
|
||||
// The keyguard carrier text lives outside the statusbar root on this device.
|
||||
// Keep a weak source reference so the lockscreen statusbar can render it when
|
||||
@@ -111,11 +115,68 @@ final class StockLayoutCanvasController {
|
||||
}
|
||||
installPluginManagerHooks(classLoader);
|
||||
installCardsNotificationIconSourceHook(classLoader);
|
||||
installLockscreenCardsNotificationRowFilterHook(classLoader);
|
||||
installUnlockedAppearanceInvalidationHooks(classLoader);
|
||||
installShadeHeaderAnimationGuardHook(classLoader);
|
||||
installViewRootHook(classLoader);
|
||||
hooksInstalled = true;
|
||||
}
|
||||
|
||||
private void installShadeHeaderAnimationGuardHook(ClassLoader classLoader) {
|
||||
Class<?> viewClass = ReflectionSupport.requireClass("android.view.View", classLoader);
|
||||
XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), viewClass, "setAlpha", chain -> {
|
||||
Object target = chain.getThisObject();
|
||||
Object alphaArg = firstArg(chain.getArgs());
|
||||
if (target instanceof View view && alphaArg instanceof Float alpha) {
|
||||
if (shouldSuppressConflictingShadeHeaderAlpha(view, alpha)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return chain.proceed();
|
||||
});
|
||||
XposedHookSupport.hookAllMethodsIfExists(runtimeContext.getFramework(), viewClass, "setTranslationY", chain -> {
|
||||
Object target = chain.getThisObject();
|
||||
Object translationArg = firstArg(chain.getArgs());
|
||||
if (target instanceof View view && translationArg instanceof Float translationY) {
|
||||
if (shouldSuppressConflictingShadeHeaderTranslationY(view, translationY)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return chain.proceed();
|
||||
});
|
||||
}
|
||||
|
||||
private void installLockscreenCardsNotificationRowFilterHook(ClassLoader classLoader) {
|
||||
Class<?> stackClass = ReflectionSupport.findClassIfExists(
|
||||
"com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout",
|
||||
classLoader);
|
||||
if (stackClass != null) {
|
||||
XposedHookSupport.hookAllMethodsIfExists(
|
||||
runtimeContext.getFramework(),
|
||||
stackClass,
|
||||
"onViewAdded",
|
||||
chain -> {
|
||||
Object result = chain.proceed();
|
||||
if (chain.getThisObject() instanceof ViewGroup stack) {
|
||||
applyLockscreenCardsNotificationRowFilter(stack);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
XposedHookSupport.hookAllMethodsIfExists(
|
||||
runtimeContext.getFramework(),
|
||||
stackClass,
|
||||
"onViewRemoved",
|
||||
chain -> {
|
||||
Object result = chain.proceed();
|
||||
if (chain.getThisObject() instanceof ViewGroup stack) {
|
||||
applyLockscreenCardsNotificationRowFilter(stack);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void installUnlockedAppearanceInvalidationHooks(ClassLoader classLoader) {
|
||||
Class<?> darkIconDispatcherClass = ReflectionSupport.findClassIfExists(
|
||||
"com.android.systemui.statusbar.phone.DarkIconDispatcherImpl",
|
||||
@@ -296,6 +357,12 @@ final class StockLayoutCanvasController {
|
||||
new ArrayList<>();
|
||||
Function<Object, Object> wrapped = entryObj -> {
|
||||
String key = LockscreenCardsNotificationIconSourceStore.keyForEntry(entryObj);
|
||||
if (!runtimeContext.getModeStateRepository().isSystemUiShadeLocked()
|
||||
&& lockscreenCardsNotificationRowFilter.shouldHideEntry(
|
||||
container.getContext(),
|
||||
entryObj)) {
|
||||
return null;
|
||||
}
|
||||
Object result = original.apply(entryObj);
|
||||
if (result == null) {
|
||||
result = fallbackStatusBarIcon(entryObj);
|
||||
@@ -384,6 +451,10 @@ final class StockLayoutCanvasController {
|
||||
return chain.proceed();
|
||||
});
|
||||
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), viewRootClass, "performTraversals", chain -> {
|
||||
Object rootObjBefore = ReflectionSupport.requireFieldValue(chain.getThisObject(), "mView");
|
||||
if (rootObjBefore instanceof View root) {
|
||||
applyBeforeTraversal(root);
|
||||
}
|
||||
Object result = chain.proceed();
|
||||
Object rootObj = ReflectionSupport.requireFieldValue(chain.getThisObject(), "mView");
|
||||
if (rootObj instanceof View root) {
|
||||
@@ -394,9 +465,44 @@ final class StockLayoutCanvasController {
|
||||
}
|
||||
|
||||
private void applyBeforeDraw(View root) {
|
||||
if (!isViewThread(root)) {
|
||||
return;
|
||||
}
|
||||
applyBeforeDrawInternal(root);
|
||||
}
|
||||
|
||||
private void applyBeforeTraversal(View root) {
|
||||
if (!isViewThread(root)) {
|
||||
return;
|
||||
}
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
||||
if (!settings.clockEnabled) {
|
||||
return;
|
||||
}
|
||||
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||
if (activeScene == null) {
|
||||
return;
|
||||
}
|
||||
if (activeScene.isLockscreen()
|
||||
&& activeScene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS
|
||||
&& isNotificationShadeWindowRoot(root)) {
|
||||
applyLockscreenCardsNotificationRowFilterInTreeIfNeeded(
|
||||
root,
|
||||
settings,
|
||||
activeScene,
|
||||
true);
|
||||
lockscreenCardsNotificationRowFilter.suppressHiddenRows();
|
||||
} else if (activeScene.isUnlocked() && isNotificationShadeWindowRoot(root)) {
|
||||
lockscreenCardsNotificationRowFilter.restoreAll();
|
||||
} else if (activeScene.isAod()
|
||||
&& activeScene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS) {
|
||||
lockscreenCardsNotificationRowFilter.suppressHiddenRows();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyBeforeDrawInternal(View root) {
|
||||
if (root == null) {
|
||||
return;
|
||||
@@ -416,7 +522,22 @@ final class StockLayoutCanvasController {
|
||||
&& activeScene.isLockscreen()
|
||||
&& activeScene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS
|
||||
&& canContainLockedStatusBarIconSurfaces(root)) {
|
||||
applyLockscreenCardsNotificationRowFilterInTreeIfNeeded(
|
||||
root,
|
||||
settings,
|
||||
activeScene,
|
||||
isNotificationShadeWindowRoot(root));
|
||||
renderLockscreenCardsNotificationStripIfNeeded(root, settings, activeScene);
|
||||
} else if (activeScene != null
|
||||
&& activeScene.isAod()
|
||||
&& activeScene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS) {
|
||||
lockscreenCardsRowFilterSignatures.remove(root);
|
||||
lockscreenCardsNotificationRowFilter.suppressHiddenRows();
|
||||
} else {
|
||||
lockscreenCardsRowFilterSignatures.remove(root);
|
||||
if (activeScene != null && activeScene.isUnlocked()) {
|
||||
lockscreenCardsNotificationRowFilter.restoreAll();
|
||||
}
|
||||
}
|
||||
if (!isUnlockedStatusBarRoot(root)) {
|
||||
return;
|
||||
@@ -460,6 +581,7 @@ final class StockLayoutCanvasController {
|
||||
Integer previous = settingsIdentityByRoot.put(root, identity);
|
||||
if (previous != null && previous != identity) {
|
||||
dirtyUnlockedLayoutRoots.add(root);
|
||||
lockscreenCardsRowFilterSignatures.remove(root);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,6 +682,9 @@ final class StockLayoutCanvasController {
|
||||
}
|
||||
|
||||
private void applyToRoot(View root) {
|
||||
if (!isViewThread(root)) {
|
||||
return;
|
||||
}
|
||||
applyToRootInternal(root);
|
||||
}
|
||||
|
||||
@@ -889,6 +1014,53 @@ final class StockLayoutCanvasController {
|
||||
return foundInRoot;
|
||||
}
|
||||
|
||||
private boolean shouldSuppressConflictingShadeHeaderAlpha(View view, float alpha) {
|
||||
return view != null
|
||||
&& alpha != view.getAlpha()
|
||||
&& isConflictingShadeHeaderAnimatorWrite(view);
|
||||
}
|
||||
|
||||
private boolean shouldSuppressConflictingShadeHeaderTranslationY(View view, float translationY) {
|
||||
return view != null
|
||||
&& translationY != view.getTranslationY()
|
||||
&& isConflictingShadeHeaderAnimatorWrite(view);
|
||||
}
|
||||
|
||||
private boolean isConflictingShadeHeaderAnimatorWrite(View view) {
|
||||
if (view == null
|
||||
|| !"split_shade_status_bar".equals(resolveIdName(view))
|
||||
|| !isLayoutEnabled(view.getContext())) {
|
||||
return false;
|
||||
}
|
||||
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||
// Samsung runs two QS header animators during lockscreen cards shade pulls.
|
||||
// LegacyQsOpenAnimator provides the visible fade. LegacyQsExpandAnimator
|
||||
// writes conflicting values both while tracking expansion and when it clears
|
||||
// its state after collapse.
|
||||
return activeScene != null
|
||||
&& activeScene.isLockscreen()
|
||||
&& activeScene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS
|
||||
&& !runtimeContext.getModeStateRepository().isSystemUiShadeLocked()
|
||||
&& (stackContains("LegacyQsExpandAnimator", "setQsExpansionPosition")
|
||||
|| stackContains("LegacyQsExpandAnimator", "clearAnimationState"));
|
||||
}
|
||||
|
||||
private boolean stackContains(String classNamePart, String methodName) {
|
||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||||
for (StackTraceElement element : stack) {
|
||||
if (element == null) {
|
||||
continue;
|
||||
}
|
||||
String className = element.getClassName();
|
||||
if (className != null
|
||||
&& className.contains(classNamePart)
|
||||
&& methodName.equals(element.getMethodName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void restoreTrackedLockedStatusBarIconSurfaces(View root) {
|
||||
if (trackedLockedStatusBarIconSurfaces.isEmpty()) {
|
||||
return;
|
||||
@@ -967,6 +1139,9 @@ final class StockLayoutCanvasController {
|
||||
Object current = view;
|
||||
while (current instanceof View currentView) {
|
||||
String className = currentView.getClass().getName();
|
||||
if ("split_shade_status_bar".equals(resolveIdName(currentView))) {
|
||||
return false;
|
||||
}
|
||||
if (className.contains("KeyguardStatusBarView")
|
||||
|| className.contains("PhoneStatusBarView")
|
||||
|| className.contains("StatusBarWindowView")) {
|
||||
@@ -1054,6 +1229,10 @@ final class StockLayoutCanvasController {
|
||||
|| className.contains("StatusBarWindowView");
|
||||
}
|
||||
|
||||
private boolean isNotificationShadeWindowRoot(View root) {
|
||||
return root != null && root.getClass().getName().contains("NotificationShadeWindowView");
|
||||
}
|
||||
|
||||
private void rememberLockedCarrierTextSource(View root) {
|
||||
if (lockedCarrierTextSource() != null || root == null || !isCarrierView(root)) {
|
||||
return;
|
||||
@@ -1167,6 +1346,7 @@ final class StockLayoutCanvasController {
|
||||
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
||||
dirtyUnlockedLayoutRoots.remove(root);
|
||||
settingsIdentityByRoot.remove(root);
|
||||
lockscreenCardsRowFilterSignatures.remove(root);
|
||||
clockTimeBucketByRoot.remove(root);
|
||||
removeDebugOverlay(root);
|
||||
root.requestLayout();
|
||||
@@ -1242,7 +1422,7 @@ final class StockLayoutCanvasController {
|
||||
}
|
||||
|
||||
private boolean hideView(View view, boolean keepLaidOut) {
|
||||
if (view == null) {
|
||||
if (view == null || !isViewThread(view)) {
|
||||
return false;
|
||||
}
|
||||
int targetVisibility = keepLaidOut ? View.VISIBLE : View.GONE;
|
||||
@@ -1260,7 +1440,7 @@ final class StockLayoutCanvasController {
|
||||
}
|
||||
|
||||
private boolean restoreView(View view) {
|
||||
if (view == null) {
|
||||
if (view == null || !isViewThread(view)) {
|
||||
return false;
|
||||
}
|
||||
HiddenViewState state = hiddenViewStates.remove(view);
|
||||
@@ -1337,6 +1517,7 @@ final class StockLayoutCanvasController {
|
||||
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
||||
dirtyUnlockedLayoutRoots.remove(root);
|
||||
settingsIdentityByRoot.remove(root);
|
||||
lockscreenCardsRowFilterSignatures.remove(root);
|
||||
clockTimeBucketByRoot.remove(root);
|
||||
if (!isUnlockedStatusBarRoot(root) || !layoutOffCleanedRoots.add(root)) {
|
||||
removeLockscreenCardsNotificationHost(root);
|
||||
@@ -1348,7 +1529,7 @@ final class StockLayoutCanvasController {
|
||||
}
|
||||
|
||||
private void forceViewVisible(View view) {
|
||||
if (view == null) {
|
||||
if (view == null || !isViewThread(view)) {
|
||||
return;
|
||||
}
|
||||
view.setAlpha(1f);
|
||||
@@ -1357,7 +1538,7 @@ final class StockLayoutCanvasController {
|
||||
}
|
||||
|
||||
private void requestViewAndParentLayout(View view) {
|
||||
if (view == null) {
|
||||
if (view == null || !isViewThread(view)) {
|
||||
return;
|
||||
}
|
||||
view.requestLayout();
|
||||
@@ -1440,6 +1621,72 @@ final class StockLayoutCanvasController {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyLockscreenCardsNotificationRowFilter(ViewGroup stack) {
|
||||
applyLockscreenCardsNotificationRowFilter(stack, false);
|
||||
}
|
||||
|
||||
private void applyLockscreenCardsNotificationRowFilter(ViewGroup stack, boolean allowAfterKeyguardUnlock) {
|
||||
if (stack == null) {
|
||||
return;
|
||||
}
|
||||
if (runtimeContext.getModeStateRepository().isSystemUiShadeLocked()) {
|
||||
lockscreenCardsNotificationRowFilter.restoreAll();
|
||||
return;
|
||||
}
|
||||
lockscreenCardsNotificationRowFilter.apply(stack, allowAfterKeyguardUnlock);
|
||||
}
|
||||
|
||||
private void applyLockscreenCardsNotificationRowFilterInTreeIfNeeded(
|
||||
View root,
|
||||
SbtSettings settings,
|
||||
SceneKey activeScene,
|
||||
boolean allowAfterKeyguardUnlock) {
|
||||
int signature = lockscreenCardsRowFilterSignature(root, settings, activeScene);
|
||||
Integer previous = lockscreenCardsRowFilterSignatures.put(root, signature);
|
||||
if (previous != null && previous == signature) {
|
||||
return;
|
||||
}
|
||||
applyLockscreenCardsNotificationRowFilterInTree(root, allowAfterKeyguardUnlock);
|
||||
}
|
||||
|
||||
private int lockscreenCardsRowFilterSignature(
|
||||
View root,
|
||||
SbtSettings settings,
|
||||
SceneKey activeScene) {
|
||||
int result = 17;
|
||||
result = 31 * result + System.identityHashCode(settings);
|
||||
result = 31 * result + (activeScene != null ? activeScene.hashCode() : 0);
|
||||
result = 31 * result
|
||||
+ (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;
|
||||
}
|
||||
|
||||
private void applyLockscreenCardsNotificationRowFilterInTree(View root, boolean allowAfterKeyguardUnlock) {
|
||||
if (!(root instanceof ViewGroup group)) {
|
||||
return;
|
||||
}
|
||||
ArrayDeque<View> queue = new ArrayDeque<>();
|
||||
queue.add(group);
|
||||
while (!queue.isEmpty()) {
|
||||
View view = queue.removeFirst();
|
||||
if (view instanceof ViewGroup viewGroup) {
|
||||
if (viewGroup.getClass().getName().contains("NotificationStackScrollLayout")) {
|
||||
applyLockscreenCardsNotificationRowFilter(viewGroup, allowAfterKeyguardUnlock);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < viewGroup.getChildCount(); i++) {
|
||||
View child = viewGroup.getChildAt(i);
|
||||
if (child != null) {
|
||||
queue.addLast(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private View findVisibleBouncerSurface(View root) {
|
||||
if (root == null) {
|
||||
return null;
|
||||
@@ -1542,6 +1789,7 @@ final class StockLayoutCanvasController {
|
||||
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
||||
dirtyUnlockedLayoutRoots.remove(root);
|
||||
settingsIdentityByRoot.remove(root);
|
||||
lockscreenCardsRowFilterSignatures.remove(root);
|
||||
clockTimeBucketByRoot.remove(root);
|
||||
return null;
|
||||
}
|
||||
@@ -1628,6 +1876,18 @@ final class StockLayoutCanvasController {
|
||||
return powerManager == null || powerManager.isInteractive();
|
||||
}
|
||||
|
||||
private boolean isViewThread(View view) {
|
||||
Looper current = Looper.myLooper();
|
||||
if (current == null || current != Looper.getMainLooper()) {
|
||||
return false;
|
||||
}
|
||||
if (view == null || view.getHandler() == null) {
|
||||
return true;
|
||||
}
|
||||
Looper looper = view.getHandler().getLooper();
|
||||
return looper == null || looper == current;
|
||||
}
|
||||
|
||||
private boolean isLayoutEnabled(Context context) {
|
||||
SbtSettings settings = RuntimeSettingsCache.get(context);
|
||||
return settings.clockEnabled;
|
||||
|
||||
+55
-33
@@ -5,6 +5,7 @@ import android.view.ViewGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
@@ -47,11 +48,15 @@ public final class LockscreenCardsNotificationIconSourceStore {
|
||||
return result;
|
||||
}
|
||||
int skipCards = visibleShelfStartIndex(container, views);
|
||||
HashSet<String> visibleCardKeys = visibleCardKeys(root, container);
|
||||
for (int i = 0; i < views.size(); i++) {
|
||||
if (i < skipCards) {
|
||||
continue;
|
||||
}
|
||||
SourceEntry entry = views.get(i);
|
||||
if (entry.key != null && visibleCardKeys.contains(entry.key)) {
|
||||
continue;
|
||||
}
|
||||
View view = entry.view;
|
||||
if (view == null || !view.isAttachedToWindow()) {
|
||||
continue;
|
||||
@@ -61,7 +66,46 @@ public final class LockscreenCardsNotificationIconSourceStore {
|
||||
if (width <= 0 || height <= 0) {
|
||||
continue;
|
||||
}
|
||||
result.add(new SnapshotSource(view, SnapshotMode.NOTIFICATION_DRAWABLE));
|
||||
result.add(new SnapshotSource(view, SnapshotMode.NOTIFICATION_DRAWABLE, entry.key));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static HashSet<String> visibleCardKeys(View root, View shelf) {
|
||||
HashSet<String> result = new HashSet<>();
|
||||
if (!(root instanceof ViewGroup group)) {
|
||||
return result;
|
||||
}
|
||||
int shelfTop = Integer.MAX_VALUE;
|
||||
if (shelf != null) {
|
||||
se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds shelfBounds =
|
||||
ViewGeometry.boundsRelativeTo(root, shelf);
|
||||
if (shelfBounds != null) {
|
||||
shelfTop = shelfBounds.top;
|
||||
}
|
||||
}
|
||||
ArrayList<View> stack = new ArrayList<>();
|
||||
stack.add(group);
|
||||
for (int i = 0; i < stack.size(); i++) {
|
||||
View view = stack.get(i);
|
||||
if (isNotificationRow(view) && isVisibleCardRow(view)) {
|
||||
se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds rowBounds =
|
||||
ViewGeometry.boundsRelativeTo(root, view);
|
||||
if (rowBounds != null && rowBounds.top < shelfTop) {
|
||||
String key = keyForRow(view);
|
||||
if (key != null && !key.isEmpty()) {
|
||||
result.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (view instanceof ViewGroup viewGroup) {
|
||||
for (int child = 0; child < viewGroup.getChildCount(); child++) {
|
||||
View childView = viewGroup.getChildAt(child);
|
||||
if (childView != null) {
|
||||
stack.add(childView);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -176,40 +220,18 @@ public final class LockscreenCardsNotificationIconSourceStore {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int visibleCardCountBeforeShelf(View root, View shelf) {
|
||||
if (!(root instanceof ViewGroup group)) {
|
||||
return 0;
|
||||
private static String keyForRow(View view) {
|
||||
Object entry = ReflectionSupport.invokeMethod(view, "getEntry", new Class<?>[0]);
|
||||
String key = keyForEntry(entry);
|
||||
if (key != null && !key.isEmpty()) {
|
||||
return key;
|
||||
}
|
||||
int shelfTop = Integer.MAX_VALUE;
|
||||
if (shelf != null) {
|
||||
se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds shelfBounds =
|
||||
ViewGeometry.boundsRelativeTo(root, shelf);
|
||||
if (shelfBounds != null) {
|
||||
shelfTop = shelfBounds.top;
|
||||
}
|
||||
entry = ReflectionSupport.getFieldValue(view, "mEntry");
|
||||
key = keyForEntry(entry);
|
||||
if (key != null && !key.isEmpty()) {
|
||||
return key;
|
||||
}
|
||||
int count = 0;
|
||||
ArrayList<View> stack = new ArrayList<>();
|
||||
stack.add(group);
|
||||
for (int i = 0; i < stack.size(); i++) {
|
||||
View view = stack.get(i);
|
||||
if (isNotificationRow(view) && isVisibleCardRow(view)) {
|
||||
se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds rowBounds =
|
||||
ViewGeometry.boundsRelativeTo(root, view);
|
||||
if (rowBounds != null && rowBounds.top < shelfTop) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (view instanceof ViewGroup viewGroup) {
|
||||
for (int child = 0; child < viewGroup.getChildCount(); child++) {
|
||||
View childView = viewGroup.getChildAt(child);
|
||||
if (childView != null) {
|
||||
stack.add(childView);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
return keyForIconView(view);
|
||||
}
|
||||
|
||||
private static boolean isNotificationRow(View view) {
|
||||
|
||||
+4
-1
@@ -30,6 +30,7 @@ public final class LockscreenCardsNotificationStripRenderer {
|
||||
|
||||
private final SnapshotRenderer renderer = new SnapshotRenderer(false, false, false);
|
||||
private final WeakHashMap<ViewGroup, ArrayList<View>> pillViewsByHost = new WeakHashMap<>();
|
||||
private final NotificationSeenAppReporter seenAppReporter = new NotificationSeenAppReporter();
|
||||
|
||||
public RenderResult render(
|
||||
View root,
|
||||
@@ -65,8 +66,10 @@ public final class LockscreenCardsNotificationStripRenderer {
|
||||
clear(host);
|
||||
return RenderResult.empty();
|
||||
}
|
||||
ArrayList<SnapshotPlacement> unfilteredPlacements = renderer.rawPlacements(root, sources);
|
||||
seenAppReporter.report(root.getContext(), unfilteredPlacements);
|
||||
ArrayList<SnapshotPlacement> placements = SnapshotIconFilter.notificationPlacements(
|
||||
renderer.rawPlacements(root, sources),
|
||||
unfilteredPlacements,
|
||||
settings,
|
||||
scene);
|
||||
StripLayout layout = layoutPlacements(root, sourceRoot, placements, settings);
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package se.ajpanton.statusbartweak.runtime.render;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
|
||||
|
||||
final class NotificationSeenAppReporter {
|
||||
private static final long MIN_REPORT_INTERVAL_MS = 500L;
|
||||
|
||||
private final Map<String, Long> lastReportedMs = new HashMap<>();
|
||||
|
||||
void report(Context context, ArrayList<SnapshotPlacement> placements) {
|
||||
if (context == null || placements == null || placements.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
HashSet<String> reportedThisPass = new HashSet<>();
|
||||
for (SnapshotPlacement placement : placements) {
|
||||
String packageName = SnapshotIconFilter.notificationPackage(
|
||||
placement != null ? placement.key : null);
|
||||
if (packageName == null || !reportedThisPass.add(packageName)) {
|
||||
continue;
|
||||
}
|
||||
Long lastReported = lastReportedMs.get(packageName);
|
||||
if (lastReported != null && now - lastReported < MIN_REPORT_INTERVAL_MS) {
|
||||
continue;
|
||||
}
|
||||
lastReportedMs.put(packageName, now);
|
||||
SbtSettings.noteSeenIconApp(context, packageName, now);
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
-18
@@ -4,6 +4,8 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -26,6 +28,13 @@ public final class OwnedIconHostManager {
|
||||
"statusbartweak.unlocked.chip.host";
|
||||
public static final String TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST =
|
||||
"statusbartweak.lockscreen.cards.notification.host";
|
||||
private static final String[] UNLOCKED_HOST_ORDER = {
|
||||
TAG_UNLOCKED_STATUS_HOST,
|
||||
TAG_UNLOCKED_NOTIFICATION_HOST,
|
||||
TAG_UNLOCKED_CLOCK_HOST,
|
||||
TAG_UNLOCKED_CARRIER_HOST,
|
||||
TAG_UNLOCKED_CHIP_HOST
|
||||
};
|
||||
|
||||
public FrameLayout ensureUnlockedStatusHost(View root) {
|
||||
return ensureHost(root, TAG_UNLOCKED_STATUS_HOST);
|
||||
@@ -58,7 +67,6 @@ public final class OwnedIconHostManager {
|
||||
}
|
||||
FrameLayout existing = findDirectHost(parent, tag);
|
||||
if (existing != null) {
|
||||
existing.bringToFront();
|
||||
return existing;
|
||||
}
|
||||
FrameLayout host = new FrameLayout(parent.getContext());
|
||||
@@ -94,19 +102,22 @@ public final class OwnedIconHostManager {
|
||||
}
|
||||
|
||||
public void setUnlockedHostsVisible(View root, boolean visible) {
|
||||
setHostVisible(root, TAG_UNLOCKED_STATUS_HOST, visible);
|
||||
setHostVisible(root, TAG_UNLOCKED_NOTIFICATION_HOST, visible);
|
||||
setHostVisible(root, TAG_UNLOCKED_CLOCK_HOST, visible);
|
||||
setHostVisible(root, TAG_UNLOCKED_CARRIER_HOST, visible);
|
||||
setHostVisible(root, TAG_UNLOCKED_CHIP_HOST, visible);
|
||||
for (String tag : UNLOCKED_HOST_ORDER) {
|
||||
setHostVisible(root, tag, visible);
|
||||
}
|
||||
}
|
||||
|
||||
public void bringUnlockedHostsToFront(View root) {
|
||||
bringHostToFront(root, TAG_UNLOCKED_STATUS_HOST);
|
||||
bringHostToFront(root, TAG_UNLOCKED_NOTIFICATION_HOST);
|
||||
bringHostToFront(root, TAG_UNLOCKED_CLOCK_HOST);
|
||||
bringHostToFront(root, TAG_UNLOCKED_CARRIER_HOST);
|
||||
bringHostToFront(root, TAG_UNLOCKED_CHIP_HOST);
|
||||
if (!(root instanceof ViewGroup parent)) {
|
||||
return;
|
||||
}
|
||||
ArrayList<View> hosts = unlockedHosts(parent);
|
||||
if (hosts.isEmpty() || unlockedHostsAreAlreadyAtFront(parent, hosts)) {
|
||||
return;
|
||||
}
|
||||
for (View host : hosts) {
|
||||
host.bringToFront();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isOwnedHost(View view) {
|
||||
@@ -140,17 +151,35 @@ public final class OwnedIconHostManager {
|
||||
if (host == null) {
|
||||
return;
|
||||
}
|
||||
host.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
|
||||
int visibility = visible ? View.VISIBLE : View.INVISIBLE;
|
||||
if (host.getVisibility() != visibility) {
|
||||
host.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
private void bringHostToFront(View root, String tag) {
|
||||
if (!(root instanceof ViewGroup parent) || tag == null) {
|
||||
return;
|
||||
private ArrayList<View> unlockedHosts(ViewGroup parent) {
|
||||
ArrayList<View> hosts = new ArrayList<>();
|
||||
for (String tag : UNLOCKED_HOST_ORDER) {
|
||||
View host = findDirectHost(parent, tag);
|
||||
if (host != null) {
|
||||
hosts.add(host);
|
||||
}
|
||||
}
|
||||
FrameLayout host = findDirectHost(parent, tag);
|
||||
if (host != null) {
|
||||
host.bringToFront();
|
||||
return hosts;
|
||||
}
|
||||
|
||||
private boolean unlockedHostsAreAlreadyAtFront(ViewGroup parent, List<View> hosts) {
|
||||
if (parent == null || hosts == null || hosts.isEmpty()
|
||||
|| parent.getChildCount() < hosts.size()) {
|
||||
return false;
|
||||
}
|
||||
int start = parent.getChildCount() - hosts.size();
|
||||
for (int i = 0; i < hosts.size(); i++) {
|
||||
if (parent.getChildAt(start + i) != hosts.get(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,8 +11,12 @@ import se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bou
|
||||
|
||||
record SnapshotSource(
|
||||
View view,
|
||||
SnapshotMode mode
|
||||
SnapshotMode mode,
|
||||
String keyHint
|
||||
) {
|
||||
SnapshotSource(View view, SnapshotMode mode) {
|
||||
this(view, mode, null);
|
||||
}
|
||||
}
|
||||
|
||||
enum SnapshotMode {
|
||||
|
||||
@@ -347,6 +347,10 @@ final class SnapshotRenderer {
|
||||
|
||||
private String keyFor(SnapshotSource source, int order) {
|
||||
View view = source.view();
|
||||
String keyHint = source.keyHint();
|
||||
if (keyHint != null && !keyHint.isEmpty()) {
|
||||
return keyHint;
|
||||
}
|
||||
String slot = reflectString(view, "getSlot", "mSlot", "slot");
|
||||
if (!slot.isEmpty()) {
|
||||
return slot;
|
||||
|
||||
+8
-4
@@ -27,6 +27,8 @@ final class UnlockedLayoutPlanner {
|
||||
private final SnapshotRenderer chipRenderer;
|
||||
private final UnlockedChipPlacementController chipPlacementController;
|
||||
private final UnlockedVerticalOffsetScaler offsetScaler;
|
||||
private final NotificationSeenAppReporter notificationSeenAppReporter =
|
||||
new NotificationSeenAppReporter();
|
||||
|
||||
UnlockedLayoutPlanner(
|
||||
SnapshotRenderer statusRenderer,
|
||||
@@ -130,11 +132,13 @@ final class UnlockedLayoutPlanner {
|
||||
}
|
||||
int notificationCount = notificationCountForScene(settings, scene);
|
||||
if (notificationCount > 0) {
|
||||
ArrayList<SnapshotPlacement> unfilteredPlacements =
|
||||
notificationRenderer.rawPlacements(root, collectedIcons.notificationIcons);
|
||||
notificationSeenAppReporter.report(
|
||||
root != null ? root.getContext() : null,
|
||||
unfilteredPlacements);
|
||||
ArrayList<SnapshotPlacement> rawPlacements =
|
||||
SnapshotIconFilter.notificationPlacements(
|
||||
notificationRenderer.rawPlacements(root, collectedIcons.notificationIcons),
|
||||
settings,
|
||||
scene);
|
||||
SnapshotIconFilter.notificationPlacements(unfilteredPlacements, settings, scene);
|
||||
if (scene != null
|
||||
&& scene.isLockscreen()
|
||||
&& scene.notificationDisplayStyle() == NotificationDisplayStyle.DOT) {
|
||||
|
||||
@@ -868,6 +868,29 @@ public final class SbtSettings {
|
||||
}
|
||||
}
|
||||
|
||||
public static void noteSeenIconApp(Context context, String packageName, long firstSeenMs) {
|
||||
if (context == null || !AppIconRules.isValidPackageName(packageName)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!MODULE_PACKAGE.equals(context.getPackageName()) && hasSettingsProvider(context)) {
|
||||
Uri uri = Uri.parse("content://" + SbtSettingsProvider.AUTHORITY);
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(AppIconRules.EXTRA_PACKAGE, packageName);
|
||||
extras.putLong(AppIconRules.EXTRA_FIRST_SEEN_MS, firstSeenMs);
|
||||
context.getContentResolver()
|
||||
.call(uri, SbtSettingsProvider.METHOD_NOTE_SEEN_ICON_APP, null, extras);
|
||||
return;
|
||||
}
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
Map<String, Long> seen = AppIconRules.loadSeenSession(prefs);
|
||||
AppIconRules.noteSeen(seen, packageName, firstSeenMs);
|
||||
AppIconRules.saveSeenSession(prefs, seen);
|
||||
} catch (Throwable ignored) {
|
||||
// This list is advisory UI state; rendering should never fail because it cannot be updated.
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasSettingsProvider(Context context) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
|
||||
@@ -429,13 +429,9 @@ final class LayoutSectionCopySupport {
|
||||
row.setTag(COPY_ROW_TAG);
|
||||
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
TextView label = new TextView(context);
|
||||
label.setText(R.string.layout_copy_from);
|
||||
row.addView(label);
|
||||
LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
spinnerParams.leftMargin = dp(context, 8);
|
||||
row.addView(spinner, spinnerParams);
|
||||
LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
@@ -460,9 +456,6 @@ final class LayoutSectionCopySupport {
|
||||
row.setTag(COPY_ROW_TAG);
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
|
||||
TextView label = new TextView(context);
|
||||
label.setText(R.string.layout_copy_from);
|
||||
row.addView(label);
|
||||
Spinner spinner = new Spinner(context);
|
||||
spinner.setAdapter(new CopyAdapter(context, options));
|
||||
spinner.setSelection(0, false);
|
||||
@@ -491,7 +484,6 @@ final class LayoutSectionCopySupport {
|
||||
LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
spinnerParams.leftMargin = dp(context, 8);
|
||||
row.addView(spinner, spinnerParams);
|
||||
return row;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user