Refine clock layout and runtime rendering
This commit is contained in:
@@ -37,7 +37,7 @@ It also supports lightweight markup:
|
|||||||
|
|
||||||
- HTML-like tags: `<i>`, `<u>`, `<small>`, `<big>`, and `<font ...>`.
|
- HTML-like tags: `<i>`, `<u>`, `<small>`, `<big>`, and `<font ...>`.
|
||||||
- Shorthand blocks such as `{#FA0 big @sans-serif-condensed}HH:mm`.
|
- Shorthand blocks such as `{#FA0 big @sans-serif-condensed}HH:mm`.
|
||||||
- Multi-line text using `\n` or `{\\n12}`.
|
- Multi-line text using `\n` or `{\\n12}`; numbered line breaks use statusbar units.
|
||||||
- Pipe alignment with `|`, useful for aligning split clock rows around a camera
|
- Pipe alignment with `|`, useful for aligning split clock rows around a camera
|
||||||
cutout.
|
cutout.
|
||||||
- Colour variants and reverse Polish notation colour math for light/dark status
|
- Colour variants and reverse Polish notation colour math for light/dark status
|
||||||
|
|||||||
+20
-4
@@ -140,7 +140,7 @@ final class BatteryBarController {
|
|||||||
Object result = chain.proceed();
|
Object result = chain.proceed();
|
||||||
Object target = chain.getThisObject();
|
Object target = chain.getThisObject();
|
||||||
if (target instanceof View root && roots.contains(root)) {
|
if (target instanceof View root && roots.contains(root)) {
|
||||||
applyBatteryBar(root);
|
applyBatteryBarAfterRootTransform(root);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
@@ -148,7 +148,7 @@ final class BatteryBarController {
|
|||||||
Object result = chain.proceed();
|
Object result = chain.proceed();
|
||||||
Object target = chain.getThisObject();
|
Object target = chain.getThisObject();
|
||||||
if (target instanceof View root && roots.contains(root)) {
|
if (target instanceof View root && roots.contains(root)) {
|
||||||
applyBatteryBar(root);
|
applyBatteryBarAfterRootTransform(root);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
@@ -156,7 +156,7 @@ final class BatteryBarController {
|
|||||||
Object result = chain.proceed();
|
Object result = chain.proceed();
|
||||||
Object target = chain.getThisObject();
|
Object target = chain.getThisObject();
|
||||||
if (target instanceof View root && roots.contains(root)) {
|
if (target instanceof View root && roots.contains(root)) {
|
||||||
applyBatteryBar(root);
|
applyBatteryBarAfterRootTransform(root);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
@@ -164,12 +164,20 @@ final class BatteryBarController {
|
|||||||
Object result = chain.proceed();
|
Object result = chain.proceed();
|
||||||
Object target = chain.getThisObject();
|
Object target = chain.getThisObject();
|
||||||
if (target instanceof View root && roots.contains(root)) {
|
if (target instanceof View root && roots.contains(root)) {
|
||||||
applyBatteryBar(root);
|
applyBatteryBarAfterRootTransform(root);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyBatteryBarAfterRootTransform(View root) {
|
||||||
|
if (isHiddenKeyguardRoot(root)) {
|
||||||
|
removeBatteryBarView(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applyBatteryBar(root);
|
||||||
|
}
|
||||||
|
|
||||||
private void installSystemBarAttributeListener(ClassLoader classLoader) {
|
private void installSystemBarAttributeListener(ClassLoader classLoader) {
|
||||||
Class<?> commandQueueClass = ReflectionSupport.findClassIfExists(
|
Class<?> commandQueueClass = ReflectionSupport.findClassIfExists(
|
||||||
"com.android.systemui.statusbar.CommandQueue",
|
"com.android.systemui.statusbar.CommandQueue",
|
||||||
@@ -336,6 +344,10 @@ final class BatteryBarController {
|
|||||||
removeBatteryBarView(root);
|
removeBatteryBarView(root);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isHiddenKeyguardRoot(root)) {
|
||||||
|
removeBatteryBarView(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
||||||
if (!settings.batteryBarEnabled || root.getWidth() <= 0 || root.getHeight() <= 0) {
|
if (!settings.batteryBarEnabled || root.getWidth() <= 0 || root.getHeight() <= 0) {
|
||||||
removeBatteryBarView(root);
|
removeBatteryBarView(root);
|
||||||
@@ -570,6 +582,10 @@ final class BatteryBarController {
|
|||||||
return root != null && root.getClass().getName().contains("KeyguardStatusBarView");
|
return root != null && root.getClass().getName().contains("KeyguardStatusBarView");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isHiddenKeyguardRoot(View root) {
|
||||||
|
return isKeyguardRoot(root) && !root.isShown();
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isLandscapeForRoot(View root, boolean preferDisplayBounds) {
|
private boolean isLandscapeForRoot(View root, boolean preferDisplayBounds) {
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+181
-34
@@ -9,6 +9,7 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -36,6 +37,8 @@ final class DrawerClockTextController {
|
|||||||
private final ClockTextRenderer textRenderer = new ClockTextRenderer();
|
private final ClockTextRenderer textRenderer = new ClockTextRenderer();
|
||||||
private final Map<TextView, Role> trackedRoles = new WeakHashMap<>();
|
private final Map<TextView, Role> trackedRoles = new WeakHashMap<>();
|
||||||
private final Map<TextView, Snapshot> stockSnapshots = new WeakHashMap<>();
|
private final Map<TextView, Snapshot> stockSnapshots = new WeakHashMap<>();
|
||||||
|
private final Map<TextView, RenderCacheEntry> renderCache = new WeakHashMap<>();
|
||||||
|
private final Map<View, CandidateCache> candidateCacheByRoot = new WeakHashMap<>();
|
||||||
private final Map<TextView, Runnable> tickers = new WeakHashMap<>();
|
private final Map<TextView, Runnable> tickers = new WeakHashMap<>();
|
||||||
private final Set<TextView> ownedTexts = Collections.newSetFromMap(new WeakHashMap<>());
|
private final Set<TextView> ownedTexts = Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
private boolean hooksInstalled;
|
private boolean hooksInstalled;
|
||||||
@@ -90,22 +93,17 @@ final class DrawerClockTextController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
registerReceiverIfNeeded(root.getContext());
|
registerReceiverIfNeeded(root.getContext());
|
||||||
|
untrackDisabledRoles(clockEnabled, dateEnabled);
|
||||||
|
|
||||||
Candidate clock = null;
|
CandidateSelection candidates = cachedCandidates(root, clockEnabled, dateEnabled);
|
||||||
Candidate date = null;
|
if (candidates == null) {
|
||||||
for (TextView textView : textViewsIn(root)) {
|
candidates = findCandidates(root);
|
||||||
if (isStatusbarClock(textView)) {
|
candidateCacheByRoot.put(root, new CandidateCache(
|
||||||
continue;
|
candidates.clock != null ? candidates.clock.view : null,
|
||||||
}
|
candidates.date != null ? candidates.date.view : null));
|
||||||
int clockScore = scoreClock(textView);
|
|
||||||
if (clockScore > 0 && (clock == null || clockScore > clock.score)) {
|
|
||||||
clock = new Candidate(textView, clockScore);
|
|
||||||
}
|
|
||||||
int dateScore = scoreDate(textView);
|
|
||||||
if (dateScore > 0 && (date == null || dateScore > date.score)) {
|
|
||||||
date = new Candidate(textView, dateScore);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Candidate clock = candidates.clock;
|
||||||
|
Candidate date = candidates.date;
|
||||||
|
|
||||||
Set<TextView> selected = Collections.newSetFromMap(new WeakHashMap<>());
|
Set<TextView> selected = Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
if (clock != null) {
|
if (clock != null) {
|
||||||
@@ -128,6 +126,16 @@ final class DrawerClockTextController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void untrackDisabledRoles(boolean clockEnabled, boolean dateEnabled) {
|
||||||
|
for (TextView textView : new ArrayList<>(trackedRoles.keySet())) {
|
||||||
|
Role role = trackedRoles.get(textView);
|
||||||
|
if (role == Role.CLOCK && !clockEnabled || role == Role.DATE && !dateEnabled) {
|
||||||
|
restore(textView);
|
||||||
|
untrack(textView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void track(TextView textView, Role role) {
|
private void track(TextView textView, Role role) {
|
||||||
if (textView == null || role == null) {
|
if (textView == null || role == null) {
|
||||||
return;
|
return;
|
||||||
@@ -141,6 +149,7 @@ final class DrawerClockTextController {
|
|||||||
private void untrack(TextView textView) {
|
private void untrack(TextView textView) {
|
||||||
trackedRoles.remove(textView);
|
trackedRoles.remove(textView);
|
||||||
stockSnapshots.remove(textView);
|
stockSnapshots.remove(textView);
|
||||||
|
renderCache.remove(textView);
|
||||||
ownedTexts.remove(textView);
|
ownedTexts.remove(textView);
|
||||||
stopTicker(textView);
|
stopTicker(textView);
|
||||||
}
|
}
|
||||||
@@ -158,22 +167,55 @@ final class DrawerClockTextController {
|
|||||||
restore(textView);
|
restore(textView);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RenderedText rendered = render(textView.getContext(), pattern(settings, role), textView.getCurrentTextColor());
|
String customPattern = pattern(settings, role);
|
||||||
|
RenderedText rendered = renderCached(
|
||||||
|
textView,
|
||||||
|
textView.getContext(),
|
||||||
|
customPattern,
|
||||||
|
textView.getCurrentTextColor());
|
||||||
if (rendered == null) {
|
if (rendered == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeText(textView, rendered.text, forceWrite);
|
writeText(textView, rendered.text, forceWrite || !rendered.cacheHit);
|
||||||
if (!TextUtils.equals(textView.getContentDescription(), rendered.contentDescription)) {
|
if (!TextUtils.equals(textView.getContentDescription(), rendered.contentDescription)) {
|
||||||
textView.setContentDescription(rendered.contentDescription);
|
textView.setContentDescription(rendered.contentDescription);
|
||||||
}
|
}
|
||||||
ownedTexts.add(textView);
|
ownedTexts.add(textView);
|
||||||
if (ClockMarkupSupport.containsSecondsPattern(pattern(settings, role))) {
|
if (ClockMarkupSupport.containsSecondsPattern(customPattern)) {
|
||||||
startTicker(textView);
|
startTicker(textView);
|
||||||
} else {
|
} else {
|
||||||
stopTicker(textView);
|
stopTicker(textView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RenderedText renderCached(
|
||||||
|
TextView textView,
|
||||||
|
Context context,
|
||||||
|
String pattern,
|
||||||
|
int referenceColor
|
||||||
|
) {
|
||||||
|
if (context == null || TextUtils.isEmpty(pattern)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
RenderCacheKey key = new RenderCacheKey(
|
||||||
|
pattern,
|
||||||
|
referenceColor,
|
||||||
|
localeKey(context),
|
||||||
|
timeBucket(pattern));
|
||||||
|
RenderCacheEntry cached = renderCache.get(textView);
|
||||||
|
if (cached != null && cached.key.equals(key)) {
|
||||||
|
return new RenderedText(cached.text, cached.contentDescription, true);
|
||||||
|
}
|
||||||
|
RenderedText rendered = render(context, pattern, referenceColor);
|
||||||
|
if (rendered != null) {
|
||||||
|
renderCache.put(textView, new RenderCacheEntry(
|
||||||
|
key,
|
||||||
|
rendered.text,
|
||||||
|
rendered.contentDescription));
|
||||||
|
}
|
||||||
|
return rendered;
|
||||||
|
}
|
||||||
|
|
||||||
private RenderedText render(Context context, String pattern, int referenceColor) {
|
private RenderedText render(Context context, String pattern, int referenceColor) {
|
||||||
if (context == null || TextUtils.isEmpty(pattern)) {
|
if (context == null || TextUtils.isEmpty(pattern)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -182,7 +224,7 @@ final class DrawerClockTextController {
|
|||||||
ArrayList<ClockTextRenderer.RowResult> rows =
|
ArrayList<ClockTextRenderer.RowResult> rows =
|
||||||
textRenderer.renderRows(context, parsedPattern, referenceColor);
|
textRenderer.renderRows(context, parsedPattern, referenceColor);
|
||||||
if (rows.isEmpty()) {
|
if (rows.isEmpty()) {
|
||||||
return new RenderedText("", "");
|
return new RenderedText("", "", false);
|
||||||
}
|
}
|
||||||
SpannableStringBuilder text = new SpannableStringBuilder();
|
SpannableStringBuilder text = new SpannableStringBuilder();
|
||||||
StringBuilder description = new StringBuilder();
|
StringBuilder description = new StringBuilder();
|
||||||
@@ -195,7 +237,7 @@ final class DrawerClockTextController {
|
|||||||
text.append(row.text);
|
text.append(row.text);
|
||||||
description.append(row.contentDescription);
|
description.append(row.contentDescription);
|
||||||
}
|
}
|
||||||
return new RenderedText(text, description.toString());
|
return new RenderedText(text, description.toString(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restore(TextView textView) {
|
private void restore(TextView textView) {
|
||||||
@@ -205,6 +247,7 @@ final class DrawerClockTextController {
|
|||||||
Snapshot snapshot = stockSnapshots.get(textView);
|
Snapshot snapshot = stockSnapshots.get(textView);
|
||||||
if (snapshot == null) {
|
if (snapshot == null) {
|
||||||
ownedTexts.remove(textView);
|
ownedTexts.remove(textView);
|
||||||
|
renderCache.remove(textView);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeText(textView, snapshot.text, false);
|
writeText(textView, snapshot.text, false);
|
||||||
@@ -212,6 +255,7 @@ final class DrawerClockTextController {
|
|||||||
textView.setContentDescription(snapshot.contentDescription);
|
textView.setContentDescription(snapshot.contentDescription);
|
||||||
}
|
}
|
||||||
ownedTexts.remove(textView);
|
ownedTexts.remove(textView);
|
||||||
|
renderCache.remove(textView);
|
||||||
stopTicker(textView);
|
stopTicker(textView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +287,7 @@ final class DrawerClockTextController {
|
|||||||
BroadcastReceiver receiver = new BroadcastReceiver() {
|
BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context ctx, Intent intent) {
|
public void onReceive(Context ctx, Intent intent) {
|
||||||
|
renderCache.clear();
|
||||||
refreshTrackedTexts();
|
refreshTrackedTexts();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -300,6 +345,19 @@ final class DrawerClockTextController {
|
|||||||
textView.postDelayed(ticker, Math.max(1L, next - now));
|
textView.postDelayed(ticker, Math.max(1L, next - now));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long timeBucket(String pattern) {
|
||||||
|
long divisor = ClockMarkupSupport.containsSecondsPattern(pattern) ? 1_000L : 60_000L;
|
||||||
|
return System.currentTimeMillis() / divisor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String localeKey(Context context) {
|
||||||
|
if (context == null || context.getResources() == null) {
|
||||||
|
return Locale.getDefault().toLanguageTag();
|
||||||
|
}
|
||||||
|
Locale locale = context.getResources().getConfiguration().getLocales().get(0);
|
||||||
|
return locale != null ? locale.toLanguageTag() : Locale.getDefault().toLanguageTag();
|
||||||
|
}
|
||||||
|
|
||||||
private boolean sameText(CharSequence left, CharSequence right) {
|
private boolean sameText(CharSequence left, CharSequence right) {
|
||||||
return TextUtils.equals(left != null ? left.toString() : "", right != null ? right.toString() : "");
|
return TextUtils.equals(left != null ? left.toString() : "", right != null ? right.toString() : "");
|
||||||
}
|
}
|
||||||
@@ -323,20 +381,25 @@ final class DrawerClockTextController {
|
|||||||
: settings.drawerDateCustomFormat;
|
: settings.drawerDateCustomFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Iterable<TextView> textViewsIn(View root) {
|
private CandidateSelection findCandidates(View root) {
|
||||||
ArrayList<TextView> textViews = new ArrayList<>();
|
Candidate clock = null;
|
||||||
if (!(root instanceof ViewGroup rootGroup)) {
|
Candidate date = null;
|
||||||
if (root instanceof TextView textView) {
|
|
||||||
textViews.add(textView);
|
|
||||||
}
|
|
||||||
return textViews;
|
|
||||||
}
|
|
||||||
ArrayDeque<View> queue = new ArrayDeque<>();
|
ArrayDeque<View> queue = new ArrayDeque<>();
|
||||||
queue.add(rootGroup);
|
queue.add(root);
|
||||||
while (!queue.isEmpty()) {
|
while (!queue.isEmpty()) {
|
||||||
View view = queue.removeFirst();
|
View view = queue.removeFirst();
|
||||||
if (view instanceof TextView textView) {
|
if (view instanceof TextView textView) {
|
||||||
textViews.add(textView);
|
if (!isStatusbarClock(textView)) {
|
||||||
|
String metadata = metadata(textView);
|
||||||
|
int clockScore = scoreClock(textView, metadata);
|
||||||
|
if (clockScore > 0 && (clock == null || clockScore > clock.score)) {
|
||||||
|
clock = new Candidate(textView, clockScore);
|
||||||
|
}
|
||||||
|
int dateScore = scoreDate(textView, metadata);
|
||||||
|
if (dateScore > 0 && (date == null || dateScore > date.score)) {
|
||||||
|
date = new Candidate(textView, dateScore);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (view instanceof ViewGroup group) {
|
if (view instanceof ViewGroup group) {
|
||||||
for (int i = 0; i < group.getChildCount(); i++) {
|
for (int i = 0; i < group.getChildCount(); i++) {
|
||||||
@@ -347,11 +410,63 @@ final class DrawerClockTextController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return textViews;
|
return new CandidateSelection(clock, date);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int scoreClock(TextView textView) {
|
private CandidateSelection cachedCandidates(
|
||||||
|
View root,
|
||||||
|
boolean clockEnabled,
|
||||||
|
boolean dateEnabled
|
||||||
|
) {
|
||||||
|
CandidateCache cache = candidateCacheByRoot.get(root);
|
||||||
|
if (cache == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Candidate clock = candidateFromCache(root, cache.clockView, Role.CLOCK, clockEnabled);
|
||||||
|
Candidate date = candidateFromCache(root, cache.dateView, Role.DATE, dateEnabled);
|
||||||
|
if (clockEnabled && clock == null || dateEnabled && date == null) {
|
||||||
|
candidateCacheByRoot.remove(root);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new CandidateSelection(clock, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Candidate candidateFromCache(
|
||||||
|
View root,
|
||||||
|
WeakReference<TextView> reference,
|
||||||
|
Role role,
|
||||||
|
boolean enabled
|
||||||
|
) {
|
||||||
|
if (!enabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TextView textView = reference != null ? reference.get() : null;
|
||||||
|
if (textView == null
|
||||||
|
|| !textView.isAttachedToWindow()
|
||||||
|
|| !isDescendantOf(textView, root)
|
||||||
|
|| isStatusbarClock(textView)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
String metadata = metadata(textView);
|
String metadata = metadata(textView);
|
||||||
|
int score = role == Role.CLOCK
|
||||||
|
? scoreClock(textView, metadata)
|
||||||
|
: scoreDate(textView, metadata);
|
||||||
|
return score > 0 ? new Candidate(textView, score) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDescendantOf(View view, View ancestor) {
|
||||||
|
View current = view;
|
||||||
|
while (current != null) {
|
||||||
|
if (current == ancestor) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Object parent = current.getParent();
|
||||||
|
current = parent instanceof View ? (View) parent : null;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int scoreClock(TextView textView, String metadata) {
|
||||||
if (metadata.contains("date") || metadata.contains("day") || isKeyguardOrAodText(metadata)) {
|
if (metadata.contains("date") || metadata.contains("day") || isKeyguardOrAodText(metadata)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -375,8 +490,7 @@ final class DrawerClockTextController {
|
|||||||
return score + Math.round(textView.getTextSize());
|
return score + Math.round(textView.getTextSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
private int scoreDate(TextView textView) {
|
private int scoreDate(TextView textView, String metadata) {
|
||||||
String metadata = metadata(textView);
|
|
||||||
if (metadata.contains("clock") || isKeyguardOrAodText(metadata)) {
|
if (metadata.contains("clock") || isKeyguardOrAodText(metadata)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -436,7 +550,40 @@ final class DrawerClockTextController {
|
|||||||
private record Candidate(TextView view, int score) {
|
private record Candidate(TextView view, int score) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private record RenderedText(CharSequence text, String contentDescription) {
|
private record CandidateSelection(Candidate clock, Candidate date) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CandidateCache {
|
||||||
|
final WeakReference<TextView> clockView;
|
||||||
|
final WeakReference<TextView> dateView;
|
||||||
|
|
||||||
|
CandidateCache(TextView clockView, TextView dateView) {
|
||||||
|
this.clockView = new WeakReference<>(clockView);
|
||||||
|
this.dateView = new WeakReference<>(dateView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record RenderCacheKey(
|
||||||
|
String pattern,
|
||||||
|
int referenceColor,
|
||||||
|
String localeKey,
|
||||||
|
long timeBucket
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record RenderedText(CharSequence text, String contentDescription, boolean cacheHit) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RenderCacheEntry {
|
||||||
|
final RenderCacheKey key;
|
||||||
|
final CharSequence text;
|
||||||
|
final String contentDescription;
|
||||||
|
|
||||||
|
RenderCacheEntry(RenderCacheKey key, CharSequence text, String contentDescription) {
|
||||||
|
this.key = key;
|
||||||
|
this.text = text != null ? text : "";
|
||||||
|
this.contentDescription = contentDescription != null ? contentDescription : "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class Snapshot {
|
private static final class Snapshot {
|
||||||
|
|||||||
+46
-15
@@ -583,15 +583,17 @@ final class StockClockController {
|
|||||||
source.getLocationOnScreen(sourceLocation);
|
source.getLocationOnScreen(sourceLocation);
|
||||||
host.getLocationOnScreen(hostLocation);
|
host.getLocationOnScreen(hostLocation);
|
||||||
|
|
||||||
int sourceBaselineY = sourceLocation[1] + source.getBaseline();
|
int sourceBaselineY = sourceLocation[1] - hostLocation[1] + source.getBaseline();
|
||||||
ArrayList<RowMetrics> rowMetrics = measureRows(source, rows);
|
ArrayList<RowMetrics> rowMetrics = measureRows(source, rows);
|
||||||
int anchorOffset = alignmentOffset(rowMetrics.get(rows.size() - 1), rowMetrics);
|
int anchorOffset = alignmentOffset(rowMetrics.get(rows.size() - 1), rowMetrics);
|
||||||
int cumulativeOffset = 0;
|
RowMetrics bottomMetrics = rowMetrics.get(rows.size() - 1);
|
||||||
|
int rowBottomY = sourceBaselineY - bottomMetrics.baseline + bottomMetrics.height;
|
||||||
for (int rowIndex = rows.size() - 2; rowIndex >= 0; rowIndex--) {
|
for (int rowIndex = rows.size() - 2; rowIndex >= 0; rowIndex--) {
|
||||||
ClockTextRenderer.LayoutRowResult nextRow = rows.get(rowIndex + 1);
|
ClockTextRenderer.LayoutRowResult nextRow = rows.get(rowIndex + 1);
|
||||||
cumulativeOffset += resolveGap(nextRow != null ? nextRow.gapBeforePx : 0, source);
|
rowBottomY -= resolveGap(nextRow != null ? nextRow.gapBeforePx : 0, source);
|
||||||
TextView rowView = rowViews.get(rowIndex);
|
TextView rowView = rowViews.get(rowIndex);
|
||||||
ClockTextRenderer.LayoutRowResult row = rows.get(rowIndex);
|
ClockTextRenderer.LayoutRowResult row = rows.get(rowIndex);
|
||||||
|
RowMetrics metrics = rowMetrics.get(rowIndex);
|
||||||
rowView.setText(row != null ? row.text : "");
|
rowView.setText(row != null ? row.text : "");
|
||||||
copyTextViewStyle(source, rowView);
|
copyTextViewStyle(source, rowView);
|
||||||
positionRowView(
|
positionRowView(
|
||||||
@@ -600,7 +602,7 @@ final class StockClockController {
|
|||||||
sourceLocation[0] - hostLocation[0]
|
sourceLocation[0] - hostLocation[0]
|
||||||
+ alignmentOffset(rowMetrics.get(rowIndex), rowMetrics)
|
+ alignmentOffset(rowMetrics.get(rowIndex), rowMetrics)
|
||||||
- anchorOffset,
|
- anchorOffset,
|
||||||
sourceBaselineY - hostLocation[1] - cumulativeOffset);
|
rowBottomY - metrics.height + metrics.baseline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,33 +625,36 @@ final class StockClockController {
|
|||||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
copyTextViewStyle(source, probe);
|
copyTextViewStyle(source, probe);
|
||||||
for (ClockTextRenderer.LayoutRowResult row : rows) {
|
for (ClockTextRenderer.LayoutRowResult row : rows) {
|
||||||
metrics.add(measureRow(probe, row));
|
metrics.add(measureRowMetrics(probe, row));
|
||||||
}
|
}
|
||||||
return metrics;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RowMetrics measureRow(
|
private RowMetrics measureRowMetrics(
|
||||||
TextView probe,
|
TextView probe,
|
||||||
ClockTextRenderer.LayoutRowResult row
|
ClockTextRenderer.LayoutRowResult row
|
||||||
) {
|
) {
|
||||||
|
TextMetrics text = measureText(probe, row != null ? row.text : "");
|
||||||
if (row == null || row.pipeCount <= 0) {
|
if (row == null || row.pipeCount <= 0) {
|
||||||
return new RowMetrics(0, 0, 0);
|
return new RowMetrics(text.width, text.height, text.baseline, 0, 0);
|
||||||
}
|
}
|
||||||
int width = measureTextWidth(probe, row.text);
|
int leftPipe = measureText(probe, row.leftText).width;
|
||||||
int leftPipe = measureTextWidth(probe, row.leftText);
|
int rightPipe = text.width - measureText(probe, row.rightText).width;
|
||||||
int rightPipe = width - measureTextWidth(probe, row.rightText);
|
|
||||||
int pipePosition = row.pipeCount > 1
|
int pipePosition = row.pipeCount > 1
|
||||||
? Math.round((leftPipe + rightPipe) / 2f)
|
? Math.round((leftPipe + rightPipe) / 2f)
|
||||||
: leftPipe;
|
: leftPipe;
|
||||||
return new RowMetrics(width, pipePosition, row.pipeCount);
|
return new RowMetrics(text.width, text.height, text.baseline, pipePosition, row.pipeCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int measureTextWidth(TextView probe, CharSequence text) {
|
private TextMetrics measureText(TextView probe, CharSequence text) {
|
||||||
probe.setText(text != null ? text : "");
|
probe.setText(text != null ? text : "");
|
||||||
int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||||
int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||||
probe.measure(widthSpec, heightSpec);
|
probe.measure(widthSpec, heightSpec);
|
||||||
return Math.max(0, probe.getMeasuredWidth());
|
return new TextMetrics(
|
||||||
|
Math.max(0, probe.getMeasuredWidth()),
|
||||||
|
Math.max(1, probe.getMeasuredHeight()),
|
||||||
|
Math.max(0, probe.getBaseline()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int alignmentOffset(RowMetrics row, ArrayList<RowMetrics> rows) {
|
private int alignmentOffset(RowMetrics row, ArrayList<RowMetrics> rows) {
|
||||||
@@ -747,13 +752,39 @@ final class StockClockController {
|
|||||||
if (gapBeforePx == ClockPatternParser.ROW_GAP_AUTO) {
|
if (gapBeforePx == ClockPatternParser.ROW_GAP_AUTO) {
|
||||||
return Math.max(1, source.getLineHeight());
|
return Math.max(1, source.getLineHeight());
|
||||||
}
|
}
|
||||||
return gapBeforePx;
|
return stepsToPx(gapBeforePx, statusbarHeight(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int statusbarHeight(TextView source) {
|
||||||
|
if (source == null || source.getHeight() <= 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
View root = source.getRootView();
|
||||||
|
if (root == null || root.getHeight() <= 0) {
|
||||||
|
return Math.max(1, source.getTop() + source.getHeight());
|
||||||
|
}
|
||||||
|
int[] sourceLocation = new int[2];
|
||||||
|
int[] rootLocation = new int[2];
|
||||||
|
source.getLocationOnScreen(sourceLocation);
|
||||||
|
root.getLocationOnScreen(rootLocation);
|
||||||
|
int bottom = sourceLocation[1] - rootLocation[1] + source.getHeight();
|
||||||
|
return Math.max(1, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int stepsToPx(int steps, int statusbarHeight) {
|
||||||
|
if (steps == 0 || statusbarHeight <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.round(steps * statusbarHeight / 100f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record VisualState(boolean visible, float alpha) {
|
private record VisualState(boolean visible, float alpha) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private record RowMetrics(int width, int pipePosition, int pipeCount) {
|
private record RowMetrics(int width, int height, int baseline, int pipePosition, int pipeCount) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private record TextMetrics(int width, int height, int baseline) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+192
-11
@@ -131,12 +131,15 @@ final class StockLayoutCanvasController {
|
|||||||
private final Map<View, Integer> statusChipContentSignatureByRoot = new WeakHashMap<>();
|
private final Map<View, Integer> statusChipContentSignatureByRoot = new WeakHashMap<>();
|
||||||
private final Map<View, Integer> statusChipLayoutSignatureByRoot = new WeakHashMap<>();
|
private final Map<View, Integer> statusChipLayoutSignatureByRoot = new WeakHashMap<>();
|
||||||
private final Map<View, Integer> statusChipLifecycleLayoutSignatureByView = new WeakHashMap<>();
|
private final Map<View, Integer> statusChipLifecycleLayoutSignatureByView = new WeakHashMap<>();
|
||||||
|
private final Map<View, Integer> aodBeforeDrawSignatureByRoot = new WeakHashMap<>();
|
||||||
private final Set<View> trackedUnlockedStockSurfaces =
|
private final Set<View> trackedUnlockedStockSurfaces =
|
||||||
Collections.newSetFromMap(new WeakHashMap<>());
|
Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
private final Set<View> trackedLockedStatusBarIconSurfaces =
|
private final Set<View> trackedLockedStatusBarIconSurfaces =
|
||||||
Collections.newSetFromMap(new WeakHashMap<>());
|
Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
private final Set<View> pendingUnlockedAppearanceRefreshRoots =
|
private final Set<View> pendingUnlockedAppearanceRefreshRoots =
|
||||||
Collections.newSetFromMap(new WeakHashMap<>());
|
Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
|
private final Set<View> pendingDrawerAppearanceRefreshRoots =
|
||||||
|
Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
private final Set<View> dirtyUnlockedLayoutRoots =
|
private final Set<View> dirtyUnlockedLayoutRoots =
|
||||||
Collections.newSetFromMap(new WeakHashMap<>());
|
Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
private final Set<View> pendingDrawerStatusChipRefreshRoots =
|
private final Set<View> pendingDrawerStatusChipRefreshRoots =
|
||||||
@@ -176,6 +179,9 @@ final class StockLayoutCanvasController {
|
|||||||
private boolean hooksInstalled;
|
private boolean hooksInstalled;
|
||||||
private int ownHiddenViewAlphaWriteDepth;
|
private int ownHiddenViewAlphaWriteDepth;
|
||||||
private boolean notificationDrawerWasOpen;
|
private boolean notificationDrawerWasOpen;
|
||||||
|
private int notificationDrawerCloseGeneration;
|
||||||
|
private boolean notificationDrawerClosePending;
|
||||||
|
private int aodVisualAlphaGeneration;
|
||||||
|
|
||||||
StockLayoutCanvasController(RuntimeContext runtimeContext) {
|
StockLayoutCanvasController(RuntimeContext runtimeContext) {
|
||||||
this.runtimeContext = runtimeContext;
|
this.runtimeContext = runtimeContext;
|
||||||
@@ -288,8 +294,7 @@ final class StockLayoutCanvasController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (expansion <= 0.01f) {
|
if (expansion <= 0.01f) {
|
||||||
notificationDrawerWasOpen = false;
|
scheduleNotificationDrawerClosed(rootForShadeTarget(target));
|
||||||
flushDeferredDrawerStatusChipRefreshes();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (expansion <= 0.05f) {
|
if (expansion <= 0.05f) {
|
||||||
@@ -315,6 +320,8 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void markNotificationDrawerSeen() {
|
private void markNotificationDrawerSeen() {
|
||||||
|
notificationDrawerCloseGeneration++;
|
||||||
|
notificationDrawerClosePending = false;
|
||||||
if (notificationDrawerWasOpen) {
|
if (notificationDrawerWasOpen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -338,9 +345,55 @@ final class StockLayoutCanvasController {
|
|||||||
markNotificationDrawerSeen();
|
markNotificationDrawerSeen();
|
||||||
} else if (previousState == STATUS_BAR_STATE_SHADE_LOCKED
|
} else if (previousState == STATUS_BAR_STATE_SHADE_LOCKED
|
||||||
&& currentState != STATUS_BAR_STATE_SHADE_LOCKED) {
|
&& currentState != STATUS_BAR_STATE_SHADE_LOCKED) {
|
||||||
|
closeNotificationDrawerNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleNotificationDrawerClosed(View root) {
|
||||||
|
if (!notificationDrawerWasOpen
|
||||||
|
&& pendingDrawerAppearanceRefreshRoots.isEmpty()
|
||||||
|
&& pendingDrawerStatusChipRefreshRoots.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
View closeRoot = root != null ? root : firstAttachedUnlockedRoot();
|
||||||
|
if (notificationDrawerClosePending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notificationDrawerClosePending = true;
|
||||||
|
int generation = notificationDrawerCloseGeneration;
|
||||||
|
View postRoot = closeRoot;
|
||||||
|
Runnable close = () -> {
|
||||||
|
if (!notificationDrawerClosePending || generation != notificationDrawerCloseGeneration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notificationDrawerClosePending = false;
|
||||||
|
closeNotificationDrawerNow();
|
||||||
|
};
|
||||||
|
if (postRoot != null && postRoot.getHandler() != null) {
|
||||||
|
postRoot.post(close);
|
||||||
|
} else {
|
||||||
|
close.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeNotificationDrawerNow() {
|
||||||
notificationDrawerWasOpen = false;
|
notificationDrawerWasOpen = false;
|
||||||
|
flushDeferredDrawerAppearanceRefreshes();
|
||||||
flushDeferredDrawerStatusChipRefreshes();
|
flushDeferredDrawerStatusChipRefreshes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private View firstAttachedUnlockedRoot() {
|
||||||
|
for (View root : new ArrayList<>(unlockedHostsByRoot.keySet())) {
|
||||||
|
if (root != null && root.isAttachedToWindow()) {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (View root : new ArrayList<>(rootGeometries.keySet())) {
|
||||||
|
if (root != null && root.isAttachedToWindow() && isUnlockedStatusBarRoot(root)) {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void installShadeHeaderAnimationGuardHook(ClassLoader classLoader) {
|
private void installShadeHeaderAnimationGuardHook(ClassLoader classLoader) {
|
||||||
@@ -536,7 +589,6 @@ final class StockLayoutCanvasController {
|
|||||||
hookStatusChipLifecycle(touchInterceptClass, "onLayout");
|
hookStatusChipLifecycle(touchInterceptClass, "onLayout");
|
||||||
hookStatusChipLifecycle(touchInterceptClass, "setVisibility");
|
hookStatusChipLifecycle(touchInterceptClass, "setVisibility");
|
||||||
hookStatusChipLifecycle(touchInterceptClass, "setAlpha");
|
hookStatusChipLifecycle(touchInterceptClass, "setAlpha");
|
||||||
hookViewLayoutInvalidation(touchInterceptClass, "onLayout");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?> batteryClass = ReflectionSupport.findClassIfExists(
|
Class<?> batteryClass = ReflectionSupport.findClassIfExists(
|
||||||
@@ -1279,6 +1331,11 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
|
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||||
if (activeScene != null && activeScene.isAod()) {
|
if (activeScene != null && activeScene.isAod()) {
|
||||||
|
int signature = aodBeforeDrawSignature(root, settings, activeScene);
|
||||||
|
Integer previous = aodBeforeDrawSignatureByRoot.put(root, signature);
|
||||||
|
if (previous != null && previous == signature) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (hasVisibleConfirmedAodPluginView()) {
|
if (hasVisibleConfirmedAodPluginView()) {
|
||||||
hideConfirmedAodPluginViews();
|
hideConfirmedAodPluginViews();
|
||||||
}
|
}
|
||||||
@@ -1292,6 +1349,7 @@ final class StockLayoutCanvasController {
|
|||||||
syncAodOwnedHostVisualState(root);
|
syncAodOwnedHostVisualState(root);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
aodBeforeDrawSignatureByRoot.remove(root);
|
||||||
removeAodCardsNotificationHost(root);
|
removeAodCardsNotificationHost(root);
|
||||||
if (activeScene != null
|
if (activeScene != null
|
||||||
&& !activeScene.isUnlocked()) {
|
&& !activeScene.isUnlocked()) {
|
||||||
@@ -1331,6 +1389,50 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int aodBeforeDrawSignature(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 + (root != null ? root.getWidth() : 0);
|
||||||
|
result = 31 * result + (root != null ? root.getHeight() : 0);
|
||||||
|
result = 31 * result + aodVisualAlphaGeneration;
|
||||||
|
result = 31 * result + confirmedAodPluginViews.size();
|
||||||
|
result = 31 * result + latestAodIconImageViews.size();
|
||||||
|
result = 31 * result + latestAodNotifications.size();
|
||||||
|
result = 31 * result + aodNotificationSourceGeneration;
|
||||||
|
result = 31 * result + Float.floatToIntBits(aodVisualAlphaForRoot(root));
|
||||||
|
result = 31 * result + aodPluginVisibilitySignature(root);
|
||||||
|
if (activeScene != null
|
||||||
|
&& activeScene.notificationDisplayStyle() == NotificationDisplayStyle.CARDS) {
|
||||||
|
Bounds sourceBounds = LockscreenCardsNotificationIconSourceStore
|
||||||
|
.sourceContainerBoundsForRoot(root);
|
||||||
|
result = 31 * result + (sourceBounds != null ? sourceBounds.signature() : 0);
|
||||||
|
result = 31 * result
|
||||||
|
+ (LockscreenCardsNotificationIconSourceStore
|
||||||
|
.sourceContainerHasHiddenAncestorForRoot(root) ? 1 : 0);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int aodPluginVisibilitySignature(View root) {
|
||||||
|
int result = 17;
|
||||||
|
ArrayDeque<View> staleViews = new ArrayDeque<>();
|
||||||
|
for (View view : confirmedAodPluginViews) {
|
||||||
|
if (view == null || !view.isAttachedToWindow()) {
|
||||||
|
staleViews.add(view);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (root != null && !isDescendantOf(view, root) && !isDescendantOf(root, view)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result = 31 * result + System.identityHashCode(view);
|
||||||
|
result = 31 * result + view.getVisibility();
|
||||||
|
result = 31 * result + Float.floatToIntBits(view.getAlpha());
|
||||||
|
}
|
||||||
|
removeStaleAodPluginViews(staleViews);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private void scheduleUnlockedAppearanceRefresh(View sourceView) {
|
private void scheduleUnlockedAppearanceRefresh(View sourceView) {
|
||||||
View root = unlockedStatusBarRootFor(sourceView);
|
View root = unlockedStatusBarRootFor(sourceView);
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
@@ -1450,6 +1552,45 @@ final class StockLayoutCanvasController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldDeferUnlockedRenderForDrawer(View root) {
|
||||||
|
if (root == null
|
||||||
|
|| !root.isAttachedToWindow()
|
||||||
|
|| !unlockedIconRenderController.hasLayoutPlan(root)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||||
|
return activeScene != null
|
||||||
|
&& activeScene.isUnlocked()
|
||||||
|
&& (notificationDrawerWasOpen
|
||||||
|
|| pendingDrawerStatusChipRefreshRoots.contains(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldDeferUnlockedAppearanceForDrawer(View root) {
|
||||||
|
if (root == null
|
||||||
|
|| !root.isAttachedToWindow()
|
||||||
|
|| !unlockedIconRenderController.hasLayoutPlan(root)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||||
|
return activeScene != null
|
||||||
|
&& activeScene.isUnlocked()
|
||||||
|
&& notificationDrawerWasOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flushDeferredDrawerAppearanceRefreshes() {
|
||||||
|
if (pendingDrawerAppearanceRefreshRoots.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ArrayDeque<View> roots = new ArrayDeque<>(pendingDrawerAppearanceRefreshRoots);
|
||||||
|
pendingDrawerAppearanceRefreshRoots.clear();
|
||||||
|
while (!roots.isEmpty()) {
|
||||||
|
View root = roots.removeFirst();
|
||||||
|
if (root != null && root.isAttachedToWindow()) {
|
||||||
|
scheduleUnlockedAppearanceRefreshForRoot(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void flushDeferredDrawerStatusChipRefreshes() {
|
private void flushDeferredDrawerStatusChipRefreshes() {
|
||||||
if (pendingDrawerStatusChipRefreshRoots.isEmpty()) {
|
if (pendingDrawerStatusChipRefreshRoots.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@@ -1484,6 +1625,13 @@ final class StockLayoutCanvasController {
|
|||||||
root.invalidate();
|
root.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private View rootForShadeTarget(Object target) {
|
||||||
|
if (!(target instanceof View view)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return unlockedStatusBarRootFor(view);
|
||||||
|
}
|
||||||
|
|
||||||
private View trackedStatusChipSourceFor(View view) {
|
private View trackedStatusChipSourceFor(View view) {
|
||||||
ArrayDeque<View> staleViews = new ArrayDeque<>();
|
ArrayDeque<View> staleViews = new ArrayDeque<>();
|
||||||
View result = null;
|
View result = null;
|
||||||
@@ -1512,6 +1660,8 @@ final class StockLayoutCanvasController {
|
|||||||
View stale = staleViews.removeFirst();
|
View stale = staleViews.removeFirst();
|
||||||
confirmedAodPluginViews.remove(stale);
|
confirmedAodPluginViews.remove(stale);
|
||||||
aodPluginVisualAlphaByView.remove(stale);
|
aodPluginVisualAlphaByView.remove(stale);
|
||||||
|
aodVisualAlphaGeneration++;
|
||||||
|
aodBeforeDrawSignatureByRoot.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1739,6 +1889,10 @@ final class StockLayoutCanvasController {
|
|||||||
|| !isUnlockedStatusBarRoot(root)) {
|
|| !isUnlockedStatusBarRoot(root)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!highPriority && shouldDeferUnlockedAppearanceForDrawer(root)) {
|
||||||
|
pendingDrawerAppearanceRefreshRoots.add(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!pendingUnlockedAppearanceRefreshRoots.add(root) && !highPriority) {
|
if (!pendingUnlockedAppearanceRefreshRoots.add(root) && !highPriority) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1785,6 +1939,10 @@ final class StockLayoutCanvasController {
|
|||||||
if (root == null || !root.isAttachedToWindow()) {
|
if (root == null || !root.isAttachedToWindow()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (shouldDeferUnlockedAppearanceForDrawer(root)) {
|
||||||
|
pendingDrawerAppearanceRefreshRoots.add(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
SbtSettings settings = RuntimeSettingsCache.get(root.getContext());
|
||||||
if (!settings.clockEnabled) {
|
if (!settings.clockEnabled) {
|
||||||
return;
|
return;
|
||||||
@@ -1869,10 +2027,9 @@ final class StockLayoutCanvasController {
|
|||||||
runtimeContext.getModeStateRepository().setLayoutEnabled(layoutEnabled);
|
runtimeContext.getModeStateRepository().setLayoutEnabled(layoutEnabled);
|
||||||
if (!layoutEnabled) {
|
if (!layoutEnabled) {
|
||||||
updateActiveScene(root, context);
|
updateActiveScene(root, context);
|
||||||
statusIconModelTracker.updateActiveSlots(
|
SceneKey activeScene = runtimeContext.getModeStateRepository().getActiveScene();
|
||||||
false,
|
statusIconModelTracker.updateActiveSlots(false, activeScene);
|
||||||
runtimeContext.getModeStateRepository().getActiveScene());
|
applyLayoutOffRoot(root, settings, statusBarRoot, activeScene);
|
||||||
applyLayoutOffRoot(root, settings, statusBarRoot, runtimeContext.getModeStateRepository().getActiveScene());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
installPendingPluginClassLoaderHooks();
|
installPendingPluginClassLoaderHooks();
|
||||||
@@ -1948,6 +2105,10 @@ final class StockLayoutCanvasController {
|
|||||||
}
|
}
|
||||||
boolean shouldRender = !unlockedIconRenderController.hasLayoutPlan(root)
|
boolean shouldRender = !unlockedIconRenderController.hasLayoutPlan(root)
|
||||||
|| rootDirty;
|
|| rootDirty;
|
||||||
|
if (shouldRender && shouldDeferUnlockedRenderForDrawer(root)) {
|
||||||
|
pendingDrawerStatusChipRefreshRoots.add(root);
|
||||||
|
shouldRender = false;
|
||||||
|
}
|
||||||
if (shouldRender) {
|
if (shouldRender) {
|
||||||
markRootGeometryBeforeDraw(root);
|
markRootGeometryBeforeDraw(root);
|
||||||
renderResult = unlockedIconRenderController.renderUnlocked(
|
renderResult = unlockedIconRenderController.renderUnlocked(
|
||||||
@@ -2241,7 +2402,12 @@ final class StockLayoutCanvasController {
|
|||||||
if (view == null || Float.isNaN(alpha)) {
|
if (view == null || Float.isNaN(alpha)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
aodPluginVisualAlphaByView.put(view, clampAlpha(alpha));
|
float safeAlpha = clampAlpha(alpha);
|
||||||
|
Float previous = aodPluginVisualAlphaByView.put(view, safeAlpha);
|
||||||
|
if (previous == null || previous.floatValue() != safeAlpha) {
|
||||||
|
aodVisualAlphaGeneration++;
|
||||||
|
aodBeforeDrawSignatureByRoot.clear();
|
||||||
|
}
|
||||||
View root = view.getRootView();
|
View root = view.getRootView();
|
||||||
if (root != null && root.isAttachedToWindow()) {
|
if (root != null && root.isAttachedToWindow()) {
|
||||||
root.invalidate();
|
root.invalidate();
|
||||||
@@ -2255,6 +2421,8 @@ final class StockLayoutCanvasController {
|
|||||||
Object parent = current.getParent();
|
Object parent = current.getParent();
|
||||||
current = parent instanceof View parentView ? parentView : null;
|
current = parent instanceof View parentView ? parentView : null;
|
||||||
}
|
}
|
||||||
|
aodVisualAlphaGeneration++;
|
||||||
|
aodBeforeDrawSignatureByRoot.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private float currentAodVisualAlpha(View view) {
|
private float currentAodVisualAlpha(View view) {
|
||||||
@@ -3126,6 +3294,7 @@ final class StockLayoutCanvasController {
|
|||||||
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
pendingUnlockedAppearanceRefreshRoots.remove(root);
|
||||||
dirtyUnlockedLayoutRoots.remove(root);
|
dirtyUnlockedLayoutRoots.remove(root);
|
||||||
settingsIdentityByRoot.remove(root);
|
settingsIdentityByRoot.remove(root);
|
||||||
|
aodBeforeDrawSignatureByRoot.remove(root);
|
||||||
lockscreenCardsRowFilterSignatures.remove(root);
|
lockscreenCardsRowFilterSignatures.remove(root);
|
||||||
lockscreenCardsNotificationRenderGenerationByRoot.remove(root);
|
lockscreenCardsNotificationRenderGenerationByRoot.remove(root);
|
||||||
clockTimeBucketByRoot.remove(root);
|
clockTimeBucketByRoot.remove(root);
|
||||||
@@ -3693,16 +3862,26 @@ final class StockLayoutCanvasController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isKeyguardIconOnlyLayoutAncestor(view)) {
|
if (isKeyguardIconOnlyLayoutAncestor(view)) {
|
||||||
confirmedAodPluginViews.remove(view);
|
if (confirmedAodPluginViews.remove(view)) {
|
||||||
|
aodVisualAlphaGeneration++;
|
||||||
|
aodBeforeDrawSignatureByRoot.clear();
|
||||||
|
}
|
||||||
restoreView(view);
|
restoreView(view);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isLayoutEnabled(view.getContext())) {
|
if (!isLayoutEnabled(view.getContext())) {
|
||||||
confirmedAodPluginViews.remove(view);
|
if (confirmedAodPluginViews.remove(view)) {
|
||||||
|
aodVisualAlphaGeneration++;
|
||||||
|
aodBeforeDrawSignatureByRoot.clear();
|
||||||
|
}
|
||||||
restoreView(view);
|
restoreView(view);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
confirmedAodPluginViews.add(view);
|
boolean added = confirmedAodPluginViews.add(view);
|
||||||
|
if (added) {
|
||||||
|
aodVisualAlphaGeneration++;
|
||||||
|
aodBeforeDrawSignatureByRoot.clear();
|
||||||
|
}
|
||||||
rememberAodPluginVisualAlphaChain(view);
|
rememberAodPluginVisualAlphaChain(view);
|
||||||
hideView(view, keepConfirmedAodPluginViewLaidOut(view));
|
hideView(view, keepConfirmedAodPluginViewLaidOut(view));
|
||||||
}
|
}
|
||||||
@@ -3852,6 +4031,8 @@ final class StockLayoutCanvasController {
|
|||||||
trackedUnlockedStockSurfaces.clear();
|
trackedUnlockedStockSurfaces.clear();
|
||||||
confirmedAodPluginViews.clear();
|
confirmedAodPluginViews.clear();
|
||||||
aodPluginVisualAlphaByView.clear();
|
aodPluginVisualAlphaByView.clear();
|
||||||
|
aodBeforeDrawSignatureByRoot.clear();
|
||||||
|
aodVisualAlphaGeneration++;
|
||||||
knownAodNotificationContainers.clear();
|
knownAodNotificationContainers.clear();
|
||||||
knownAodNotificationIconOnlyAreas.clear();
|
knownAodNotificationIconOnlyAreas.clear();
|
||||||
latestAodIconImageViews = new ArrayList<>();
|
latestAodIconImageViews = new ArrayList<>();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package se.ajpanton.statusbartweak.runtime.render;
|
package se.ajpanton.statusbartweak.runtime.render;
|
||||||
|
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.style.RelativeSizeSpan;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -53,12 +55,16 @@ final class ClockLayout {
|
|||||||
TextView source,
|
TextView source,
|
||||||
ClockLayoutTextFactory.Model model,
|
ClockLayoutTextFactory.Model model,
|
||||||
LayoutPosition position,
|
LayoutPosition position,
|
||||||
int sourceBaselineY,
|
|
||||||
int rootWidth,
|
int rootWidth,
|
||||||
Bounds cutoutBounds,
|
Bounds cutoutBounds,
|
||||||
boolean middleAutoNearestSide,
|
boolean middleAutoNearestSide,
|
||||||
AnchorSide middleSide,
|
AnchorSide middleSide,
|
||||||
float textSizePx
|
float textSizePx,
|
||||||
|
int baseHeightSteps,
|
||||||
|
int autoGapSteps,
|
||||||
|
int statusbarHeight,
|
||||||
|
int visualBaselineY,
|
||||||
|
int verticalOffsetSteps
|
||||||
) {
|
) {
|
||||||
if (source == null || model == null || model.rows.isEmpty()) {
|
if (source == null || model == null || model.rows.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -68,7 +74,8 @@ final class ClockLayout {
|
|||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
ClockProxyRenderer.copyTextStyleIfChanged(source, probe);
|
ClockProxyRenderer.copyTextStyleIfChanged(source, probe);
|
||||||
applyTextSizeOverride(probe, textSizePx);
|
float resolvedTextSizePx = textSizeForTargetPlainRowHeight(probe, model, textSizePx);
|
||||||
|
applyTextSizeOverride(probe, resolvedTextSizePx);
|
||||||
ArrayList<MeasuredClockRow> measuredRows = new ArrayList<>();
|
ArrayList<MeasuredClockRow> measuredRows = new ArrayList<>();
|
||||||
for (int i = 0; i < model.rows.size(); i++) {
|
for (int i = 0; i < model.rows.size(); i++) {
|
||||||
ClockLayoutTextFactory.Row row = model.rows.get(i);
|
ClockLayoutTextFactory.Row row = model.rows.get(i);
|
||||||
@@ -84,14 +91,22 @@ final class ClockLayout {
|
|||||||
text.width,
|
text.width,
|
||||||
text.height,
|
text.height,
|
||||||
text.baseline,
|
text.baseline,
|
||||||
|
logicalRowHeightSteps(row.text, baseHeightSteps),
|
||||||
row.pipeCount > 0 ? pipePosition : 0,
|
row.pipeCount > 0 ? pipePosition : 0,
|
||||||
0,
|
0,
|
||||||
leftText,
|
leftText,
|
||||||
rightText,
|
rightText,
|
||||||
0,
|
0,
|
||||||
|
0,
|
||||||
i));
|
i));
|
||||||
}
|
}
|
||||||
measuredRows = positionRowsFromBaseline(measuredRows, sourceBaselineY, source);
|
measuredRows = positionRowsFromLogicalSteps(
|
||||||
|
measuredRows,
|
||||||
|
Math.max(1, baseHeightSteps),
|
||||||
|
Math.max(1, autoGapSteps),
|
||||||
|
Math.max(1, statusbarHeight),
|
||||||
|
visualBaselineY,
|
||||||
|
verticalOffsetSteps);
|
||||||
|
|
||||||
RowAlignment alignment = alignRows(measuredRows, position);
|
RowAlignment alignment = alignRows(measuredRows, position);
|
||||||
ArrayList<ClockRow> rows = alignment.rows;
|
ArrayList<ClockRow> rows = alignment.rows;
|
||||||
@@ -115,7 +130,9 @@ final class ClockLayout {
|
|||||||
row.height,
|
row.height,
|
||||||
0,
|
0,
|
||||||
row.rowIndex,
|
row.rowIndex,
|
||||||
row.segment));
|
row.segment,
|
||||||
|
row.logicalTopSteps,
|
||||||
|
row.logicalHeightSteps));
|
||||||
}
|
}
|
||||||
return new ClockLayout(
|
return new ClockLayout(
|
||||||
source,
|
source,
|
||||||
@@ -127,7 +144,7 @@ final class ClockLayout {
|
|||||||
false,
|
false,
|
||||||
alignment.width,
|
alignment.width,
|
||||||
bounds.height,
|
bounds.height,
|
||||||
effectiveTextSize(source, textSizePx));
|
effectiveTextSize(source, resolvedTextSizePx));
|
||||||
}
|
}
|
||||||
|
|
||||||
static ClockLayout simpleText(
|
static ClockLayout simpleText(
|
||||||
@@ -197,30 +214,50 @@ final class ClockLayout {
|
|||||||
return (extraHeight / 2) + measured.baseline;
|
return (extraHeight / 2) + measured.baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ArrayList<MeasuredClockRow> positionRowsFromBaseline(
|
private static ArrayList<MeasuredClockRow> positionRowsFromLogicalSteps(
|
||||||
ArrayList<MeasuredClockRow> measuredRows,
|
ArrayList<MeasuredClockRow> measuredRows,
|
||||||
int sourceBaselineY,
|
int baseHeightSteps,
|
||||||
TextView source
|
int autoGapSteps,
|
||||||
|
int statusbarHeight,
|
||||||
|
int visualBaselineY,
|
||||||
|
int verticalOffsetSteps
|
||||||
) {
|
) {
|
||||||
ArrayList<MeasuredClockRow> positionedRows = new ArrayList<>(measuredRows);
|
ArrayList<MeasuredClockRow> positionedRows = new ArrayList<>(measuredRows);
|
||||||
int baselineY = sourceBaselineY;
|
int nextBottomSteps = 0;
|
||||||
|
int nextBottomPx = 0;
|
||||||
for (int rowIndex = measuredRows.size() - 1; rowIndex >= 0; rowIndex--) {
|
for (int rowIndex = measuredRows.size() - 1; rowIndex >= 0; rowIndex--) {
|
||||||
MeasuredClockRow row = measuredRows.get(rowIndex);
|
MeasuredClockRow row = measuredRows.get(rowIndex);
|
||||||
int top = baselineY - row.baseline;
|
int topSteps;
|
||||||
|
int bottomPx;
|
||||||
|
if (rowIndex == measuredRows.size() - 1) {
|
||||||
|
topSteps = 100 - Math.max(1, row.logicalHeightSteps) - verticalOffsetSteps;
|
||||||
|
int heightPx = Math.max(1, stepsToPx(Math.max(1, row.logicalHeightSteps), statusbarHeight));
|
||||||
|
int baselineY = visualBaselineY - stepsToPx(verticalOffsetSteps, statusbarHeight);
|
||||||
|
bottomPx = baselineY - row.baseline + heightPx;
|
||||||
|
} else {
|
||||||
|
MeasuredClockRow nextRow = measuredRows.get(rowIndex + 1);
|
||||||
|
int gapSteps = resolveGapSteps(nextRow.source.gapBeforePx, autoGapSteps);
|
||||||
|
topSteps = nextBottomSteps - gapSteps - Math.max(1, row.logicalHeightSteps);
|
||||||
|
bottomPx = nextBottomPx - stepsToPx(gapSteps, statusbarHeight);
|
||||||
|
}
|
||||||
|
nextBottomSteps = topSteps + Math.max(1, row.logicalHeightSteps);
|
||||||
|
int heightPx = Math.max(1, stepsToPx(Math.max(1, row.logicalHeightSteps), statusbarHeight));
|
||||||
|
int topPx = bottomPx - heightPx;
|
||||||
|
nextBottomPx = bottomPx;
|
||||||
|
int baselineY = topPx + row.baseline;
|
||||||
positionedRows.set(rowIndex, new MeasuredClockRow(
|
positionedRows.set(rowIndex, new MeasuredClockRow(
|
||||||
row.source,
|
row.source,
|
||||||
row.width,
|
row.width,
|
||||||
row.height,
|
heightPx,
|
||||||
row.baseline,
|
row.baseline,
|
||||||
|
row.logicalHeightSteps,
|
||||||
row.pipePosition,
|
row.pipePosition,
|
||||||
baselineY,
|
baselineY,
|
||||||
row.leftSegment,
|
row.leftSegment,
|
||||||
row.rightSegment,
|
row.rightSegment,
|
||||||
top,
|
topPx,
|
||||||
|
topSteps,
|
||||||
row.rowIndex));
|
row.rowIndex));
|
||||||
if (rowIndex > 0) {
|
|
||||||
baselineY -= resolveGap(row.source.gapBeforePx, source);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return positionedRows;
|
return positionedRows;
|
||||||
}
|
}
|
||||||
@@ -278,6 +315,9 @@ final class ClockLayout {
|
|||||||
false,
|
false,
|
||||||
row.baselineY,
|
row.baselineY,
|
||||||
row.rowIndex,
|
row.rowIndex,
|
||||||
|
row.height,
|
||||||
|
row.logicalTopSteps,
|
||||||
|
row.logicalHeightSteps,
|
||||||
ClockRowSegment.LEFT);
|
ClockRowSegment.LEFT);
|
||||||
addSplitSegment(
|
addSplitSegment(
|
||||||
rightRows,
|
rightRows,
|
||||||
@@ -287,6 +327,9 @@ final class ClockLayout {
|
|||||||
true,
|
true,
|
||||||
row.baselineY,
|
row.baselineY,
|
||||||
row.rowIndex,
|
row.rowIndex,
|
||||||
|
row.height,
|
||||||
|
row.logicalTopSteps,
|
||||||
|
row.logicalHeightSteps,
|
||||||
ClockRowSegment.RIGHT);
|
ClockRowSegment.RIGHT);
|
||||||
} else {
|
} else {
|
||||||
int x = side == AnchorSide.LEFT
|
int x = side == AnchorSide.LEFT
|
||||||
@@ -301,7 +344,9 @@ final class ClockLayout {
|
|||||||
row.height,
|
row.height,
|
||||||
0,
|
0,
|
||||||
row.rowIndex,
|
row.rowIndex,
|
||||||
ClockRowSegment.FULL));
|
ClockRowSegment.FULL,
|
||||||
|
row.logicalTopSteps,
|
||||||
|
row.logicalHeightSteps));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClockLayout left = fixedLayoutFromAbsoluteRows(leftRows);
|
ClockLayout left = fixedLayoutFromAbsoluteRows(leftRows);
|
||||||
@@ -334,7 +379,9 @@ final class ClockLayout {
|
|||||||
row.height,
|
row.height,
|
||||||
0,
|
0,
|
||||||
row.rowIndex,
|
row.rowIndex,
|
||||||
row.segment));
|
row.segment,
|
||||||
|
row.logicalTopSteps,
|
||||||
|
row.logicalHeightSteps));
|
||||||
}
|
}
|
||||||
return new ClockLayout(
|
return new ClockLayout(
|
||||||
source,
|
source,
|
||||||
@@ -357,6 +404,9 @@ final class ClockLayout {
|
|||||||
boolean extendRight,
|
boolean extendRight,
|
||||||
int baselineY,
|
int baselineY,
|
||||||
int rowIndex,
|
int rowIndex,
|
||||||
|
int rowHeightPx,
|
||||||
|
int logicalTopSteps,
|
||||||
|
int logicalHeightSteps,
|
||||||
ClockRowSegment segment
|
ClockRowSegment segment
|
||||||
) {
|
) {
|
||||||
if (text == null || text.length() <= 0 || measuredText == null || measuredText.width <= 0) {
|
if (text == null || text.length() <= 0 || measuredText == null || measuredText.width <= 0) {
|
||||||
@@ -369,10 +419,12 @@ final class ClockLayout {
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
measuredText.width,
|
measuredText.width,
|
||||||
measuredText.height,
|
rowHeightPx,
|
||||||
0,
|
0,
|
||||||
rowIndex,
|
rowIndex,
|
||||||
segment));
|
segment,
|
||||||
|
logicalTopSteps,
|
||||||
|
logicalHeightSteps));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AnchorSide splitSide(
|
private static AnchorSide splitSide(
|
||||||
@@ -429,7 +481,9 @@ final class ClockLayout {
|
|||||||
row.height,
|
row.height,
|
||||||
0,
|
0,
|
||||||
row.rowIndex,
|
row.rowIndex,
|
||||||
ClockRowSegment.FULL));
|
ClockRowSegment.FULL,
|
||||||
|
row.logicalTopSteps,
|
||||||
|
row.logicalHeightSteps));
|
||||||
}
|
}
|
||||||
return new RowAlignment(rows, alignedWidth);
|
return new RowAlignment(rows, alignedWidth);
|
||||||
}
|
}
|
||||||
@@ -456,13 +510,6 @@ final class ClockLayout {
|
|||||||
Math.max(0, view.getBaseline()));
|
Math.max(0, view.getBaseline()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int resolveGap(int gapBeforePx, TextView source) {
|
|
||||||
if (gapBeforePx == ClockLayoutTextFactory.ROW_GAP_AUTO) {
|
|
||||||
return Math.max(1, source.getLineHeight());
|
|
||||||
}
|
|
||||||
return gapBeforePx;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width() {
|
int width() {
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
@@ -492,6 +539,21 @@ final class ClockLayout {
|
|||||||
return boxes;
|
return boxes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArrayList<Bounds> logicalCollisionBoxes() {
|
||||||
|
ArrayList<Bounds> boxes = new ArrayList<>();
|
||||||
|
if (rows == null) {
|
||||||
|
return boxes;
|
||||||
|
}
|
||||||
|
for (ClockRow row : rows) {
|
||||||
|
if (row.width > 0
|
||||||
|
&& row.logicalTopSteps != Integer.MIN_VALUE
|
||||||
|
&& row.logicalHeightSteps > 0) {
|
||||||
|
boxes.add(new Bounds(row.x, row.logicalTopSteps, row.width, row.logicalHeightSteps));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return boxes;
|
||||||
|
}
|
||||||
|
|
||||||
Bounds place(int left) {
|
Bounds place(int left) {
|
||||||
if (!fixedPlacement) {
|
if (!fixedPlacement) {
|
||||||
placedLeft = left;
|
placedLeft = left;
|
||||||
@@ -516,6 +578,97 @@ final class ClockLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static float textSizeForTargetPlainRowHeight(
|
||||||
|
TextView probe,
|
||||||
|
ClockLayoutTextFactory.Model model,
|
||||||
|
float targetHeightPx
|
||||||
|
) {
|
||||||
|
if (probe == null || targetHeightPx <= 0f) {
|
||||||
|
return targetHeightPx;
|
||||||
|
}
|
||||||
|
float textSize = Math.max(1f, targetHeightPx);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
applyTextSizeOverride(probe, textSize);
|
||||||
|
float scale = rowHeightScaleForTarget(probe, model, targetHeightPx);
|
||||||
|
if (scale <= 0f) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (Math.abs(1f - scale) < 0.01f) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
textSize = Math.max(1f, textSize * scale);
|
||||||
|
}
|
||||||
|
return textSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float rowHeightScaleForTarget(
|
||||||
|
TextView probe,
|
||||||
|
ClockLayoutTextFactory.Model model,
|
||||||
|
float targetPlainHeightPx
|
||||||
|
) {
|
||||||
|
if (model == null || model.rows == null || model.rows.isEmpty()) {
|
||||||
|
MeasuredText measured = measureText(probe, "0");
|
||||||
|
return measured.height > 0 ? targetPlainHeightPx / measured.height : 0f;
|
||||||
|
}
|
||||||
|
float scaleSum = 0f;
|
||||||
|
int scaleCount = 0;
|
||||||
|
for (ClockLayoutTextFactory.Row row : model.rows) {
|
||||||
|
CharSequence text = row != null ? row.text : null;
|
||||||
|
MeasuredText measured = measureText(probe, text != null && text.length() > 0 ? text : "0");
|
||||||
|
if (measured.height <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float targetHeight = targetPlainHeightPx * maxRelativeTextScale(text);
|
||||||
|
if (targetHeight <= 0f) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
scaleSum += targetHeight / measured.height;
|
||||||
|
scaleCount++;
|
||||||
|
}
|
||||||
|
return scaleCount > 0 ? scaleSum / scaleCount : 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int logicalRowHeightSteps(CharSequence text, int baseHeightSteps) {
|
||||||
|
return Math.max(1, Math.round(Math.max(1, baseHeightSteps) * maxRelativeTextScale(text)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float maxRelativeTextScale(CharSequence text) {
|
||||||
|
if (!(text instanceof Spanned spanned) || text.length() <= 0) {
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
RelativeSizeSpan[] spans = spanned.getSpans(0, spanned.length(), RelativeSizeSpan.class);
|
||||||
|
if (spans == null || spans.length == 0) {
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
float max = 0f;
|
||||||
|
for (int index = 0; index < spanned.length(); index++) {
|
||||||
|
float scale = 1f;
|
||||||
|
for (RelativeSizeSpan span : spans) {
|
||||||
|
int start = spanned.getSpanStart(span);
|
||||||
|
int end = spanned.getSpanEnd(span);
|
||||||
|
if (start <= index && index < end) {
|
||||||
|
scale *= span.getSizeChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
max = Math.max(max, scale);
|
||||||
|
}
|
||||||
|
return max > 0f ? max : 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int resolveGapSteps(int gapBeforeSteps, int autoGapSteps) {
|
||||||
|
if (gapBeforeSteps == ClockLayoutTextFactory.ROW_GAP_AUTO) {
|
||||||
|
return Math.max(1, autoGapSteps);
|
||||||
|
}
|
||||||
|
return gapBeforeSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int stepsToPx(int steps, int statusbarHeight) {
|
||||||
|
if (steps == 0 || statusbarHeight <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.round((float) steps * statusbarHeight / 100f);
|
||||||
|
}
|
||||||
|
|
||||||
private static float effectiveTextSize(TextView source, float textSizePx) {
|
private static float effectiveTextSize(TextView source, float textSizePx) {
|
||||||
if (textSizePx > 0f) {
|
if (textSizePx > 0f) {
|
||||||
return textSizePx;
|
return textSizePx;
|
||||||
@@ -528,11 +681,13 @@ final class ClockLayout {
|
|||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
int baseline,
|
int baseline,
|
||||||
|
int logicalHeightSteps,
|
||||||
int pipePosition,
|
int pipePosition,
|
||||||
int baselineY,
|
int baselineY,
|
||||||
MeasuredText leftSegment,
|
MeasuredText leftSegment,
|
||||||
MeasuredText rightSegment,
|
MeasuredText rightSegment,
|
||||||
int y,
|
int y,
|
||||||
|
int logicalTopSteps,
|
||||||
int rowIndex
|
int rowIndex
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ final class ClockRow {
|
|||||||
final int clipLeft;
|
final int clipLeft;
|
||||||
final int rowIndex;
|
final int rowIndex;
|
||||||
final ClockRowSegment segment;
|
final ClockRowSegment segment;
|
||||||
|
final int logicalTopSteps;
|
||||||
|
final int logicalHeightSteps;
|
||||||
|
|
||||||
ClockRow(
|
ClockRow(
|
||||||
CharSequence text,
|
CharSequence text,
|
||||||
@@ -69,6 +71,31 @@ final class ClockRow {
|
|||||||
int clipLeft,
|
int clipLeft,
|
||||||
int rowIndex,
|
int rowIndex,
|
||||||
ClockRowSegment segment
|
ClockRowSegment segment
|
||||||
|
) {
|
||||||
|
this(
|
||||||
|
text,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
clipLeft,
|
||||||
|
rowIndex,
|
||||||
|
segment,
|
||||||
|
Integer.MIN_VALUE,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClockRow(
|
||||||
|
CharSequence text,
|
||||||
|
int x,
|
||||||
|
int y,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int clipLeft,
|
||||||
|
int rowIndex,
|
||||||
|
ClockRowSegment segment,
|
||||||
|
int logicalTopSteps,
|
||||||
|
int logicalHeightSteps
|
||||||
) {
|
) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.x = x;
|
this.x = x;
|
||||||
@@ -78,6 +105,8 @@ final class ClockRow {
|
|||||||
this.clipLeft = clipLeft;
|
this.clipLeft = clipLeft;
|
||||||
this.rowIndex = rowIndex;
|
this.rowIndex = rowIndex;
|
||||||
this.segment = segment;
|
this.segment = segment;
|
||||||
|
this.logicalTopSteps = logicalTopSteps;
|
||||||
|
this.logicalHeightSteps = logicalHeightSteps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-73
@@ -5,8 +5,6 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
@@ -33,8 +31,6 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
private final TextVerticalOffsetScaler textOffsetScaler = new TextVerticalOffsetScaler();
|
private final TextVerticalOffsetScaler textOffsetScaler = new TextVerticalOffsetScaler();
|
||||||
private final UnlockedChipPlacementController chipPlacementController;
|
private final UnlockedChipPlacementController chipPlacementController;
|
||||||
private final UnlockedLayoutPlanner layoutPlanner;
|
private final UnlockedLayoutPlanner layoutPlanner;
|
||||||
private final WeakHashMap<View, Integer> clockBaselineDeltaByRoot = new WeakHashMap<>();
|
|
||||||
private final LinkedHashMap<Integer, Integer> clockSourceBaselineByRootKey = new LinkedHashMap<>();
|
|
||||||
private final WeakHashMap<View, LayoutInputSignature> lastLayoutSignatureByRoot = new WeakHashMap<>();
|
private final WeakHashMap<View, LayoutInputSignature> lastLayoutSignatureByRoot = new WeakHashMap<>();
|
||||||
private final WeakHashMap<View, CollectedIcons> lastCollectedIconsByRoot = new WeakHashMap<>();
|
private final WeakHashMap<View, CollectedIcons> lastCollectedIconsByRoot = new WeakHashMap<>();
|
||||||
private final WeakHashMap<View, LayoutPlan> lastLayoutPlanByRoot = new WeakHashMap<>();
|
private final WeakHashMap<View, LayoutPlan> lastLayoutPlanByRoot = new WeakHashMap<>();
|
||||||
@@ -112,6 +108,7 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
boolean chipSourcesChanged = previousSignature == null
|
boolean chipSourcesChanged = previousSignature == null
|
||||||
|| signature.chipSources != previousSignature.chipSources;
|
|| signature.chipSources != previousSignature.chipSources;
|
||||||
if (signature.equals(previousSignature) && !forceChipRefresh) {
|
if (signature.equals(previousSignature) && !forceChipRefresh) {
|
||||||
|
LayoutPlan cachedPlan = lastLayoutPlanByRoot.get(root);
|
||||||
return new RenderResult(
|
return new RenderResult(
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
@@ -267,9 +264,6 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
lastLayoutPlanByRoot.put(root, updatedPlan);
|
lastLayoutPlanByRoot.put(root, updatedPlan);
|
||||||
layoutPlan = updatedPlan;
|
layoutPlan = updatedPlan;
|
||||||
}
|
}
|
||||||
if (settings.layoutChipEnabledUnlocked && layoutPlan.chipPlacements != null) {
|
|
||||||
chipRenderer.renderPlacements(chipHost, layoutPlan.chipPlacements);
|
|
||||||
}
|
|
||||||
if (settings.layoutStatusEnabledUnlocked && layoutPlan.statusPlacements != null) {
|
if (settings.layoutStatusEnabledUnlocked && layoutPlan.statusPlacements != null) {
|
||||||
statusRenderer.renderPlacements(statusHost, layoutPlan.statusPlacements);
|
statusRenderer.renderPlacements(statusHost, layoutPlan.statusPlacements);
|
||||||
}
|
}
|
||||||
@@ -638,8 +632,6 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
notificationRenderer.clearAll();
|
notificationRenderer.clearAll();
|
||||||
chipRenderer.clearAll();
|
chipRenderer.clearAll();
|
||||||
sourceCollector.clear();
|
sourceCollector.clear();
|
||||||
clockBaselineDeltaByRoot.clear();
|
|
||||||
clockSourceBaselineByRootKey.clear();
|
|
||||||
forcedChipRefreshByRoot.clear();
|
forcedChipRefreshByRoot.clear();
|
||||||
lastLayoutSignatureByRoot.clear();
|
lastLayoutSignatureByRoot.clear();
|
||||||
lastCollectedIconsByRoot.clear();
|
lastCollectedIconsByRoot.clear();
|
||||||
@@ -656,7 +648,6 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
}
|
}
|
||||||
forcedChipRefreshByRoot.remove(root);
|
forcedChipRefreshByRoot.remove(root);
|
||||||
sourceCollector.clearRoot(root);
|
sourceCollector.clearRoot(root);
|
||||||
clockBaselineDeltaByRoot.remove(root);
|
|
||||||
lastLayoutSignatureByRoot.remove(root);
|
lastLayoutSignatureByRoot.remove(root);
|
||||||
lastCollectedIconsByRoot.remove(root);
|
lastCollectedIconsByRoot.remove(root);
|
||||||
lastLayoutPlanByRoot.remove(root);
|
lastLayoutPlanByRoot.remove(root);
|
||||||
@@ -727,26 +718,54 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
if (model == null || model.isEmpty()) {
|
if (model == null || model.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int baselineY = stableClockBaselineY(root, source, clockContainer);
|
|
||||||
int statusbarHeight = statusbarHeightForClock(root, sourceBounds, anchors, scene);
|
int statusbarHeight = statusbarHeightForClock(root, sourceBounds, anchors, scene);
|
||||||
float textSizePx = clockTextSizePx(statusbarHeight, settings, scene);
|
int textSizeSteps = clockTextSizeSteps(settings, scene);
|
||||||
int verticalOffsetPx = stepsToPx(settings.clockVerticalOffsetPx, statusbarHeight);
|
float textSizePx = clockTextSizePx(statusbarHeight, textSizeSteps);
|
||||||
return ClockLayout.measure(
|
int autoGapSteps = clockAutoGapSteps(source, statusbarHeight, textSizeSteps);
|
||||||
|
int visualBaselineY = clockVisualBaselineY(source, sourceBounds, statusbarHeight);
|
||||||
|
ClockLayout layout = ClockLayout.measure(
|
||||||
source,
|
source,
|
||||||
model,
|
model,
|
||||||
UnlockedLayoutPlanner.positionFor(settings.clockPosition),
|
UnlockedLayoutPlanner.positionFor(settings.clockPosition),
|
||||||
baselineY - verticalOffsetPx,
|
|
||||||
root.getWidth(),
|
root.getWidth(),
|
||||||
cutoutBounds,
|
cutoutBounds,
|
||||||
settings.clockMiddleAutoNearestSide,
|
settings.clockMiddleAutoNearestSide,
|
||||||
UnlockedLayoutPlanner.sideFor(settings.clockMiddleCutoutSide),
|
UnlockedLayoutPlanner.sideFor(settings.clockMiddleCutoutSide),
|
||||||
textSizePx);
|
textSizePx,
|
||||||
|
textSizeSteps,
|
||||||
|
autoGapSteps,
|
||||||
|
statusbarHeight,
|
||||||
|
visualBaselineY,
|
||||||
|
settings.clockVerticalOffsetPx);
|
||||||
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float clockTextSizePx(int statusbarHeight, SbtSettings settings, SceneKey scene) {
|
private int clockAutoGapSteps(TextView source, int statusbarHeight, int fallbackSteps) {
|
||||||
int steps = scene != null && scene.isLockscreen()
|
int lineHeight = source != null ? source.getLineHeight() : 0;
|
||||||
|
if (lineHeight <= 0 || statusbarHeight <= 0) {
|
||||||
|
return Math.max(1, fallbackSteps);
|
||||||
|
}
|
||||||
|
return Math.max(1, Math.round(lineHeight * 100f / statusbarHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int clockVisualBaselineY(TextView source, Bounds sourceBounds, int fallbackBaselineY) {
|
||||||
|
if (source == null || sourceBounds == null) {
|
||||||
|
return Math.max(1, fallbackBaselineY);
|
||||||
|
}
|
||||||
|
int baseline = source.getBaseline();
|
||||||
|
if (baseline <= 0) {
|
||||||
|
return Math.max(1, sourceBounds.top + sourceBounds.height);
|
||||||
|
}
|
||||||
|
return Math.max(1, sourceBounds.top + baseline);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int clockTextSizeSteps(SbtSettings settings, SceneKey scene) {
|
||||||
|
return scene != null && scene.isLockscreen()
|
||||||
? settings.clockLockTextSizeSteps
|
? settings.clockLockTextSizeSteps
|
||||||
: settings.clockTextSizeSteps;
|
: settings.clockTextSizeSteps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float clockTextSizePx(int statusbarHeight, int steps) {
|
||||||
return Math.max(1f, Math.max(1, statusbarHeight) * Math.max(1, steps) / 100f);
|
return Math.max(1f, Math.max(1, statusbarHeight) * Math.max(1, steps) / 100f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,13 +789,6 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
return Math.max(1, root != null ? root.getHeight() : 1);
|
return Math.max(1, root != null ? root.getHeight() : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int stepsToPx(int steps, int statusbarHeight) {
|
|
||||||
if (steps == 0 || statusbarHeight <= 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return Math.round((float) steps * statusbarHeight / 100f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer clockGeometrySignature(
|
private Integer clockGeometrySignature(
|
||||||
View root,
|
View root,
|
||||||
ClockLayout clockLayout,
|
ClockLayout clockLayout,
|
||||||
@@ -906,7 +918,9 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
row.height,
|
row.height,
|
||||||
row.clipLeft,
|
row.clipLeft,
|
||||||
row.rowIndex,
|
row.rowIndex,
|
||||||
row.segment));
|
row.segment,
|
||||||
|
row.logicalTopSteps,
|
||||||
|
row.logicalHeightSteps));
|
||||||
}
|
}
|
||||||
return new ClockPlacement(
|
return new ClockPlacement(
|
||||||
source,
|
source,
|
||||||
@@ -967,7 +981,9 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
previousRow.height,
|
previousRow.height,
|
||||||
previousRow.clipLeft,
|
previousRow.clipLeft,
|
||||||
previousRow.rowIndex,
|
previousRow.rowIndex,
|
||||||
previousRow.segment));
|
previousRow.segment,
|
||||||
|
previousRow.logicalTopSteps,
|
||||||
|
previousRow.logicalHeightSteps));
|
||||||
}
|
}
|
||||||
return new ClockPlacement(
|
return new ClockPlacement(
|
||||||
previous.source,
|
previous.source,
|
||||||
@@ -1023,52 +1039,6 @@ public final class UnlockedIconSnapshotRenderController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int stableClockBaselineY(View root, TextView source, View clockContainer) {
|
|
||||||
Bounds sourceBounds = ViewGeometry.boundsRelativeTo(root, source);
|
|
||||||
int sourceBaseline = source.getBaseline() > 0
|
|
||||||
? source.getBaseline()
|
|
||||||
: (sourceBounds != null ? Math.max(0, (sourceBounds.height * 3) / 4) : 0);
|
|
||||||
sourceBaseline = stableClockSourceBaseline(root, source, sourceBaseline);
|
|
||||||
Bounds containerBounds = ViewGeometry.boundsRelativeTo(root, clockContainer);
|
|
||||||
if (containerBounds != null
|
|
||||||
&& containerBounds.width > 0
|
|
||||||
&& containerBounds.height > 0
|
|
||||||
&& sourceBounds != null
|
|
||||||
&& sourceBounds.height > 0) {
|
|
||||||
int delta = sourceBounds.top + sourceBaseline - containerBounds.top;
|
|
||||||
Integer previousDelta = clockBaselineDeltaByRoot.get(root);
|
|
||||||
int tolerance = Math.max(4, containerBounds.height / 10);
|
|
||||||
if (previousDelta != null && Math.abs(delta - previousDelta) > tolerance) {
|
|
||||||
return containerBounds.top + previousDelta;
|
|
||||||
}
|
|
||||||
clockBaselineDeltaByRoot.put(root, delta);
|
|
||||||
return containerBounds.top + delta;
|
|
||||||
}
|
|
||||||
return sourceBounds != null ? sourceBounds.top + sourceBaseline : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int stableClockSourceBaseline(View root, TextView source, int sourceBaseline) {
|
|
||||||
if (root == null || source == null || root.getWidth() <= 0 || sourceBaseline <= 0) {
|
|
||||||
return sourceBaseline;
|
|
||||||
}
|
|
||||||
int key = 31 * root.getWidth() + Math.round(source.getTextSize());
|
|
||||||
Integer previous = clockSourceBaselineByRootKey.get(key);
|
|
||||||
int stable = previous == null ? sourceBaseline : Math.min(previous, sourceBaseline);
|
|
||||||
clockSourceBaselineByRootKey.put(key, stable);
|
|
||||||
while (clockSourceBaselineByRootKey.size() > 8) {
|
|
||||||
Integer eldest = null;
|
|
||||||
for (Map.Entry<Integer, Integer> entry : clockSourceBaselineByRootKey.entrySet()) {
|
|
||||||
eldest = entry.getKey();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (eldest == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
clockSourceBaselineByRootKey.remove(eldest);
|
|
||||||
}
|
|
||||||
return stable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public record RenderResult(
|
public record RenderResult(
|
||||||
boolean layoutChanged,
|
boolean layoutChanged,
|
||||||
boolean stockSourcesChanged,
|
boolean stockSourcesChanged,
|
||||||
|
|||||||
+24
-1
@@ -378,6 +378,10 @@ final class UnlockedLayoutBand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ArrayList<Bounds> solverCollisionBoxes() {
|
ArrayList<Bounds> solverCollisionBoxes() {
|
||||||
|
ArrayList<Bounds> logicalBoxes = logicalClockCollisionBoxes();
|
||||||
|
if (!logicalBoxes.isEmpty()) {
|
||||||
|
return logicalBoxes;
|
||||||
|
}
|
||||||
ArrayList<Bounds> boxes = collisionBoxes();
|
ArrayList<Bounds> boxes = collisionBoxes();
|
||||||
if (solverVerticalScaleHeight > 0 && !boxes.isEmpty()) {
|
if (solverVerticalScaleHeight > 0 && !boxes.isEmpty()) {
|
||||||
ArrayList<Bounds> solverBoxes = new ArrayList<>();
|
ArrayList<Bounds> solverBoxes = new ArrayList<>();
|
||||||
@@ -408,6 +412,23 @@ final class UnlockedLayoutBand {
|
|||||||
return solverBoxes;
|
return solverBoxes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ArrayList<Bounds> logicalClockCollisionBoxes() {
|
||||||
|
ArrayList<Bounds> boxes = new ArrayList<>();
|
||||||
|
if (!isTextKind() || clockLayout == null) {
|
||||||
|
return boxes;
|
||||||
|
}
|
||||||
|
int clipLeft = clipStartPx;
|
||||||
|
int clipRight = naturalWidth() - clipEndPx;
|
||||||
|
for (Bounds box : clockLayout.logicalCollisionBoxes()) {
|
||||||
|
int left = Math.max(box.left, clipLeft);
|
||||||
|
int right = Math.min(box.left + box.width, clipRight);
|
||||||
|
if (right >= left) {
|
||||||
|
boxes.add(new Bounds(left - clipStartPx, box.top, right - left, box.height));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return boxes;
|
||||||
|
}
|
||||||
|
|
||||||
private int pixelToStepFloor(int px, int sourceHeight) {
|
private int pixelToStepFloor(int px, int sourceHeight) {
|
||||||
if (sourceHeight <= 0) {
|
if (sourceHeight <= 0) {
|
||||||
return px;
|
return px;
|
||||||
@@ -733,7 +754,9 @@ final class UnlockedLayoutBand {
|
|||||||
row.height,
|
row.height,
|
||||||
row.clipLeft + left - rowLeft,
|
row.clipLeft + left - rowLeft,
|
||||||
row.rowIndex,
|
row.rowIndex,
|
||||||
row.segment));
|
row.segment,
|
||||||
|
row.logicalTopSteps,
|
||||||
|
row.logicalHeightSteps));
|
||||||
}
|
}
|
||||||
return new ClockPlacement(
|
return new ClockPlacement(
|
||||||
placement.source,
|
placement.source,
|
||||||
|
|||||||
+6
-25
@@ -21,7 +21,6 @@ import se.ajpanton.statusbartweak.runtime.layoutsolver.PackedStatusBarLayoutSolv
|
|||||||
import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle;
|
import se.ajpanton.statusbartweak.runtime.mode.NotificationDisplayStyle;
|
||||||
import se.ajpanton.statusbartweak.runtime.mode.SceneKey;
|
import se.ajpanton.statusbartweak.runtime.mode.SceneKey;
|
||||||
import se.ajpanton.statusbartweak.runtime.notifications.NotificationUnreadTracker;
|
import se.ajpanton.statusbartweak.runtime.notifications.NotificationUnreadTracker;
|
||||||
import se.ajpanton.statusbartweak.shell.settings.SbtDefaults;
|
|
||||||
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
|
import se.ajpanton.statusbartweak.shell.settings.SbtSettings;
|
||||||
|
|
||||||
final class UnlockedLayoutPlanner {
|
final class UnlockedLayoutPlanner {
|
||||||
@@ -91,15 +90,11 @@ final class UnlockedLayoutPlanner {
|
|||||||
LayoutEdges edges = layoutEdges(root, settings, anchors, scene, aodStatusAnchorBounds);
|
LayoutEdges edges = layoutEdges(root, settings, anchors, scene, aodStatusAnchorBounds);
|
||||||
int statusbarHeight = statusbarHeight(root, anchors, scene, aodStatusContentBounds);
|
int statusbarHeight = statusbarHeight(root, anchors, scene, aodStatusContentBounds);
|
||||||
if (clockEnabledForScene(settings, scene) && clockLayout != null && clockLayout.width() > 0) {
|
if (clockEnabledForScene(settings, scene) && clockLayout != null && clockLayout.width() > 0) {
|
||||||
int clockHeightSteps = clockTextSizeSteps(settings, scene);
|
|
||||||
bands.add(UnlockedLayoutBand.clock(
|
bands.add(UnlockedLayoutBand.clock(
|
||||||
clockHost,
|
clockHost,
|
||||||
clockLayout,
|
clockLayout,
|
||||||
positionFor(settings.clockPosition),
|
positionFor(settings.clockPosition),
|
||||||
sideFor(settings.clockMiddleCutoutSide))
|
sideFor(settings.clockMiddleCutoutSide)));
|
||||||
.withSolverCollision(
|
|
||||||
logicalRowTop(clockHeightSteps, settings.clockVerticalOffsetPx),
|
|
||||||
clockHeightSteps));
|
|
||||||
}
|
}
|
||||||
ClockLayout carrierLayout = carrierLayout(
|
ClockLayout carrierLayout = carrierLayout(
|
||||||
root,
|
root,
|
||||||
@@ -1130,15 +1125,6 @@ final class UnlockedLayoutPlanner {
|
|||||||
return Math.max(1, height);
|
return Math.max(1, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int clockTextSizeSteps(SbtSettings settings, SceneKey scene) {
|
|
||||||
if (settings == null) {
|
|
||||||
return SbtDefaults.CLOCK_TEXT_SIZE_STEPS_DEFAULT;
|
|
||||||
}
|
|
||||||
return scene != null && scene.isLockscreen()
|
|
||||||
? settings.clockLockTextSizeSteps
|
|
||||||
: settings.clockTextSizeSteps;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int statusIconHeightSteps(SbtSettings settings, SceneKey scene) {
|
private int statusIconHeightSteps(SbtSettings settings, SceneKey scene) {
|
||||||
if (scene != null && scene.isLockscreen()) {
|
if (scene != null && scene.isLockscreen()) {
|
||||||
return settings.layoutStatusLockIconHeightSteps;
|
return settings.layoutStatusLockIconHeightSteps;
|
||||||
@@ -1311,23 +1297,16 @@ final class UnlockedLayoutPlanner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bands.remove(index);
|
bands.remove(index);
|
||||||
int clockHeightSteps = clockTextSizeSteps(settings, scene);
|
|
||||||
bands.add(index, UnlockedLayoutBand.fixedClock(
|
bands.add(index, UnlockedLayoutBand.fixedClock(
|
||||||
clockBand.host,
|
clockBand.host,
|
||||||
splitLayouts.get(1),
|
splitLayouts.get(1),
|
||||||
SplitRegion.RIGHT,
|
SplitRegion.RIGHT,
|
||||||
clockBand.middleSide)
|
clockBand.middleSide));
|
||||||
.withSolverCollision(
|
|
||||||
logicalRowTop(clockHeightSteps, settings.clockVerticalOffsetPx),
|
|
||||||
clockHeightSteps));
|
|
||||||
bands.add(index, UnlockedLayoutBand.fixedClock(
|
bands.add(index, UnlockedLayoutBand.fixedClock(
|
||||||
clockBand.host,
|
clockBand.host,
|
||||||
splitLayouts.get(0),
|
splitLayouts.get(0),
|
||||||
SplitRegion.LEFT,
|
SplitRegion.LEFT,
|
||||||
clockBand.middleSide)
|
clockBand.middleSide));
|
||||||
.withSolverCollision(
|
|
||||||
logicalRowTop(clockHeightSteps, settings.clockVerticalOffsetPx),
|
|
||||||
clockHeightSteps));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<UnlockedLayoutBand> bandsForPosition(
|
private ArrayList<UnlockedLayoutBand> bandsForPosition(
|
||||||
@@ -1662,7 +1641,9 @@ final class UnlockedLayoutPlanner {
|
|||||||
row.height,
|
row.height,
|
||||||
row.clipLeft,
|
row.clipLeft,
|
||||||
row.rowIndex,
|
row.rowIndex,
|
||||||
row.segment));
|
row.segment,
|
||||||
|
row.logicalTopSteps,
|
||||||
|
row.logicalHeightSteps));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ClockPlacement(
|
return new ClockPlacement(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package se.ajpanton.statusbartweak.shell.debug;
|
package se.ajpanton.statusbartweak.shell.debug;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
@@ -19,7 +18,6 @@ import android.graphics.drawable.Icon;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.service.notification.StatusBarNotification;
|
import android.service.notification.StatusBarNotification;
|
||||||
|
|
||||||
@@ -54,7 +52,6 @@ public final class DebugNotifications {
|
|||||||
private static final Handler CYCLE_HANDLER = new Handler(Looper.getMainLooper());
|
private static final Handler CYCLE_HANDLER = new Handler(Looper.getMainLooper());
|
||||||
private static Runnable recoveryRunnable;
|
private static Runnable recoveryRunnable;
|
||||||
private static Runnable cycleRunnable;
|
private static Runnable cycleRunnable;
|
||||||
private static PowerManager.WakeLock cycleWakeLock;
|
|
||||||
private static boolean recoveryInProgress;
|
private static boolean recoveryInProgress;
|
||||||
|
|
||||||
private DebugNotifications() {
|
private DebugNotifications() {
|
||||||
@@ -361,7 +358,6 @@ public final class DebugNotifications {
|
|||||||
if (cycleRunnable != null) {
|
if (cycleRunnable != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
acquireCycleWakeLock(appContext);
|
|
||||||
cycleRunnable = new Runnable() {
|
cycleRunnable = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -380,7 +376,6 @@ public final class DebugNotifications {
|
|||||||
CYCLE_HANDLER.removeCallbacks(cycleRunnable);
|
CYCLE_HANDLER.removeCallbacks(cycleRunnable);
|
||||||
cycleRunnable = null;
|
cycleRunnable = null;
|
||||||
}
|
}
|
||||||
releaseCycleWakeLock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean advanceCycleAndApply(Context appContext) {
|
private static boolean advanceCycleAndApply(Context appContext) {
|
||||||
@@ -418,25 +413,6 @@ public final class DebugNotifications {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("WakelockTimeout")
|
|
||||||
private static void acquireCycleWakeLock(Context context) {
|
|
||||||
if (cycleWakeLock == null) {
|
|
||||||
PowerManager pm = context.getSystemService(PowerManager.class);
|
|
||||||
cycleWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
|
||||||
"StatusBarTweak:DebugCycle");
|
|
||||||
cycleWakeLock.setReferenceCounted(false);
|
|
||||||
}
|
|
||||||
if (!cycleWakeLock.isHeld()) {
|
|
||||||
cycleWakeLock.acquire();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void releaseCycleWakeLock() {
|
|
||||||
if (cycleWakeLock != null && cycleWakeLock.isHeld()) {
|
|
||||||
cycleWakeLock.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int dpToPx(Context context, int dp) {
|
private static int dpToPx(Context context, int dp) {
|
||||||
return (int) (dp * context.getResources().getDisplayMetrics().density + 0.5f);
|
return (int) (dp * context.getResources().getDisplayMetrics().density + 0.5f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public final class SbtDefaults {
|
|||||||
public static final int CLOCK_VERTICAL_OFFSET_PX_DEFAULT = 0;
|
public static final int CLOCK_VERTICAL_OFFSET_PX_DEFAULT = 0;
|
||||||
public static final int CLOCK_VERTICAL_OFFSET_PX_MIN = -99;
|
public static final int CLOCK_VERTICAL_OFFSET_PX_MIN = -99;
|
||||||
public static final int CLOCK_VERTICAL_OFFSET_PX_MAX = 99;
|
public static final int CLOCK_VERTICAL_OFFSET_PX_MAX = 99;
|
||||||
public static final int CLOCK_TEXT_SIZE_STEPS_DEFAULT = 40;
|
public static final int CLOCK_TEXT_SIZE_STEPS_DEFAULT = 44;
|
||||||
public static final boolean CLOCK_SHOW_SECONDS_DEFAULT = false;
|
public static final boolean CLOCK_SHOW_SECONDS_DEFAULT = false;
|
||||||
public static final boolean CLOCK_SHOW_DATE_PREFIX_DEFAULT = false;
|
public static final boolean CLOCK_SHOW_DATE_PREFIX_DEFAULT = false;
|
||||||
public static final boolean CLOCK_CUSTOM_FORMAT_ENABLED_DEFAULT = false;
|
public static final boolean CLOCK_CUSTOM_FORMAT_ENABLED_DEFAULT = false;
|
||||||
|
|||||||
@@ -244,7 +244,7 @@
|
|||||||
<string name="drawer_date_custom_format_enabled">Enable custom date/time pattern</string>
|
<string name="drawer_date_custom_format_enabled">Enable custom date/time pattern</string>
|
||||||
<string name="clock_custom_format_hint">Examples: HH:mm, EEE d MMM HH:mm:ss, {color(#batterybar 1.2 * ; invRGB 0.5 *RGB) big @sans-serif-condensed}HH:mm</string>
|
<string name="clock_custom_format_hint">Examples: HH:mm, EEE d MMM HH:mm:ss, {color(#batterybar 1.2 * ; invRGB 0.5 *RGB) big @sans-serif-condensed}HH:mm</string>
|
||||||
<string name="clock_custom_format_help_common">Date/time patterns:\n\u2022 Uses Java SimpleDateFormat-style pattern letters\n\u2022 Common symbols: yyyy yy MMMM MMM MM EEEE EEE dd d HH hh mm ss a\n\nTags:\n\u2022 Supported tags: <i>, <u>, <small>, <big>, <font color=\'...\'>, <font face=\'...\'>\n\u2022 <b> is not supported\n\u2022 Custom font tags: <sans>, <serif>, <mono>, <condensed>\n\u2022 {/font} closes any open <font ...> tag\n\u2022 Opening a new colour or font face automatically closes the previous one</string>
|
<string name="clock_custom_format_help_common">Date/time patterns:\n\u2022 Uses Java SimpleDateFormat-style pattern letters\n\u2022 Common symbols: yyyy yy MMMM MMM MM EEEE EEE dd d HH hh mm ss a\n\nTags:\n\u2022 Supported tags: <i>, <u>, <small>, <big>, <font color=\'...\'>, <font face=\'...\'>\n\u2022 <b> is not supported\n\u2022 Custom font tags: <sans>, <serif>, <mono>, <condensed>\n\u2022 {/font} closes any open <font ...> tag\n\u2022 Opening a new colour or font face automatically closes the previous one</string>
|
||||||
<string name="clock_custom_format_help_styling">Shorthand blocks:\n\u2022 {#FA0 big @sans-serif-condensed} is shorthand for opening multiple tags in order\n\u2022 {#FA0} is shorthand for {color(#FA0)}\n\u2022 In shorthand: / closes a tag, color(...) sets font colour, @ sets font face\n\u2022 Each whitespace-separated token inside {} becomes its own tag, except color(...) which may contain spaces\n\nLine breaks:\n\u2022 Use \\n, {\\n} or {\\n15}; the number is the line-height delta in px\n\u2022 Omit the number to use the default row spacing\n\u2022 { /\\n } style line breaks are written without spaces: {/\\n} or {/\\n15}\n\u2022 Normal line breaks propagate open tags onto the next row\n\u2022 A / line break ends all currently open tags before the next row starts\n\nPipes and grouping:\n\u2022 Maximum two pipes per row; more than two is a syntax error for that row only\n\u2022 Pipes inside single quotes or {} do not split the row\n\u2022 One pipe splits into left and right segments\n\u2022 Two pipes remove the middle section, then split using the outer pipes\n\u2022 Tags are logically continued across pipe splits, even if the middle section is ignored\n\u2022 Rows without pipes are independent; rows with pipes share one common pipe group\n\u2022 A syntax-error row becomes the text Syntax error, with no whole-clock fallback\n\nColours:\n\u2022 Supported operands: #RGB, #ARGB, #RRGGBB, #AARRGGBB, #batterybar, [R,G,B], [A,R,G,B], and numbers\n\u2022 Colour expressions use reverse Polish notation: operands first, operator last\n\u2022 Use ; to separate bright/dark variants; the dark side starts with the raw result from the bright side on the stack\n\u2022 Operators: + - * / ^ inv min max sqrt clip dup\n\u2022 Operators can be channel-filtered, for example +A, *RGB, invRGB, maxRGB, clipA\n\u2022 Final values are clipped to 0..255; a final number becomes opaque grayscale</string>
|
<string name="clock_custom_format_help_styling">Shorthand blocks:\n\u2022 {#FA0 big @sans-serif-condensed} is shorthand for opening multiple tags in order\n\u2022 {#FA0} is shorthand for {color(#FA0)}\n\u2022 In shorthand: / closes a tag, color(...) sets font colour, @ sets font face\n\u2022 Each whitespace-separated token inside {} becomes its own tag, except color(...) which may contain spaces\n\nLine breaks:\n\u2022 Use \\n, {\\n} or {\\n15}; the number is the line-height delta in statusbar units\n\u2022 Omit the number to use the default row spacing\n\u2022 { /\\n } style line breaks are written without spaces: {/\\n} or {/\\n15}\n\u2022 Normal line breaks propagate open tags onto the next row\n\u2022 A / line break ends all currently open tags before the next row starts\n\nPipes and grouping:\n\u2022 Maximum two pipes per row; more than two is a syntax error for that row only\n\u2022 Pipes inside single quotes or {} do not split the row\n\u2022 One pipe splits into left and right segments\n\u2022 Two pipes remove the middle section, then split using the outer pipes\n\u2022 Tags are logically continued across pipe splits, even if the middle section is ignored\n\u2022 Rows without pipes are independent; rows with pipes share one common pipe group\n\u2022 A syntax-error row becomes the text Syntax error, with no whole-clock fallback\n\nColours:\n\u2022 Supported operands: #RGB, #ARGB, #RRGGBB, #AARRGGBB, #batterybar, [R,G,B], [A,R,G,B], and numbers\n\u2022 Colour expressions use reverse Polish notation: operands first, operator last\n\u2022 Use ; to separate bright/dark variants; the dark side starts with the raw result from the bright side on the stack\n\u2022 Operators: + - * / ^ inv min max sqrt clip dup\n\u2022 Operators can be channel-filtered, for example +A, *RGB, invRGB, maxRGB, clipA\n\u2022 Final values are clipped to 0..255; a final number becomes opaque grayscale</string>
|
||||||
<string name="clock_custom_format_help_examples">Examples:\n\u2022 <small>EEE d MMM</small> HH:mm\n\u2022 HH<small>:mm</small>\n\u2022 <font color=\'#FA0\'>ss</font>\n\u2022 {color(#FA0 ; invRGB 0.8 *RGB)}ss\n\u2022 {color(#batterybar 1.2 * ; invRGB 0.5 *RGB) big u @sans-serif-condensed}HH:mm\n\u2022 {color([255, 255, 170, 0] ; invRGB)}HH:mm\n\u2022 HH:mm{\\n12 small}EEE d MMM\n\u2022 HH|mm aligns the pipe position across every piped row\n\u2022 left||right removes the middle section before split alignment\n\u2022 {/font}{/\\n big}HH:mm starts a new row without carrying the previous font tags forward</string>
|
<string name="clock_custom_format_help_examples">Examples:\n\u2022 <small>EEE d MMM</small> HH:mm\n\u2022 HH<small>:mm</small>\n\u2022 <font color=\'#FA0\'>ss</font>\n\u2022 {color(#FA0 ; invRGB 0.8 *RGB)}ss\n\u2022 {color(#batterybar 1.2 * ; invRGB 0.5 *RGB) big u @sans-serif-condensed}HH:mm\n\u2022 {color([255, 255, 170, 0] ; invRGB)}HH:mm\n\u2022 HH:mm{\\n12 small}EEE d MMM\n\u2022 HH|mm aligns the pipe position across every piped row\n\u2022 left||right removes the middle section before split alignment\n\u2022 {/font}{/\\n big}HH:mm starts a new row without carrying the previous font tags forward</string>
|
||||||
<string name="clock_font_picker_button">Browse font families</string>
|
<string name="clock_font_picker_button">Browse font families</string>
|
||||||
<string name="clock_custom_format_help_show">▾ Show pattern instructions</string>
|
<string name="clock_custom_format_help_show">▾ Show pattern instructions</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user