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.app.KeyguardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -69,6 +70,7 @@ final class StockLayoutCanvasController {
|
|||||||
private final Map<View, RootGeometry> rootGeometries = new WeakHashMap<>();
|
private final Map<View, RootGeometry> rootGeometries = new WeakHashMap<>();
|
||||||
private final Map<View, Integer> debugOverlaySignatures = new WeakHashMap<>();
|
private final Map<View, Integer> debugOverlaySignatures = new WeakHashMap<>();
|
||||||
private final Map<View, Integer> settingsIdentityByRoot = new WeakHashMap<>();
|
private final Map<View, Integer> settingsIdentityByRoot = new WeakHashMap<>();
|
||||||
|
private final Map<View, Integer> lockscreenCardsRowFilterSignatures = new WeakHashMap<>();
|
||||||
private final Map<View, Long> clockTimeBucketByRoot = new WeakHashMap<>();
|
private final Map<View, Long> clockTimeBucketByRoot = new WeakHashMap<>();
|
||||||
private final Set<View> confirmedAodPluginViews =
|
private final Set<View> confirmedAodPluginViews =
|
||||||
Collections.newSetFromMap(new WeakHashMap<>());
|
Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
@@ -92,6 +94,8 @@ final class StockLayoutCanvasController {
|
|||||||
new DebugBoundsOverlayController();
|
new DebugBoundsOverlayController();
|
||||||
private final LockscreenCardsNotificationStripRenderer lockscreenCardsNotificationStripRenderer =
|
private final LockscreenCardsNotificationStripRenderer lockscreenCardsNotificationStripRenderer =
|
||||||
new LockscreenCardsNotificationStripRenderer();
|
new LockscreenCardsNotificationStripRenderer();
|
||||||
|
private final LockscreenCardsNotificationRowFilter lockscreenCardsNotificationRowFilter =
|
||||||
|
new LockscreenCardsNotificationRowFilter();
|
||||||
private final UnlockedIconSnapshotRenderController unlockedIconRenderController;
|
private final UnlockedIconSnapshotRenderController unlockedIconRenderController;
|
||||||
// The keyguard carrier text lives outside the statusbar root on this device.
|
// 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
|
// Keep a weak source reference so the lockscreen statusbar can render it when
|
||||||
@@ -111,11 +115,68 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
installPluginManagerHooks(classLoader);
|
installPluginManagerHooks(classLoader);
|
||||||
installCardsNotificationIconSourceHook(classLoader);
|
installCardsNotificationIconSourceHook(classLoader);
|
||||||
|
installLockscreenCardsNotificationRowFilterHook(classLoader);
|
||||||
installUnlockedAppearanceInvalidationHooks(classLoader);
|
installUnlockedAppearanceInvalidationHooks(classLoader);
|
||||||
|
installShadeHeaderAnimationGuardHook(classLoader);
|
||||||
installViewRootHook(classLoader);
|
installViewRootHook(classLoader);
|
||||||
hooksInstalled = true;
|
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) {
|
private void installUnlockedAppearanceInvalidationHooks(ClassLoader classLoader) {
|
||||||
Class<?> darkIconDispatcherClass = ReflectionSupport.findClassIfExists(
|
Class<?> darkIconDispatcherClass = ReflectionSupport.findClassIfExists(
|
||||||
"com.android.systemui.statusbar.phone.DarkIconDispatcherImpl",
|
"com.android.systemui.statusbar.phone.DarkIconDispatcherImpl",
|
||||||
@@ -296,6 +357,12 @@ final class StockLayoutCanvasController {
|
|||||||
new ArrayList<>();
|
new ArrayList<>();
|
||||||
Function<Object, Object> wrapped = entryObj -> {
|
Function<Object, Object> wrapped = entryObj -> {
|
||||||
String key = LockscreenCardsNotificationIconSourceStore.keyForEntry(entryObj);
|
String key = LockscreenCardsNotificationIconSourceStore.keyForEntry(entryObj);
|
||||||
|
if (!runtimeContext.getModeStateRepository().isSystemUiShadeLocked()
|
||||||
|
&& lockscreenCardsNotificationRowFilter.shouldHideEntry(
|
||||||
|
container.getContext(),
|
||||||
|
entryObj)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Object result = original.apply(entryObj);
|
Object result = original.apply(entryObj);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = fallbackStatusBarIcon(entryObj);
|
result = fallbackStatusBarIcon(entryObj);
|
||||||
@@ -384,6 +451,10 @@ final class StockLayoutCanvasController {
|
|||||||
return chain.proceed();
|
return chain.proceed();
|
||||||
});
|
});
|
||||||
XposedHookSupport.hookAllMethods(runtimeContext.getFramework(), viewRootClass, "performTraversals", chain -> {
|
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 result = chain.proceed();
|
||||||
Object rootObj = ReflectionSupport.requireFieldValue(chain.getThisObject(), "mView");
|
Object rootObj = ReflectionSupport.requireFieldValue(chain.getThisObject(), "mView");
|
||||||
if (rootObj instanceof View root) {
|
if (rootObj instanceof View root) {
|
||||||
@@ -394,9 +465,44 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applyBeforeDraw(View root) {
|
private void applyBeforeDraw(View root) {
|
||||||
|
if (!isViewThread(root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
applyBeforeDrawInternal(root);
|
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) {
|
private void applyBeforeDrawInternal(View root) {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return;
|
return;
|
||||||
@@ -416,7 +522,22 @@ final class StockLayoutCanvasController {
|
|||||||
&& activeScene.isLockscreen()
|
&& activeScene.isLockscreen()
|
||||||
&& activeScene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS
|
&& activeScene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS
|
||||||
&& canContainLockedStatusBarIconSurfaces(root)) {
|
&& canContainLockedStatusBarIconSurfaces(root)) {
|
||||||
|
applyLockscreenCardsNotificationRowFilterInTreeIfNeeded(
|
||||||
|
root,
|
||||||
|
settings,
|
||||||
|
activeScene,
|
||||||
|
isNotificationShadeWindowRoot(root));
|
||||||
renderLockscreenCardsNotificationStripIfNeeded(root, settings, activeScene);
|
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)) {
|
if (!isUnlockedStatusBarRoot(root)) {
|
||||||
return;
|
return;
|
||||||
@@ -460,6 +581,7 @@ final class StockLayoutCanvasController {
|
|||||||
Integer previous = settingsIdentityByRoot.put(root, identity);
|
Integer previous = settingsIdentityByRoot.put(root, identity);
|
||||||
if (previous != null && previous != identity) {
|
if (previous != null && previous != identity) {
|
||||||
dirtyUnlockedLayoutRoots.add(root);
|
dirtyUnlockedLayoutRoots.add(root);
|
||||||
|
lockscreenCardsRowFilterSignatures.remove(root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,6 +682,9 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void applyToRoot(View root) {
|
private void applyToRoot(View root) {
|
||||||
|
if (!isViewThread(root)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
applyToRootInternal(root);
|
applyToRootInternal(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -889,6 +1014,53 @@ final class StockLayoutCanvasController {
|
|||||||
return foundInRoot;
|
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) {
|
private void restoreTrackedLockedStatusBarIconSurfaces(View root) {
|
||||||
if (trackedLockedStatusBarIconSurfaces.isEmpty()) {
|
if (trackedLockedStatusBarIconSurfaces.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@@ -967,6 +1139,9 @@ final class StockLayoutCanvasController {
|
|||||||
Object current = view;
|
Object current = view;
|
||||||
while (current instanceof View currentView) {
|
while (current instanceof View currentView) {
|
||||||
String className = currentView.getClass().getName();
|
String className = currentView.getClass().getName();
|
||||||
|
if ("split_shade_status_bar".equals(resolveIdName(currentView))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (className.contains("KeyguardStatusBarView")
|
if (className.contains("KeyguardStatusBarView")
|
||||||
|| className.contains("PhoneStatusBarView")
|
|| className.contains("PhoneStatusBarView")
|
||||||
|| className.contains("StatusBarWindowView")) {
|
|| className.contains("StatusBarWindowView")) {
|
||||||
@@ -1054,6 +1229,10 @@ final class StockLayoutCanvasController {
|
|||||||
|| className.contains("StatusBarWindowView");
|
|| className.contains("StatusBarWindowView");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isNotificationShadeWindowRoot(View root) {
|
||||||
|
return root != null && root.getClass().getName().contains("NotificationShadeWindowView");
|
||||||
|
}
|
||||||
|
|
||||||
private void rememberLockedCarrierTextSource(View root) {
|
private void rememberLockedCarrierTextSource(View root) {
|
||||||
if (lockedCarrierTextSource() != null || root == null || !isCarrierView(root)) {
|
if (lockedCarrierTextSource() != null || root == null || !isCarrierView(root)) {
|
||||||
return;
|
return;
|
||||||
@@ -1167,6 +1346,7 @@ final class StockLayoutCanvasController {
|
|||||||
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
||||||
dirtyUnlockedLayoutRoots.remove(root);
|
dirtyUnlockedLayoutRoots.remove(root);
|
||||||
settingsIdentityByRoot.remove(root);
|
settingsIdentityByRoot.remove(root);
|
||||||
|
lockscreenCardsRowFilterSignatures.remove(root);
|
||||||
clockTimeBucketByRoot.remove(root);
|
clockTimeBucketByRoot.remove(root);
|
||||||
removeDebugOverlay(root);
|
removeDebugOverlay(root);
|
||||||
root.requestLayout();
|
root.requestLayout();
|
||||||
@@ -1242,7 +1422,7 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean hideView(View view, boolean keepLaidOut) {
|
private boolean hideView(View view, boolean keepLaidOut) {
|
||||||
if (view == null) {
|
if (view == null || !isViewThread(view)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int targetVisibility = keepLaidOut ? View.VISIBLE : View.GONE;
|
int targetVisibility = keepLaidOut ? View.VISIBLE : View.GONE;
|
||||||
@@ -1260,7 +1440,7 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean restoreView(View view) {
|
private boolean restoreView(View view) {
|
||||||
if (view == null) {
|
if (view == null || !isViewThread(view)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
HiddenViewState state = hiddenViewStates.remove(view);
|
HiddenViewState state = hiddenViewStates.remove(view);
|
||||||
@@ -1337,6 +1517,7 @@ final class StockLayoutCanvasController {
|
|||||||
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
||||||
dirtyUnlockedLayoutRoots.remove(root);
|
dirtyUnlockedLayoutRoots.remove(root);
|
||||||
settingsIdentityByRoot.remove(root);
|
settingsIdentityByRoot.remove(root);
|
||||||
|
lockscreenCardsRowFilterSignatures.remove(root);
|
||||||
clockTimeBucketByRoot.remove(root);
|
clockTimeBucketByRoot.remove(root);
|
||||||
if (!isUnlockedStatusBarRoot(root) || !layoutOffCleanedRoots.add(root)) {
|
if (!isUnlockedStatusBarRoot(root) || !layoutOffCleanedRoots.add(root)) {
|
||||||
removeLockscreenCardsNotificationHost(root);
|
removeLockscreenCardsNotificationHost(root);
|
||||||
@@ -1348,7 +1529,7 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void forceViewVisible(View view) {
|
private void forceViewVisible(View view) {
|
||||||
if (view == null) {
|
if (view == null || !isViewThread(view)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
view.setAlpha(1f);
|
view.setAlpha(1f);
|
||||||
@@ -1357,7 +1538,7 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void requestViewAndParentLayout(View view) {
|
private void requestViewAndParentLayout(View view) {
|
||||||
if (view == null) {
|
if (view == null || !isViewThread(view)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
view.requestLayout();
|
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) {
|
private View findVisibleBouncerSurface(View root) {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -1542,6 +1789,7 @@ final class StockLayoutCanvasController {
|
|||||||
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
||||||
dirtyUnlockedLayoutRoots.remove(root);
|
dirtyUnlockedLayoutRoots.remove(root);
|
||||||
settingsIdentityByRoot.remove(root);
|
settingsIdentityByRoot.remove(root);
|
||||||
|
lockscreenCardsRowFilterSignatures.remove(root);
|
||||||
clockTimeBucketByRoot.remove(root);
|
clockTimeBucketByRoot.remove(root);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1628,6 +1876,18 @@ final class StockLayoutCanvasController {
|
|||||||
return powerManager == null || powerManager.isInteractive();
|
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) {
|
private boolean isLayoutEnabled(Context context) {
|
||||||
SbtSettings settings = RuntimeSettingsCache.get(context);
|
SbtSettings settings = RuntimeSettingsCache.get(context);
|
||||||
return settings.clockEnabled;
|
return settings.clockEnabled;
|
||||||
|
|||||||
+55
-33
@@ -5,6 +5,7 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
@@ -47,11 +48,15 @@ public final class LockscreenCardsNotificationIconSourceStore {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
int skipCards = visibleShelfStartIndex(container, views);
|
int skipCards = visibleShelfStartIndex(container, views);
|
||||||
|
HashSet<String> visibleCardKeys = visibleCardKeys(root, container);
|
||||||
for (int i = 0; i < views.size(); i++) {
|
for (int i = 0; i < views.size(); i++) {
|
||||||
if (i < skipCards) {
|
if (i < skipCards) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
SourceEntry entry = views.get(i);
|
SourceEntry entry = views.get(i);
|
||||||
|
if (entry.key != null && visibleCardKeys.contains(entry.key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
View view = entry.view;
|
View view = entry.view;
|
||||||
if (view == null || !view.isAttachedToWindow()) {
|
if (view == null || !view.isAttachedToWindow()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -61,7 +66,46 @@ public final class LockscreenCardsNotificationIconSourceStore {
|
|||||||
if (width <= 0 || height <= 0) {
|
if (width <= 0 || height <= 0) {
|
||||||
continue;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -176,40 +220,18 @@ public final class LockscreenCardsNotificationIconSourceStore {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int visibleCardCountBeforeShelf(View root, View shelf) {
|
private static String keyForRow(View view) {
|
||||||
if (!(root instanceof ViewGroup group)) {
|
Object entry = ReflectionSupport.invokeMethod(view, "getEntry", new Class<?>[0]);
|
||||||
return 0;
|
String key = keyForEntry(entry);
|
||||||
|
if (key != null && !key.isEmpty()) {
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
int shelfTop = Integer.MAX_VALUE;
|
entry = ReflectionSupport.getFieldValue(view, "mEntry");
|
||||||
if (shelf != null) {
|
key = keyForEntry(entry);
|
||||||
se.ajpanton.statusbartweak.runtime.layoutsolver.StatusBarLayoutSolver.Bounds shelfBounds =
|
if (key != null && !key.isEmpty()) {
|
||||||
ViewGeometry.boundsRelativeTo(root, shelf);
|
return key;
|
||||||
if (shelfBounds != null) {
|
|
||||||
shelfTop = shelfBounds.top;
|
|
||||||
}
|
}
|
||||||
}
|
return keyForIconView(view);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isNotificationRow(View 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 SnapshotRenderer renderer = new SnapshotRenderer(false, false, false);
|
||||||
private final WeakHashMap<ViewGroup, ArrayList<View>> pillViewsByHost = new WeakHashMap<>();
|
private final WeakHashMap<ViewGroup, ArrayList<View>> pillViewsByHost = new WeakHashMap<>();
|
||||||
|
private final NotificationSeenAppReporter seenAppReporter = new NotificationSeenAppReporter();
|
||||||
|
|
||||||
public RenderResult render(
|
public RenderResult render(
|
||||||
View root,
|
View root,
|
||||||
@@ -65,8 +66,10 @@ public final class LockscreenCardsNotificationStripRenderer {
|
|||||||
clear(host);
|
clear(host);
|
||||||
return RenderResult.empty();
|
return RenderResult.empty();
|
||||||
}
|
}
|
||||||
|
ArrayList<SnapshotPlacement> unfilteredPlacements = renderer.rawPlacements(root, sources);
|
||||||
|
seenAppReporter.report(root.getContext(), unfilteredPlacements);
|
||||||
ArrayList<SnapshotPlacement> placements = SnapshotIconFilter.notificationPlacements(
|
ArrayList<SnapshotPlacement> placements = SnapshotIconFilter.notificationPlacements(
|
||||||
renderer.rawPlacements(root, sources),
|
unfilteredPlacements,
|
||||||
settings,
|
settings,
|
||||||
scene);
|
scene);
|
||||||
StripLayout layout = layoutPlacements(root, sourceRoot, placements, settings);
|
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.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,6 +28,13 @@ public final class OwnedIconHostManager {
|
|||||||
"statusbartweak.unlocked.chip.host";
|
"statusbartweak.unlocked.chip.host";
|
||||||
public static final String TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST =
|
public static final String TAG_LOCKSCREEN_CARDS_NOTIFICATION_HOST =
|
||||||
"statusbartweak.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) {
|
public FrameLayout ensureUnlockedStatusHost(View root) {
|
||||||
return ensureHost(root, TAG_UNLOCKED_STATUS_HOST);
|
return ensureHost(root, TAG_UNLOCKED_STATUS_HOST);
|
||||||
@@ -58,7 +67,6 @@ public final class OwnedIconHostManager {
|
|||||||
}
|
}
|
||||||
FrameLayout existing = findDirectHost(parent, tag);
|
FrameLayout existing = findDirectHost(parent, tag);
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.bringToFront();
|
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
FrameLayout host = new FrameLayout(parent.getContext());
|
FrameLayout host = new FrameLayout(parent.getContext());
|
||||||
@@ -94,19 +102,22 @@ public final class OwnedIconHostManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setUnlockedHostsVisible(View root, boolean visible) {
|
public void setUnlockedHostsVisible(View root, boolean visible) {
|
||||||
setHostVisible(root, TAG_UNLOCKED_STATUS_HOST, visible);
|
for (String tag : UNLOCKED_HOST_ORDER) {
|
||||||
setHostVisible(root, TAG_UNLOCKED_NOTIFICATION_HOST, visible);
|
setHostVisible(root, tag, visible);
|
||||||
setHostVisible(root, TAG_UNLOCKED_CLOCK_HOST, visible);
|
}
|
||||||
setHostVisible(root, TAG_UNLOCKED_CARRIER_HOST, visible);
|
|
||||||
setHostVisible(root, TAG_UNLOCKED_CHIP_HOST, visible);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bringUnlockedHostsToFront(View root) {
|
public void bringUnlockedHostsToFront(View root) {
|
||||||
bringHostToFront(root, TAG_UNLOCKED_STATUS_HOST);
|
if (!(root instanceof ViewGroup parent)) {
|
||||||
bringHostToFront(root, TAG_UNLOCKED_NOTIFICATION_HOST);
|
return;
|
||||||
bringHostToFront(root, TAG_UNLOCKED_CLOCK_HOST);
|
}
|
||||||
bringHostToFront(root, TAG_UNLOCKED_CARRIER_HOST);
|
ArrayList<View> hosts = unlockedHosts(parent);
|
||||||
bringHostToFront(root, TAG_UNLOCKED_CHIP_HOST);
|
if (hosts.isEmpty() || unlockedHostsAreAlreadyAtFront(parent, hosts)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (View host : hosts) {
|
||||||
|
host.bringToFront();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOwnedHost(View view) {
|
public boolean isOwnedHost(View view) {
|
||||||
@@ -140,17 +151,35 @@ public final class OwnedIconHostManager {
|
|||||||
if (host == null) {
|
if (host == null) {
|
||||||
return;
|
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) {
|
private ArrayList<View> unlockedHosts(ViewGroup parent) {
|
||||||
if (!(root instanceof ViewGroup parent) || tag == null) {
|
ArrayList<View> hosts = new ArrayList<>();
|
||||||
return;
|
for (String tag : UNLOCKED_HOST_ORDER) {
|
||||||
}
|
View host = findDirectHost(parent, tag);
|
||||||
FrameLayout host = findDirectHost(parent, tag);
|
|
||||||
if (host != null) {
|
if (host != null) {
|
||||||
host.bringToFront();
|
hosts.add(host);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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(
|
record SnapshotSource(
|
||||||
View view,
|
View view,
|
||||||
SnapshotMode mode
|
SnapshotMode mode,
|
||||||
|
String keyHint
|
||||||
) {
|
) {
|
||||||
|
SnapshotSource(View view, SnapshotMode mode) {
|
||||||
|
this(view, mode, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SnapshotMode {
|
enum SnapshotMode {
|
||||||
|
|||||||
@@ -347,6 +347,10 @@ final class SnapshotRenderer {
|
|||||||
|
|
||||||
private String keyFor(SnapshotSource source, int order) {
|
private String keyFor(SnapshotSource source, int order) {
|
||||||
View view = source.view();
|
View view = source.view();
|
||||||
|
String keyHint = source.keyHint();
|
||||||
|
if (keyHint != null && !keyHint.isEmpty()) {
|
||||||
|
return keyHint;
|
||||||
|
}
|
||||||
String slot = reflectString(view, "getSlot", "mSlot", "slot");
|
String slot = reflectString(view, "getSlot", "mSlot", "slot");
|
||||||
if (!slot.isEmpty()) {
|
if (!slot.isEmpty()) {
|
||||||
return slot;
|
return slot;
|
||||||
|
|||||||
+8
-4
@@ -27,6 +27,8 @@ final class UnlockedLayoutPlanner {
|
|||||||
private final SnapshotRenderer chipRenderer;
|
private final SnapshotRenderer chipRenderer;
|
||||||
private final UnlockedChipPlacementController chipPlacementController;
|
private final UnlockedChipPlacementController chipPlacementController;
|
||||||
private final UnlockedVerticalOffsetScaler offsetScaler;
|
private final UnlockedVerticalOffsetScaler offsetScaler;
|
||||||
|
private final NotificationSeenAppReporter notificationSeenAppReporter =
|
||||||
|
new NotificationSeenAppReporter();
|
||||||
|
|
||||||
UnlockedLayoutPlanner(
|
UnlockedLayoutPlanner(
|
||||||
SnapshotRenderer statusRenderer,
|
SnapshotRenderer statusRenderer,
|
||||||
@@ -130,11 +132,13 @@ final class UnlockedLayoutPlanner {
|
|||||||
}
|
}
|
||||||
int notificationCount = notificationCountForScene(settings, scene);
|
int notificationCount = notificationCountForScene(settings, scene);
|
||||||
if (notificationCount > 0) {
|
if (notificationCount > 0) {
|
||||||
|
ArrayList<SnapshotPlacement> unfilteredPlacements =
|
||||||
|
notificationRenderer.rawPlacements(root, collectedIcons.notificationIcons);
|
||||||
|
notificationSeenAppReporter.report(
|
||||||
|
root != null ? root.getContext() : null,
|
||||||
|
unfilteredPlacements);
|
||||||
ArrayList<SnapshotPlacement> rawPlacements =
|
ArrayList<SnapshotPlacement> rawPlacements =
|
||||||
SnapshotIconFilter.notificationPlacements(
|
SnapshotIconFilter.notificationPlacements(unfilteredPlacements, settings, scene);
|
||||||
notificationRenderer.rawPlacements(root, collectedIcons.notificationIcons),
|
|
||||||
settings,
|
|
||||||
scene);
|
|
||||||
if (scene != null
|
if (scene != null
|
||||||
&& scene.isLockscreen()
|
&& scene.isLockscreen()
|
||||||
&& scene.notificationDisplayStyle() == NotificationDisplayStyle.DOT) {
|
&& 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) {
|
private static boolean hasSettingsProvider(Context context) {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -429,13 +429,9 @@ final class LayoutSectionCopySupport {
|
|||||||
row.setTag(COPY_ROW_TAG);
|
row.setTag(COPY_ROW_TAG);
|
||||||
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
|
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
|
||||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
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(
|
LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
spinnerParams.leftMargin = dp(context, 8);
|
|
||||||
row.addView(spinner, spinnerParams);
|
row.addView(spinner, spinnerParams);
|
||||||
LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams(
|
LinearLayout.LayoutParams rowParams = new LinearLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
@@ -460,9 +456,6 @@ final class LayoutSectionCopySupport {
|
|||||||
row.setTag(COPY_ROW_TAG);
|
row.setTag(COPY_ROW_TAG);
|
||||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
|
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 spinner = new Spinner(context);
|
||||||
spinner.setAdapter(new CopyAdapter(context, options));
|
spinner.setAdapter(new CopyAdapter(context, options));
|
||||||
spinner.setSelection(0, false);
|
spinner.setSelection(0, false);
|
||||||
@@ -491,7 +484,6 @@ final class LayoutSectionCopySupport {
|
|||||||
LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams(
|
LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
spinnerParams.leftMargin = dp(context, 8);
|
|
||||||
row.addView(spinner, spinnerParams);
|
row.addView(spinner, spinnerParams);
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user