Merge "Create HintSessionWrapper in RT right after CanvasContext" into tm-qpr-dev
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 7393bcd..21ed1eb 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -37,7 +37,9 @@
import android.os.HandlerExecutor;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.text.TextUtils;
import android.util.Log;
@@ -91,6 +93,14 @@
public class AlarmManager {
private static final String TAG = "AlarmManager";
+ /**
+ * Prefix used by {{@link #makeTag(long, WorkSource)}} to make a tag on behalf of the caller
+ * when the {@link #set(int, long, long, long, OnAlarmListener, Handler, WorkSource)} API is
+ * used. This prefix is a unique sequence of characters to differentiate with other tags that
+ * apps may provide to other APIs that accept a listener callback.
+ */
+ private static final String GENERATED_TAG_PREFIX = "$android.alarm.generated";
+
/** @hide */
@IntDef(prefix = { "RTC", "ELAPSED" }, value = {
RTC_WAKEUP,
@@ -861,6 +871,24 @@
}
/**
+ * This is only used to make an identifying tag for the deprecated
+ * {@link #set(int, long, long, long, OnAlarmListener, Handler, WorkSource)} API which doesn't
+ * accept a tag. For all other APIs, the tag provided by the app is used, even if it is
+ * {@code null}.
+ */
+ private static String makeTag(long triggerMillis, WorkSource ws) {
+ final StringBuilder tagBuilder = new StringBuilder(GENERATED_TAG_PREFIX);
+
+ tagBuilder.append(":");
+ final int attributionUid =
+ (ws == null || ws.isEmpty()) ? Process.myUid() : ws.getAttributionUid();
+ tagBuilder.append(UserHandle.formatUid(attributionUid));
+ tagBuilder.append(":");
+ tagBuilder.append(triggerMillis);
+ return tagBuilder.toString();
+ }
+
+ /**
* Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
* Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
* <p>
@@ -875,8 +903,8 @@
public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
long intervalMillis, OnAlarmListener listener, Handler targetHandler,
WorkSource workSource) {
- setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
- targetHandler, workSource, null);
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener,
+ makeTag(triggerAtMillis, workSource), targetHandler, workSource, null);
}
/**
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 37ce0d2..f1a3931 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -4739,8 +4739,14 @@
}
final ArraySet<Pair<String, Integer>> triggerPackages =
new ArraySet<>();
+ final SparseIntArray countsPerUid = new SparseIntArray();
+ final SparseIntArray wakeupCountsPerUid = new SparseIntArray();
for (int i = 0; i < triggerList.size(); i++) {
final Alarm a = triggerList.get(i);
+ increment(countsPerUid, a.uid);
+ if (a.wakeup) {
+ increment(wakeupCountsPerUid, a.uid);
+ }
if (mConstants.USE_TARE_POLICY) {
if (!isExemptFromTare(a)) {
triggerPackages.add(Pair.create(
@@ -4761,7 +4767,8 @@
}
rescheduleKernelAlarmsLocked();
updateNextAlarmClockLocked();
- MetricsHelper.pushAlarmBatchDelivered(triggerList.size(), wakeUps);
+ logAlarmBatchDelivered(
+ triggerList.size(), wakeUps, countsPerUid, wakeupCountsPerUid);
}
}
@@ -4776,6 +4783,32 @@
}
}
+ private static void increment(SparseIntArray array, int key) {
+ final int index = array.indexOfKey(key);
+ if (index >= 0) {
+ array.setValueAt(index, array.valueAt(index) + 1);
+ } else {
+ array.put(key, 1);
+ }
+ }
+
+ private void logAlarmBatchDelivered(
+ int alarms,
+ int wakeups,
+ SparseIntArray countsPerUid,
+ SparseIntArray wakeupCountsPerUid) {
+ final int[] uids = new int[countsPerUid.size()];
+ final int[] countsArray = new int[countsPerUid.size()];
+ final int[] wakeupCountsArray = new int[countsPerUid.size()];
+ for (int i = 0; i < countsPerUid.size(); i++) {
+ uids[i] = countsPerUid.keyAt(i);
+ countsArray[i] = countsPerUid.valueAt(i);
+ wakeupCountsArray[i] = wakeupCountsPerUid.get(uids[i], 0);
+ }
+ MetricsHelper.pushAlarmBatchDelivered(
+ alarms, wakeups, uids, countsArray, wakeupCountsArray);
+ }
+
/**
* Attribute blame for a WakeLock.
*
@@ -5695,12 +5728,7 @@
}
private void incrementAlarmCount(int uid) {
- final int uidIndex = mAlarmsPerUid.indexOfKey(uid);
- if (uidIndex >= 0) {
- mAlarmsPerUid.setValueAt(uidIndex, mAlarmsPerUid.valueAt(uidIndex) + 1);
- } else {
- mAlarmsPerUid.put(uid, 1);
- }
+ increment(mAlarmsPerUid, uid);
}
/**
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
index 75ed616..2923cfd 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
@@ -111,10 +111,14 @@
ActivityManager.processStateAmToProto(callerProcState));
}
- static void pushAlarmBatchDelivered(int numAlarms, int wakeups) {
+ static void pushAlarmBatchDelivered(
+ int numAlarms, int wakeups, int[] uids, int[] alarmsPerUid, int[] wakeupAlarmsPerUid) {
FrameworkStatsLog.write(
FrameworkStatsLog.ALARM_BATCH_DELIVERED,
numAlarms,
- wakeups);
+ wakeups,
+ uids,
+ alarmsPerUid,
+ wakeupAlarmsPerUid);
}
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d3b63a8..8c00c6a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -797,7 +797,6 @@
field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
- field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -2918,9 +2917,7 @@
}
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
- method public void getBoundsOnScreen(@NonNull android.graphics.Rect, boolean);
method public android.view.View getTooltipView();
- method public void getWindowDisplayFrame(@NonNull android.graphics.Rect);
method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled();
method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 0c08735..22a1a47 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -318,6 +318,13 @@
"android:activity.applyActivityFlagsForBubbles";
/**
+ * Indicates to apply {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK} to the launching shortcut.
+ * @hide
+ */
+ private static final String KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT =
+ "android:activity.applyMultipleTaskFlagForShortcut";
+
+ /**
* For Activity transitions, the calling Activity's TransitionListener used to
* notify the called Activity when the shared element and the exit transitions
* complete.
@@ -449,6 +456,7 @@
private boolean mLockTaskMode = false;
private boolean mDisallowEnterPictureInPictureWhileLaunching;
private boolean mApplyActivityFlagsForBubbles;
+ private boolean mApplyMultipleTaskFlagForShortcut;
private boolean mTaskAlwaysOnTop;
private boolean mTaskOverlay;
private boolean mTaskOverlayCanResume;
@@ -1246,6 +1254,8 @@
KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
mApplyActivityFlagsForBubbles = opts.getBoolean(
KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES, false);
+ mApplyMultipleTaskFlagForShortcut = opts.getBoolean(
+ KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT, false);
if (opts.containsKey(KEY_ANIM_SPECS)) {
Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS);
mAnimSpecs = new AppTransitionAnimationSpec[specs.length];
@@ -1815,6 +1825,16 @@
return mApplyActivityFlagsForBubbles;
}
+ /** @hide */
+ public void setApplyMultipleTaskFlagForShortcut(boolean apply) {
+ mApplyMultipleTaskFlagForShortcut = apply;
+ }
+
+ /** @hide */
+ public boolean isApplyMultipleTaskFlagForShortcut() {
+ return mApplyMultipleTaskFlagForShortcut;
+ }
+
/**
* Sets a launch cookie that can be used to track the activity and task that are launch as a
* result of this option. If the launched activity is a trampoline that starts another activity
@@ -2143,6 +2163,10 @@
if (mApplyActivityFlagsForBubbles) {
b.putBoolean(KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES, mApplyActivityFlagsForBubbles);
}
+ if (mApplyMultipleTaskFlagForShortcut) {
+ b.putBoolean(KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT,
+ mApplyMultipleTaskFlagForShortcut);
+ }
if (mAnimSpecs != null) {
b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 46d8481..0fd80c5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6185,10 +6185,8 @@
private RemoteViews generateActionButton(Action action, boolean emphasizedMode,
StandardTemplateParams p) {
final boolean tombstone = (action.actionIntent == null);
- RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
- emphasizedMode ? getEmphasizedActionLayoutResource()
- : tombstone ? getActionTombstoneLayoutResource()
- : getActionLayoutResource());
+ final RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(),
+ getActionButtonLayoutResource(emphasizedMode, tombstone));
if (!tombstone) {
button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
}
@@ -6200,6 +6198,12 @@
// change the background bgColor
CharSequence title = action.title;
int buttonFillColor = getColors(p).getSecondaryAccentColor();
+ if (tombstone) {
+ buttonFillColor = setAlphaComponentByFloatDimen(mContext,
+ ContrastColorUtil.resolveSecondaryColor(
+ mContext, getColors(p).getBackgroundColor(), mInNightMode),
+ R.dimen.notification_action_disabled_container_alpha);
+ }
if (isLegacy()) {
title = ContrastColorUtil.clearColorSpans(title);
} else {
@@ -6215,8 +6219,14 @@
title = ensureColorSpanContrast(title, buttonFillColor);
}
button.setTextViewText(R.id.action0, processTextSpans(title));
- final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
+ int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
buttonFillColor, mInNightMode);
+ if (tombstone) {
+ textColor = setAlphaComponentByFloatDimen(mContext,
+ ContrastColorUtil.resolveSecondaryColor(
+ mContext, getColors(p).getBackgroundColor(), mInNightMode),
+ R.dimen.notification_action_disabled_content_alpha);
+ }
button.setTextColor(R.id.action0, textColor);
// We only want about 20% alpha for the ripple
final int rippleColor = (textColor & 0x00ffffff) | 0x33000000;
@@ -6246,6 +6256,26 @@
return button;
}
+ private int getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone) {
+ if (emphasizedMode) {
+ return tombstone ? getEmphasizedTombstoneActionLayoutResource()
+ : getEmphasizedActionLayoutResource();
+ } else {
+ return tombstone ? getActionTombstoneLayoutResource()
+ : getActionLayoutResource();
+ }
+ }
+
+ /**
+ * Set the alpha component of {@code color} to be {@code alphaDimenResId}.
+ */
+ private static int setAlphaComponentByFloatDimen(Context context, @ColorInt int color,
+ @DimenRes int alphaDimenResId) {
+ final TypedValue alphaValue = new TypedValue();
+ context.getResources().getValue(alphaDimenResId, alphaValue, true);
+ return ColorUtils.setAlphaComponent(color, Math.round(alphaValue.getFloat() * 255));
+ }
+
/**
* Extract the color from a full-length span from the text.
*
@@ -6725,6 +6755,10 @@
return R.layout.notification_material_action_emphasized;
}
+ private int getEmphasizedTombstoneActionLayoutResource() {
+ return R.layout.notification_material_action_emphasized_tombstone;
+ }
+
private int getActionTombstoneLayoutResource() {
return R.layout.notification_material_action_tombstone;
}
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index b1252fd..49d3cac 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.PendingIntentInfo;
+import android.app.ActivityOptions;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.os.Handler;
@@ -158,7 +159,7 @@
*/
public void sendIntent(Context context, int code, Intent intent,
OnFinished onFinished, Handler handler) throws SendIntentException {
- sendIntent(context, code, intent, onFinished, handler, null);
+ sendIntent(context, code, intent, onFinished, handler, null, null /* options */);
}
/**
@@ -190,6 +191,42 @@
public void sendIntent(Context context, int code, Intent intent,
OnFinished onFinished, Handler handler, String requiredPermission)
throws SendIntentException {
+ sendIntent(context, code, intent, onFinished, handler, requiredPermission,
+ null /* options */);
+ }
+
+ /**
+ * Perform the operation associated with this IntentSender, allowing the
+ * caller to specify information about the Intent to use and be notified
+ * when the send has completed.
+ *
+ * @param context The Context of the caller. This may be null if
+ * <var>intent</var> is also null.
+ * @param code Result code to supply back to the IntentSender's target.
+ * @param intent Additional Intent data. See {@link Intent#fillIn
+ * Intent.fillIn()} for information on how this is applied to the
+ * original Intent. Use null to not modify the original Intent.
+ * @param onFinished The object to call back on when the send has
+ * completed, or null for no callback.
+ * @param handler Handler identifying the thread on which the callback
+ * should happen. If null, the callback will happen from the thread
+ * pool of the process.
+ * @param requiredPermission Name of permission that a recipient of the PendingIntent
+ * is required to hold. This is only valid for broadcast intents, and
+ * corresponds to the permission argument in
+ * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}.
+ * If null, no permission is required.
+ * @param options Additional options the caller would like to provide to modify the sending
+ * behavior. May be built from an {@link ActivityOptions} to apply to an activity start.
+ *
+ * @throws SendIntentException Throws CanceledIntentException if the IntentSender
+ * is no longer allowing more intents to be sent through it.
+ * @hide
+ */
+ public void sendIntent(Context context, int code, Intent intent,
+ OnFinished onFinished, Handler handler, String requiredPermission,
+ @Nullable Bundle options)
+ throws SendIntentException {
try {
String resolvedType = intent != null ?
intent.resolveTypeIfNeeded(context.getContentResolver())
@@ -199,7 +236,7 @@
onFinished != null
? new FinishedDispatcher(this, onFinished, handler)
: null,
- requiredPermission, null);
+ requiredPermission, options);
if (res < 0) {
throw new SendIntentException();
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 0c9fb32..bbe99f5 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1058,6 +1058,17 @@
public static final long ALWAYS_SANDBOX_DISPLAY_APIS = 185004937L; // buganizer id
/**
+ * This change id excludes the packages it is applied to from ignoreOrientationRequest behaviour
+ * that can be enabled by the device manufacturers for the com.android.server.wm.DisplayArea
+ * or for the whole display.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ public static final long OVERRIDE_RESPECT_REQUESTED_ORIENTATION = 236283604L; // buganizer id
+
+ /**
* This change id excludes the packages it is applied to from the camera compat force rotation
* treatment. See com.android.server.wm.DisplayRotationCompatPolicy for context.
* @hide
@@ -1093,34 +1104,6 @@
264301586L; // buganizer id
/**
- * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
- * an activity bounds for:
- *
- * <p>{@link android.view.View#getLocationOnScreen},
- * {@link android.view.View#getWindowVisibleDisplayFrame},
- * {@link android.view.View}#getWindowDisplayFrame,
- * {@link android.view.View}#getBoundsOnScreen.
- *
- * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
- * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
- * through
- * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
- * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
- *
- * <p>Some applications assume that they occupy the whole screen and therefore use the display
- * coordinates in their calculations as if an activity is positioned in the top-left corner of
- * the screen, with left coordinate equal to 0. This may not be the case of applications in
- * multi-window and in letterbox modes. This can lead to shifted or out of bounds UI elements in
- * case the activity is Letterboxed or is in multi-window mode.
- * @hide
- */
- @ChangeId
- @Overridable
- @Disabled
- @TestApi
- public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // buganizer id
-
- /**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
index 9c2aa66..a36ccf6 100644
--- a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
+++ b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
@@ -39,5 +39,15 @@
* {@link android.view.Display#getDisplayId()}.
*/
void onHbmDisabled(int displayId);
+
+ /**
+ * To avoid delay in switching refresh rate when activating LHBM, allow screens to request
+ * higher refersh rate if auth is possible on particular screen
+ *
+ * @param displayId The displayId for which the refresh rate should be unset. See
+ * {@link android.view.Display#getDisplayId()}.
+ * @param isPossible If authentication is possible on particualr screen
+ */
+ void onAuthenticationPossible(int displayId, boolean isPossible);
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 3d5c34c..76475f2 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -649,8 +649,11 @@
return Uid.PROCESS_STATE_NONEXISTENT;
} else if (procState == ActivityManager.PROCESS_STATE_TOP) {
return Uid.PROCESS_STATE_TOP;
- } else if (ActivityManager.isForegroundService(procState)) {
- // State when app has put itself in the foreground.
+ } else if (procState == ActivityManager.PROCESS_STATE_BOUND_TOP) {
+ return Uid.PROCESS_STATE_BACKGROUND;
+ } else if (procState == ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+ } else if (procState == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
} else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
// Persistent and other foreground states go here.
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 91d231e..787b609 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -51,8 +51,7 @@
}
/**
- * The state of an application when it is either running a foreground (top) activity
- * or a foreground service.
+ * The state of an application when it is either running a foreground (top) activity.
*/
public static final int STATE_FOREGROUND = 0;
@@ -64,7 +63,8 @@
* {@link android.app.ActivityManager#PROCESS_STATE_TRANSIENT_BACKGROUND},
* {@link android.app.ActivityManager#PROCESS_STATE_BACKUP},
* {@link android.app.ActivityManager#PROCESS_STATE_SERVICE},
- * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER}.
+ * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER},
+ * {@link android.app.ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE}.
*/
public static final int STATE_BACKGROUND = 1;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ce4a735..ce29c73 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9921,6 +9921,17 @@
"active_unlock_on_unlock_intent_when_biometric_enrolled";
/**
+ * If active unlock triggers on unlock intents, then also request active unlock on
+ * these wake-up reasons. See PowerManager.WakeReason for value mappings.
+ * WakeReasons should be separated by a pipe. For example: "0|3" or "0". If this
+ * setting should be disabled, then this should be set to an empty string. A null value
+ * will use the system default value (WAKE_REASON_UNFOLD_DEVICE).
+ * @hide
+ */
+ public static final String ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS =
+ "active_unlock_wakeups_considered_unlock_intents";
+
+ /**
* Whether the assist gesture should be enabled.
*
* @hide
@@ -11036,6 +11047,13 @@
"extra_automatic_power_save_mode";
/**
+ * Whether lockscreen weather is enabled.
+ *
+ * @hide
+ */
+ public static final String LOCK_SCREEN_WEATHER_ENABLED = "lockscreen_weather_enabled";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 6a4710f..d79ea89 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -234,6 +234,7 @@
private boolean mCanDoze;
private boolean mDozing;
private boolean mWindowless;
+ private boolean mOverlayFinishing;
private int mDozeScreenState = Display.STATE_UNKNOWN;
private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
@@ -1051,6 +1052,7 @@
// We must unbind from any overlay connection if we are unbound before finishing.
if (mOverlayConnection != null) {
mOverlayConnection.unbind();
+ mOverlayConnection = null;
}
return super.onUnbind(intent);
@@ -1067,7 +1069,9 @@
// If there is an active overlay connection, signal that the dream is ending before
// continuing. Note that the overlay cannot rely on the unbound state, since another dream
// might have bound to it in the meantime.
- if (mOverlayConnection != null) {
+ if (mOverlayConnection != null && !mOverlayFinishing) {
+ // Set mOverlayFinish to true to only allow this consumer to be added once.
+ mOverlayFinishing = true;
mOverlayConnection.addConsumer(overlay -> {
try {
overlay.endDream();
@@ -1300,9 +1304,10 @@
* Must run on mHandler.
*
* @param dreamToken Token for this dream service.
- * @param started A callback that will be invoked once onDreamingStarted has completed.
+ * @param started A callback that will be invoked once onDreamingStarted has completed.
*/
- private void attach(IBinder dreamToken, boolean canDoze, IRemoteCallback started) {
+ private void attach(IBinder dreamToken, boolean canDoze, boolean isPreviewMode,
+ IRemoteCallback started) {
if (mDreamToken != null) {
Slog.e(mTag, "attach() called when dream with token=" + mDreamToken
+ " already attached");
@@ -1350,7 +1355,8 @@
i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
final ServiceInfo serviceInfo = fetchServiceInfo(this,
new ComponentName(this, getClass()));
- i.putExtra(DreamActivity.EXTRA_DREAM_TITLE, fetchDreamLabel(this, serviceInfo));
+ i.putExtra(DreamActivity.EXTRA_DREAM_TITLE,
+ fetchDreamLabel(this, serviceInfo, isPreviewMode));
try {
if (!ActivityTaskManager.getService().startDreamActivity(i)) {
@@ -1466,10 +1472,18 @@
@Nullable
private static CharSequence fetchDreamLabel(Context context,
- @Nullable ServiceInfo serviceInfo) {
- if (serviceInfo == null) return null;
+ @Nullable ServiceInfo serviceInfo,
+ boolean isPreviewMode) {
+ if (serviceInfo == null) {
+ return null;
+ }
final PackageManager pm = context.getPackageManager();
- return serviceInfo.loadLabel(pm);
+ final CharSequence dreamLabel = serviceInfo.loadLabel(pm);
+ if (!isPreviewMode || dreamLabel == null) {
+ return dreamLabel;
+ }
+ // When in preview mode, return a special label indicating the dream is in preview.
+ return context.getResources().getString(R.string.dream_preview_title, dreamLabel);
}
@Nullable
@@ -1525,8 +1539,9 @@
final class DreamServiceWrapper extends IDreamService.Stub {
@Override
public void attach(final IBinder dreamToken, final boolean canDoze,
- IRemoteCallback started) {
- mHandler.post(() -> DreamService.this.attach(dreamToken, canDoze, started));
+ final boolean isPreviewMode, IRemoteCallback started) {
+ mHandler.post(
+ () -> DreamService.this.attach(dreamToken, canDoze, isPreviewMode, started));
}
@Override
diff --git a/core/java/android/service/dreams/IDreamService.aidl b/core/java/android/service/dreams/IDreamService.aidl
index ce04354..8b5d875 100644
--- a/core/java/android/service/dreams/IDreamService.aidl
+++ b/core/java/android/service/dreams/IDreamService.aidl
@@ -22,7 +22,7 @@
* @hide
*/
oneway interface IDreamService {
- void attach(IBinder windowToken, boolean canDoze, IRemoteCallback started);
+ void attach(IBinder windowToken, boolean canDoze, boolean isPreviewMode, IRemoteCallback started);
void detach();
void wakeUp();
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index baee094..d7480e5 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8774,8 +8774,7 @@
* @hide
*/
@UnsupportedAppUsage
- @TestApi
- public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
+ public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -8783,7 +8782,6 @@
getBoundsToScreenInternal(position, clipToParent);
outRect.set(Math.round(position.left), Math.round(position.top),
Math.round(position.right), Math.round(position.bottom));
- mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
}
/**
@@ -15588,8 +15586,7 @@
* @hide
*/
@UnsupportedAppUsage
- @TestApi
- public void getWindowDisplayFrame(@NonNull Rect outRect) {
+ public void getWindowDisplayFrame(Rect outRect) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
return;
@@ -25786,11 +25783,7 @@
getLocationInWindow(outLocation);
final AttachInfo info = mAttachInfo;
-
- // Need to offset the outLocation with the window bounds, but only if "Sandboxing View
- // Bounds APIs" is disabled. If this override is enabled, it sandboxes {@link outLocation}
- // within activity bounds.
- if (info != null && !info.mViewRootImpl.isViewBoundsSandboxingEnabled()) {
+ if (info != null) {
outLocation[0] += info.mWindowLeft;
outLocation[1] += info.mWindowTop;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 88861ee..953f17a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,7 +16,6 @@
package android.view;
-import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
@@ -83,7 +82,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -102,7 +100,6 @@
import android.app.ICompatCameraControlCallback;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
-import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -874,15 +871,6 @@
private boolean mRelayoutRequested;
- /**
- * Whether sandboxing of {@link android.view.View#getBoundsOnScreen},
- * {@link android.view.View#getLocationOnScreen},
- * {@link android.view.View#getWindowDisplayFrame} and
- * {@link android.view.View#getWindowVisibleDisplayFrame}
- * within Activity bounds is enabled for the current application.
- */
- private final boolean mViewBoundsSandboxingEnabled;
-
private int mLastTransformHint = Integer.MIN_VALUE;
/**
@@ -970,8 +958,6 @@
mHandwritingInitiator = new HandwritingInitiator(mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
- mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
-
String processorOverrideName = context.getResources().getString(
R.string.config_inputEventCompatProcessorOverrideClassName);
if (processorOverrideName.isEmpty()) {
@@ -1405,6 +1391,8 @@
listener, listener.data, mHandler, true /*waitForPresentTime*/);
mAttachInfo.mThreadedRenderer.addObserver(mHardwareRendererObserver);
}
+ // Update unbuffered request when set the root view.
+ mUnbufferedInputSource = mView.mUnbufferedInputSource;
}
view.assignParent(this);
@@ -8438,9 +8426,6 @@
*/
void getDisplayFrame(Rect outFrame) {
outFrame.set(mTmpFrames.displayFrame);
- // Apply sandboxing here (in getter) due to possible layout updates on the client after
- // {@link #mTmpFrames.displayFrame} is received from the server.
- applyViewBoundsSandboxingIfNeeded(outFrame);
}
/**
@@ -8457,60 +8442,6 @@
outFrame.top += insets.top;
outFrame.right -= insets.right;
outFrame.bottom -= insets.bottom;
- // Apply sandboxing here (in getter) due to possible layout updates on the client after
- // {@link #mTmpFrames.displayFrame} is received from the server.
- applyViewBoundsSandboxingIfNeeded(outFrame);
- }
-
- /**
- * Offset outRect to make it sandboxed within Window's bounds.
- *
- * <p>This is used by {@link android.view.View#getBoundsOnScreen},
- * {@link android.view.ViewRootImpl#getDisplayFrame} and
- * {@link android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, which are invoked by
- * {@link android.view.View#getWindowDisplayFrame} and
- * {@link android.view.View#getWindowVisibleDisplayFrame}, as well as
- * {@link android.view.ViewDebug#captureLayers} for debugging.
- */
- void applyViewBoundsSandboxingIfNeeded(final Rect inOutRect) {
- if (isViewBoundsSandboxingEnabled()) {
- inOutRect.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
- }
- }
-
- /**
- * Whether the sanboxing of the {@link android.view.View} APIs is enabled.
- *
- * <p>This is called by {@link #applyViewBoundsSandboxingIfNeeded} and
- * {@link android.view.View#getLocationOnScreen} to check if there is a need to add
- * {@link android.view.View.AttachInfo.mWindowLeft} and
- * {@link android.view.View.AttachInfo.mWindowTop} offsets.
- */
- boolean isViewBoundsSandboxingEnabled() {
- return mViewBoundsSandboxingEnabled;
- }
-
- private boolean getViewBoundsSandboxingEnabled() {
- if (!CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) {
- // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change-id is disabled.
- return false;
- }
-
- // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is enabled by the device manufacturer.
- try {
- final List<PackageManager.Property> properties = mContext.getPackageManager()
- .queryApplicationProperty(PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS);
-
- final boolean isOptedOut = !properties.isEmpty() && !properties.get(0).getBoolean();
- if (isOptedOut) {
- // PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS is disabled by the app devs.
- return false;
- }
- } catch (RuntimeException e) {
- // remote exception.
- }
-
- return true;
}
/**
@@ -8701,6 +8632,8 @@
mInsetsController.dump(prefix, writer);
+ mOnBackInvokedDispatcher.dump(prefix, writer);
+
writer.println(prefix + "View Hierarchy:");
dumpViewHierarchy(innerPrefix, writer, mView);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index a01c832..17df585 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -854,42 +854,6 @@
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager
- * .Property} for an app to inform the system that it needs to be opted-out from the
- * compatibility treatment that sandboxes {@link android.view.View} API.
- *
- * <p>The treatment can be enabled by device manufacturers for applications which misuse
- * {@link android.view.View} APIs by expecting that
- * {@link android.view.View#getLocationOnScreen},
- * {@link android.view.View#getBoundsOnScreen},
- * {@link android.view.View#getWindowVisibleDisplayFrame},
- * {@link android.view.View#getWindowDisplayFrame}
- * return coordinates as if an activity is positioned in the top-left corner of the screen, with
- * left coordinate equal to 0. This may not be the case for applications in multi-window and in
- * letterbox modes.
- *
- * <p>Setting this property to {@code false} informs the system that the application must be
- * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
- * if the device manufacturer has opted the app into the treatment.
- *
- * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
- *
- * <p><b>Syntax:</b>
- * <pre>
- * <application>
- * <property
- * android:name="android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"
- * android:value="false"/>
- * </application>
- * </pre>
- *
- * @hide
- */
- // TODO(b/263984287): Make this public API.
- String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
- "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
-
- /**
- * Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for an app to inform the system that the application can be opted-in or opted-out
* from the compatibility treatment that enables sending a fake focus event for unfocused
* resumed split screen activities. This is needed because some game engines wait to get
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 4a4f561..940b133 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -99,7 +99,21 @@
}
/**
- * Returns a value between 0 and 1 on how far along the back gesture is.
+ * Returns a value between 0 and 1 on how far along the back gesture is. This value is
+ * driven by the horizontal location of the touch point, and should be used as the fraction to
+ * seek the predictive back animation with. Specifically,
+ * <ol>
+ * <li>The progress is 0 when the touch is at the starting edge of the screen (left or right),
+ * and animation should seek to its start state.
+ * <li>The progress is approximately 1 when the touch is at the opposite side of the screen,
+ * and animation should seek to its end state. Exact end value may vary depending on
+ * screen size.
+ * </ol>
+ * <li> After the gesture finishes in cancel state, this method keeps getting invoked until the
+ * progress value animates back to 0.
+ * </ol>
+ * In-between locations are linearly interpolated based on horizontal distance from the starting
+ * edge and smooth clamped to 1 when the distance exceeds a system-wide threshold.
*/
public float getProgress() {
return mProgress;
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 38c52e7..b22f967 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -16,8 +16,10 @@
package android.window;
+import android.annotation.NonNull;
import android.util.FloatProperty;
+import com.android.internal.dynamicanimation.animation.DynamicAnimation;
import com.android.internal.dynamicanimation.animation.SpringAnimation;
import com.android.internal.dynamicanimation.animation.SpringForce;
@@ -123,6 +125,27 @@
mProgress = 0;
}
+ /**
+ * Animate the back progress animation from current progress to start position.
+ * This should be called when back is cancelled.
+ *
+ * @param finishCallback the callback to be invoked when the progress is reach to 0.
+ */
+ public void onBackCancelled(@NonNull Runnable finishCallback) {
+ final DynamicAnimation.OnAnimationEndListener listener =
+ new DynamicAnimation.OnAnimationEndListener() {
+ @Override
+ public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+ float velocity) {
+ mSpring.removeEndListener(this);
+ finishCallback.run();
+ reset();
+ }
+ };
+ mSpring.addEndListener(listener);
+ mSpring.animateToFinalPosition(0);
+ }
+
private void updateProgressValue(float progress) {
if (mLastBackEvent == null || mCallback == null || !mStarted) {
return;
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index a0bd7f7..34b75a4 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -211,6 +211,12 @@
IOnBackInvokedCallback getIOnBackInvokedCallback() {
return mIOnBackInvokedCallback;
}
+
+ @Override
+ public String toString() {
+ return "ImeCallback=ImeOnBackInvokedCallback@" + mId
+ + " Callback=" + mIOnBackInvokedCallback;
+ }
}
/**
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 49acde9..eb3bcae 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -179,16 +179,7 @@
return;
}
clearCallbacksOnDispatcher();
- if (actualDispatcher instanceof ProxyOnBackInvokedDispatcher) {
- // We don't want to nest ProxyDispatchers, so if we are given on, we unwrap its
- // actual dispatcher.
- // This can happen when an Activity is recreated but the Window is preserved (e.g.
- // when going from split-screen back to single screen)
- mActualDispatcher =
- ((ProxyOnBackInvokedDispatcher) actualDispatcher).mActualDispatcher;
- } else {
- mActualDispatcher = actualDispatcher;
- }
+ mActualDispatcher = actualDispatcher;
transferCallbacksToDispatcher();
}
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index dd9483a..2b5e16f 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -27,6 +27,7 @@
import android.view.IWindow;
import android.view.IWindowSession;
+import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
@@ -221,6 +222,26 @@
@NonNull
private static final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ /**
+ * Dump information about this WindowOnBackInvokedDispatcher
+ * @param prefix the prefix that will be prepended to each line of the produced output
+ * @param writer the writer that will receive the resulting text
+ */
+ public void dump(String prefix, PrintWriter writer) {
+ String innerPrefix = prefix + " ";
+ writer.println(prefix + "WindowOnBackDispatcher:");
+ if (mAllCallbacks.isEmpty()) {
+ writer.println(prefix + "<None>");
+ return;
+ }
+
+ writer.println(innerPrefix + "Top Callback: " + getTopCallback());
+ writer.println(innerPrefix + "Callbacks: ");
+ mAllCallbacks.forEach((callback, priority) -> {
+ writer.println(innerPrefix + " Callback: " + callback + " Priority=" + priority);
+ });
+ }
+
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback;
@@ -255,11 +276,12 @@
@Override
public void onBackCancelled() {
Handler.getMain().post(() -> {
- mProgressAnimator.reset();
- final OnBackAnimationCallback callback = getBackAnimationCallback();
- if (callback != null) {
- callback.onBackCancelled();
- }
+ mProgressAnimator.onBackCancelled(() -> {
+ final OnBackAnimationCallback callback = getBackAnimationCallback();
+ if (callback != null) {
+ callback.onBackCancelled();
+ }
+ });
});
}
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 43be031..1b901f5 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -21,6 +21,7 @@
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
+import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
import static com.android.internal.util.ArrayUtils.convertToLongArray;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -147,11 +148,13 @@
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
"1" /* Value to enable */, "0" /* Value to disable */,
R.string.color_correction_feature_name));
- featuresMap.put(ONE_HANDED_COMPONENT_NAME,
- new ToggleableFrameworkFeatureInfo(
- Settings.Secure.ONE_HANDED_MODE_ACTIVATED,
- "1" /* Value to enable */, "0" /* Value to disable */,
- R.string.one_handed_mode_feature_name));
+ if (SUPPORT_ONE_HANDED_MODE) {
+ featuresMap.put(ONE_HANDED_COMPONENT_NAME,
+ new ToggleableFrameworkFeatureInfo(
+ Settings.Secure.ONE_HANDED_MODE_ACTIVATED,
+ "1" /* Value to enable */, "0" /* Value to disable */,
+ R.string.one_handed_mode_feature_name));
+ }
featuresMap.put(REDUCE_BRIGHT_COLORS_COMPONENT_NAME,
new ToggleableFrameworkFeatureInfo(
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index fc2c8cc..2d87745 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -25,6 +25,7 @@
import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
+import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityShortcutInfo;
@@ -209,6 +210,7 @@
context.getString(R.string.accessibility_magnification_chooser_text),
context.getDrawable(R.drawable.ic_accessibility_magnification),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+ targets.add(magnification);
final ToggleAllowListingFeatureTarget daltonizer =
new ToggleAllowListingFeatureTarget(context,
@@ -219,6 +221,7 @@
context.getString(R.string.color_correction_feature_name),
context.getDrawable(R.drawable.ic_accessibility_color_correction),
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
+ targets.add(daltonizer);
final ToggleAllowListingFeatureTarget colorInversion =
new ToggleAllowListingFeatureTarget(context,
@@ -229,16 +232,20 @@
context.getString(R.string.color_inversion_feature_name),
context.getDrawable(R.drawable.ic_accessibility_color_inversion),
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+ targets.add(colorInversion);
- final ToggleAllowListingFeatureTarget oneHandedMode =
- new ToggleAllowListingFeatureTarget(context,
- shortcutType,
- isShortcutContained(context, shortcutType,
- ONE_HANDED_COMPONENT_NAME.flattenToString()),
- ONE_HANDED_COMPONENT_NAME.flattenToString(),
- context.getString(R.string.one_handed_mode_feature_name),
- context.getDrawable(R.drawable.ic_accessibility_one_handed),
- Settings.Secure.ONE_HANDED_MODE_ACTIVATED);
+ if (SUPPORT_ONE_HANDED_MODE) {
+ final ToggleAllowListingFeatureTarget oneHandedMode =
+ new ToggleAllowListingFeatureTarget(context,
+ shortcutType,
+ isShortcutContained(context, shortcutType,
+ ONE_HANDED_COMPONENT_NAME.flattenToString()),
+ ONE_HANDED_COMPONENT_NAME.flattenToString(),
+ context.getString(R.string.one_handed_mode_feature_name),
+ context.getDrawable(R.drawable.ic_accessibility_one_handed),
+ Settings.Secure.ONE_HANDED_MODE_ACTIVATED);
+ targets.add(oneHandedMode);
+ }
final ToggleAllowListingFeatureTarget reduceBrightColors =
new ToggleAllowListingFeatureTarget(context,
@@ -249,11 +256,6 @@
context.getString(R.string.reduce_bright_colors_feature_name),
context.getDrawable(R.drawable.ic_accessibility_reduce_bright_colors),
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED);
-
- targets.add(magnification);
- targets.add(daltonizer);
- targets.add(colorInversion);
- targets.add(oneHandedMode);
targets.add(reduceBrightColors);
return targets;
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
new file mode 100644
index 0000000..c946db1
--- /dev/null
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -0,0 +1,188 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.config.sysui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Provides a central definition of debug SystemUI's SystemProperties flags, and their defaults.
+ *
+ * The main feature of this class is that it encodes a system-wide default for each flag which can
+ * be updated by engineers with a single-line CL.
+ *
+ * NOTE: Because flag values returned by this class are not cached, it is important that developers
+ * understand the intricacies of changing values and how that applies to their own code.
+ * Generally, the best practice is to set the property, and then restart the device so that any
+ * processes with stale state can be updated. However, if your code has no state derived from the
+ * flag value and queries it any time behavior is relevant, then it may be safe to change the flag
+ * and not immediately reboot.
+ *
+ * To enable flags in debuggable builds, use the following commands:
+ *
+ * $ adb shell setprop persist.sysui.whatever_the_flag true
+ * $ adb reboot
+ *
+ * @hide
+ */
+public class SystemUiSystemPropertiesFlags {
+
+ /** The teamfood flag allows multiple features to be opted into at once. */
+ public static final Flag TEAMFOOD = devFlag("persist.sysui.teamfood");
+
+ /**
+ * Flags related to notification features
+ */
+ public static final class NotificationFlags {
+
+ /**
+ * FOR DEVELOPMENT / TESTING ONLY!!!
+ * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission.
+ * NOTE: enabling this implies SHOW_STICKY_HUN_FOR_DENIED_FSI in SystemUI
+ */
+ public static final Flag FSI_FORCE_DEMOTE =
+ devFlag("persist.sysui.notification.fsi_force_demote");
+
+ /** Gating the feature which shows FSI-denied notifications as Sticky HUNs */
+ public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
+ devFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
+
+ /** Gating the ability for users to dismiss ongoing event notifications */
+ public static final Flag ALLOW_DISMISS_ONGOING =
+ devFlag("persist.sysui.notification.ongoing_dismissal");
+
+ /** Gating the redaction of OTP notifications on the lockscreen */
+ public static final Flag OTP_REDACTION =
+ devFlag("persist.sysui.notification.otp_redaction");
+
+ }
+
+ //// == End of flags. Everything below this line is the implementation. == ////
+
+ /** The interface used for resolving SystemUI SystemProperties Flags to booleans. */
+ public interface FlagResolver {
+ /** Is the flag enabled? */
+ boolean isEnabled(Flag flag);
+ }
+
+ /** The primary, immutable resolver returned by getResolver() */
+ private static final FlagResolver
+ MAIN_RESOLVER =
+ Build.IS_DEBUGGABLE ? new DebugResolver() : new ProdResolver();
+
+ /**
+ * On debuggable builds, this can be set to override the resolver returned by getResolver().
+ * This can be useful to override flags when testing components that do not allow injecting the
+ * SystemUiPropertiesFlags resolver they use.
+ * Always set this to null when tests tear down.
+ */
+ @VisibleForTesting
+ public static FlagResolver TEST_RESOLVER = null;
+
+ /** Get the resolver for this device configuration. */
+ public static FlagResolver getResolver() {
+ if (Build.IS_DEBUGGABLE && TEST_RESOLVER != null) {
+ Log.i("SystemUiSystemPropertiesFlags", "Returning debug resolver " + TEST_RESOLVER);
+ return TEST_RESOLVER;
+ }
+ return MAIN_RESOLVER;
+ }
+
+ /**
+ * Creates a flag that is enabled by default in debuggable builds.
+ * It can be enabled by setting this flag's SystemProperty to 1.
+ *
+ * This flag is ALWAYS disabled in release builds.
+ */
+ @VisibleForTesting
+ public static Flag devFlag(String name) {
+ return new Flag(name, false, null);
+ }
+
+ /**
+ * Creates a flag that is disabled by default in debuggable builds.
+ * It can be enabled or force-disabled by setting this flag's SystemProperty to 1 or 0.
+ * If this flag's SystemProperty is not set, the flag can be enabled by setting the
+ * TEAMFOOD flag's SystemProperty to 1.
+ *
+ * This flag is ALWAYS disabled in release builds.
+ */
+ @VisibleForTesting
+ public static Flag teamfoodFlag(String name) {
+ return new Flag(name, false, TEAMFOOD);
+ }
+
+ /**
+ * Creates a flag that is enabled by default in debuggable builds.
+ * It can be enabled by setting this flag's SystemProperty to 0.
+ *
+ * This flag is ALWAYS enabled in release builds.
+ */
+ @VisibleForTesting
+ public static Flag releasedFlag(String name) {
+ return new Flag(name, true, null);
+ }
+
+ /** Represents a developer-switchable gate for a feature. */
+ public static final class Flag {
+ public final String mSysPropKey;
+ public final boolean mDefaultValue;
+ @Nullable
+ public final Flag mDebugDefault;
+
+ /** constructs a new flag. only visible for testing the class */
+ @VisibleForTesting
+ public Flag(@NonNull String sysPropKey, boolean defaultValue, @Nullable Flag debugDefault) {
+ mSysPropKey = sysPropKey;
+ mDefaultValue = defaultValue;
+ mDebugDefault = debugDefault;
+ }
+ }
+
+ /** Implementation of the interface used in release builds. */
+ @VisibleForTesting
+ public static final class ProdResolver implements
+ FlagResolver {
+ @Override
+ public boolean isEnabled(Flag flag) {
+ return flag.mDefaultValue;
+ }
+ }
+
+ /** Implementation of the interface used in debuggable builds. */
+ @VisibleForTesting
+ public static class DebugResolver implements FlagResolver {
+ @Override
+ public final boolean isEnabled(Flag flag) {
+ if (flag.mDebugDefault == null) {
+ return getBoolean(flag.mSysPropKey, flag.mDefaultValue);
+ }
+ return getBoolean(flag.mSysPropKey, isEnabled(flag.mDebugDefault));
+ }
+
+ /** Look up the value; overridable for tests to avoid needing to set SystemProperties */
+ @VisibleForTesting
+ public boolean getBoolean(String key, boolean defaultValue) {
+ return SystemProperties.getBoolean(key, defaultValue);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 09e409b..ac4976f 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -298,18 +298,16 @@
BatteryStats.Uid.PROCESS_STATE_FOREGROUND, realtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
- totalForegroundDurationUs += uid.getProcessStateTime(
- BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE, realtimeUs,
- BatteryStats.STATS_SINCE_CHARGED);
-
return totalForegroundDurationUs / 1000;
}
private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, long realtimeUs) {
- return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND, realtimeUs,
- BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ return (uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
+ realtimeUs, BatteryStats.STATS_SINCE_CHARGED)
+ + uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
+ realtimeUs, BatteryStats.STATS_SINCE_CHARGED))
+ / 1000;
}
-
private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryUsageStatsQuery query) {
final boolean includePowerModels = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index 98d81c9..cccd80e 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -31,6 +31,8 @@
SystemProperties.getInt("ro.factorytest", 0);
public static final String CONTROL_PRIVAPP_PERMISSIONS =
SystemProperties.get("ro.control_privapp_permissions");
+ public static final boolean SUPPORT_ONE_HANDED_MODE =
+ SystemProperties.getBoolean("ro.support_one_handed_mode", /* def= */ false);
// ------ ro.hdmi.* -------- //
/**
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index fb38bba..bb69192f 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -379,8 +379,12 @@
// window, as we'll be skipping the addView in handleResumeActivity(), and
// the token will not be updated as for a new window.
getAttributes().token = preservedWindow.getAttributes().token;
- mProxyOnBackInvokedDispatcher.setActualDispatcher(
- preservedWindow.getOnBackInvokedDispatcher());
+ final ViewRootImpl viewRoot = mDecor.getViewRootImpl();
+ if (viewRoot != null) {
+ // Clear the old callbacks and attach to the new window.
+ viewRoot.getOnBackInvokedDispatcher().clear();
+ onViewRootImplSet(viewRoot);
+ }
}
// Even though the device doesn't support picture-in-picture mode,
// an user can force using it through developer options.
diff --git a/core/res/res/layout/notification_material_action_emphasized_tombstone.xml b/core/res/res/layout/notification_material_action_emphasized_tombstone.xml
new file mode 100644
index 0000000..60f10db
--- /dev/null
+++ b/core/res/res/layout/notification_material_action_emphasized_tombstone.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.internal.widget.EmphasizedNotificationButton
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/NotificationEmphasizedAction"
+ android:id="@+id/action0"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginStart="12dp"
+ android:drawablePadding="6dp"
+ android:enabled="false"
+ android:gravity="center"
+ android:singleLine="true"
+ android:ellipsize="end"
+/>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index ef746fa..fcfdd95 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Bekyk tans volskerm"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Swiep van bo na onder as jy wil uitgaan."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Het dit"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Draai vir ’n beter aansig"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Verlaat gedeelde skerm vir ’n beter aansig"</string>
<string name="done_label" msgid="7283767013231718521">"Klaar"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Ure se sirkelglyer"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Minute se sirkelglyer"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 89039a6..a5619fd 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"ሙሉ ገጽ በማሳየት ላይ"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"ለመውጣት፣ ከላይ ወደታች ጠረግ ያድርጉ።"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"ገባኝ"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"ለተሻለ ዕይታ ያሽከርክሩ"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"ለተሻለ ዕይታ የተከፈለ ማያ ገጽን ትተው ይውጡ"</string>
<string name="done_label" msgid="7283767013231718521">"ተከናውኗል"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"የሰዓታት ክብ ተንሸራታች"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"የደቂቃዎች ክብ ተንሸራታች"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index b04af91..1aba35a 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1844,10 +1844,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"جارٍ العرض بملء الشاشة"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"للخروج، مرر بسرعة من أعلى إلى أسفل."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"حسنًا"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"يمكنك تدوير الجهاز لرؤية شاشة معاينة الكاميرا بشكل أوضح."</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"يمكنك الخروج من وضع \"تقسيم الشاشة\" لرؤية شاشة معاينة الكاميرا بشكل أوضح."</string>
<string name="done_label" msgid="7283767013231718521">"تم"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"شريط التمرير الدائري للساعات"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"شريط التمرير الدائري للدقائق"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index cd69314..38ec792 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"স্ক্ৰীন পূৰ্ণৰূপত চাই আছে"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"বাহিৰ হ\'বলৈ ওপৰৰপৰা তললৈ ছোৱাইপ কৰক।"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"বুজি পালোঁ"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"ভালকৈ চাবলৈ ঘূৰাওক"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"ভালকৈ চাবলৈ বিভাজিত স্ক্ৰীনৰ পৰা বাহিৰ হওক"</string>
<string name="done_label" msgid="7283767013231718521">"সম্পন্ন কৰা হ’ল"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"ঘড়ীৰ বৃত্তাকাৰ শ্লাইডাৰ"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"মিনিটৰ বৃত্তাকাৰ শ্লাইডাৰ"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 5abd2b6..d2359c7 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Tam ekrana baxış"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Çıxmaq üçün yuxarıdan aşağı sürüşdürün."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Anladım"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Daha yaxşı görünüş üçün fırladın"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Daha yaxşı görünüş üçün bölünmüş ekrandan çıxın"</string>
<string name="done_label" msgid="7283767013231718521">"Hazırdır"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Dairəvi saat slayderi"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Dairəvi dəqiqə slayderi"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index fa0ebc1..24d7990 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -1842,10 +1842,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Прагляд у поўнаэкранным рэжыме"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Для выхаду правядзіце зверху ўніз."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Зразумела"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Павярнуць для лепшага прагляду"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Выйсці з рэжыму падзеленага экрана для лепшага прагляду"</string>
<string name="done_label" msgid="7283767013231718521">"Гатова"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Кругавы паўзунок гадзін"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Кругавы паўзунок хвілін"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index ba4466f..164040f 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Изглед на цял екран"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"За изход плъзнете пръст надолу от горната част."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Разбрах"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Завъртете за по-добър изглед"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Излезте от разделения екран за по-добър изглед"</string>
<string name="done_label" msgid="7283767013231718521">"Готово"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Кръгов плъзгач за часовете"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Кръгов плъзгач за минутите"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index ef51384..b0a9467 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"পূর্ণ স্ক্রিনে দেখা হচ্ছে"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"প্রস্থান করতে উপর থেকে নিচের দিকে সোয়াইপ করুন"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"বুঝেছি"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"আরও ভাল ক্যামেরা ভিউ পাওয়ার জন্য ঘোরান"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"আরও ভাল ক্যামেরা ভিউ পাওয়ার জন্য স্প্লিট স্ক্রিন থেকে বেরিয়ে আসুন"</string>
<string name="done_label" msgid="7283767013231718521">"সম্পন্ন হয়েছে"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"বৃত্তাকার ঘণ্টা নির্বাচকের স্লাইডার"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"বৃত্তাকার মিনিট নির্বাচকের স্লাইডার"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index fd5cd64..ec381a5 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Prikazuje se cijeli ekran"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Da izađete, prevucite odozgo nadolje."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Razumijem"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotirajte za bolji prikaz"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Izađite iz podijeljenog ekrana za bolji prikaz"</string>
<string name="done_label" msgid="7283767013231718521">"Gotovo"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Kružni klizač za odabir sata"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Kružni klizač za minute"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 153f430..ca1d87f 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Mode de pantalla completa"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Per sortir, llisca cap avall des de la part superior."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Entesos"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Gira per a una millor visualització"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Surt de la pantalla dividida per a una millor visualització"</string>
<string name="done_label" msgid="7283767013231718521">"Fet"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Control circular de les hores"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Control circular dels minuts"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 24617d0..a04b34a 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1842,10 +1842,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Zobrazení celé obrazovky"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Režim ukončíte přejetím prstem shora dolů."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Rozumím"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Otočte obrazovku, abyste lépe viděli"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Ukončete režim rozdělené obrazovky, abyste lépe viděli"</string>
<string name="done_label" msgid="7283767013231718521">"Hotovo"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Kruhový posuvník hodin"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Kruhový posuvník minut"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index bcc7d50..51688d3 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Visning i fuld skærm"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Stryg ned fra toppen for at afslutte."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK, det er forstået"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Roter, og få en bedre visning"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Afslut opdelt skærm, og få en bedre visning"</string>
<string name="done_label" msgid="7283767013231718521">"Udfør"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Cirkulær timevælger"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Cirkulær minutvælger"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 0df4fc6..9b416bc 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Vollbildmodus wird aktiviert"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Zum Beenden von oben nach unten wischen"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Ok"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Drehen, um die Ansicht zu verbessern"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Modus für geteilten Bildschirm beenden, um die Ansicht zu verbessern"</string>
<string name="done_label" msgid="7283767013231718521">"Fertig"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Kreisförmiger Schieberegler für Stunden"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Kreisförmiger Schieberegler für Minuten"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 8c354fb..8ab0345 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -305,7 +305,7 @@
<string name="permgrouplab_calendar" msgid="6426860926123033230">"Ημερολόγιο"</string>
<string name="permgroupdesc_calendar" msgid="6762751063361489379">"έχει πρόσβαση στο ημερολόγιό σας"</string>
<string name="permgrouplab_sms" msgid="795737735126084874">"SMS"</string>
- <string name="permgroupdesc_sms" msgid="5726462398070064542">"στέλνει και να διαβάζει μηνύματα SMS"</string>
+ <string name="permgroupdesc_sms" msgid="5726462398070064542">"στέλνει και διαβάζει μηνύματα SMS"</string>
<string name="permgrouplab_storage" msgid="17339216290379241">"Αρχεία"</string>
<string name="permgroupdesc_storage" msgid="5378659041354582769">"πρόσβαση στα αρχεία της συσκευής σας"</string>
<string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Μουσική και ήχος"</string>
@@ -313,7 +313,7 @@
<string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Φωτογραφίες και βίντεο"</string>
<string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"πρόσβαση στις φωτογραφίες και τα βίντεο στη συσκευή σας"</string>
<string name="permgrouplab_microphone" msgid="2480597427667420076">"Μικρόφωνο"</string>
- <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ηχογραφεί"</string>
+ <string name="permgroupdesc_microphone" msgid="1047786732792487722">"ηχογράφηση"</string>
<string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Σωματική δραστ/τητα"</string>
<string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"πρόσβαση στη σωματική σας δραστηριότητα"</string>
<string name="permgrouplab_camera" msgid="9090413408963547706">"Κάμερα"</string>
@@ -323,7 +323,7 @@
<string name="permgrouplab_calllog" msgid="7926834372073550288">"Αρχεία καταγρ. κλήσ."</string>
<string name="permgroupdesc_calllog" msgid="2026996642917801803">"ανάγνωση και εγγραφή αρχείου καταγραφής τηλεφωνικών κλήσεων"</string>
<string name="permgrouplab_phone" msgid="570318944091926620">"Τηλέφωνο"</string>
- <string name="permgroupdesc_phone" msgid="270048070781478204">"πραγματοποιεί και να διαχειρίζεται τηλ/κές κλήσεις"</string>
+ <string name="permgroupdesc_phone" msgid="270048070781478204">"πραγματοποιεί και διαχειρίζεται τηλ/κές κλήσεις"</string>
<string name="permgrouplab_sensors" msgid="9134046949784064495">"Αισθητήρες σώματος"</string>
<string name="permgroupdesc_sensors" msgid="2610631290633747752">"πρόσβαση στα δεδομένα αισθητήρα σχετικά με τις ζωτικές ενδείξεις σας"</string>
<string name="permgrouplab_notifications" msgid="5472972361980668884">"Ειδοποιήσεις"</string>
@@ -370,7 +370,7 @@
<string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Επιτρέπει στην εφαρμογή την ανάγνωση μηνυμάτων που έχουν μεταδοθεί μέσω κινητού τηλεφώνου και έχουν ληφθεί από τη συσκευή σας. Ειδοποιήσεις που μεταδίδονται μέσω κινητού παραδίδονται σε ορισμένες τοποθεσίες για να σας προειδοποιήσουν για καταστάσεις έκτακτης ανάγκης. Κακόβουλες εφαρμογές ενδέχεται να παρεμποδίσουν την απόδοση ή τη λειτουργία της συσκευής σας κατά τη λήψη μετάδοσης μέσω κινητού σχετικά με μια επείγουσα κατάσταση."</string>
<string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"διαβάζει ροές δεδομένων στις οποίες έχετε εγγραφεί"</string>
<string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"Επιτρέπει στην εφαρμογή τη λήψη λεπτομερειών σχετικά με τις τρέχουσες συγχρονισμένες ροές δεδομένων."</string>
- <string name="permlab_sendSms" msgid="7757368721742014252">"στέλνει και να διαβάζει μηνύματα SMS"</string>
+ <string name="permlab_sendSms" msgid="7757368721742014252">"στέλνει και διαβάζει μηνύματα SMS"</string>
<string name="permdesc_sendSms" msgid="6757089798435130769">"Επιτρέπει στην εφαρμογή των αποστολή μηνυμάτων SMS. Αυτό μπορεί να προκαλέσει μη αναμενόμενες χρεώσεις. Οι κακόβουλες εφαρμογές ενδέχεται να σας κοστίσουν χρήματα, αποστέλλοντας μηνύματα χωρίς την έγκρισή σας."</string>
<string name="permlab_readSms" msgid="5164176626258800297">"διαβάζει τα μηνύματα κειμένου (SMS ή MMS)"</string>
<string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"Αυτή η εφαρμογή μπορεί να διαβάσει όλα τα μηνύματα SMS (κειμένου) που είναι αποθηκευμένα στο tablet που χρησιμοποιείτε."</string>
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Προβολή σε πλήρη οθόνη"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Για έξοδο, σύρετε προς τα κάτω από το επάνω μέρος."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Το κατάλαβα"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Περιστρέψτε την οθόνη για καλύτερη προβολή"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Εξέλθετε από τον διαχωρισμό οθόνης για καλύτερη προβολή"</string>
<string name="done_label" msgid="7283767013231718521">"Τέλος"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Κυκλικό ρυθμιστικό ωρών"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Κυκλικό ρυθμιστικό λεπτών"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index fc37786..e96b543 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotate for a better view"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Exit split screen for a better view"</string>
<string name="done_label" msgid="7283767013231718521">"Done"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 7bc59d7..0ee88ad 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotate for a better view"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Exit split screen for a better view"</string>
<string name="done_label" msgid="7283767013231718521">"Done"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 997929d..68cbfa8 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Got it"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotate for a better view"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Exit split screen for a better view"</string>
<string name="done_label" msgid="7283767013231718521">"Done"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Hours circular slider"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Minutes circular slider"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 884b767..514f8ef 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Visualización en pantalla completa"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Para salir, desliza el dedo hacia abajo desde la parte superior."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Entendido"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Gira la pantalla para obtener una mejor vista"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Sal de la pantalla dividida para obtener una mejor vista"</string>
<string name="done_label" msgid="7283767013231718521">"Listo"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Control deslizante circular de horas"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Control deslizante circular de minutos"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index b26ee6a..f921c7b 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Modo de pantalla completa"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Para salir, desliza el dedo de arriba abajo."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Entendido"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Gira la pantalla para verlo mejor"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Sal de la pantalla dividida para verlo mejor"</string>
<string name="done_label" msgid="7283767013231718521">"Hecho"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Control deslizante circular de horas"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Control deslizante circular de minutos"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 0c95591..9178aff 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Kuvamine täisekraanil"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Väljumiseks pühkige ülevalt alla."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Selge"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Pöörake parema vaate jaoks"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Parema vaate jaoks väljuge jagatud ekraanikuvast"</string>
<string name="done_label" msgid="7283767013231718521">"Valmis"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Ringikujuline tunniliugur"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Ringikujuline minutiliugur"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 0dffb90..28d3e6a 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Pantaila osoko ikuspegia"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Irteteko, pasatu hatza goitik behera."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Ados"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Biratu pantaila ikuspegi hobea lortzeko"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Irten pantaila zatitutik ikuspegi hobea lortzeko"</string>
<string name="done_label" msgid="7283767013231718521">"Eginda"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Ordua aukeratzeko ikuspegi zirkularra"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Minutuak aukeratzeko ikuspegi zirkularra"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 20b6745..26d1d4b 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"مشاهده در حالت تمام صفحه"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"برای خروج، انگشتتان را از بالای صفحه به پایین بکشید."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"متوجه شدم"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"برای دید بهتر، دستگاه را بچرخانید"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"برای دید بهتر، از صفحهٔ دونیمه خارج شوید"</string>
<string name="done_label" msgid="7283767013231718521">"تمام"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"لغزنده دایرهای ساعت"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"لغزنده دایرهای دقیقه"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index eb8a778..2795609 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Affichage plein écran"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Pour quitter, balayez vers le bas à partir du haut."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Faire pivoter pour obtenir un meilleur affichage"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Quitter l\'écran partagé pour obtenir un meilleur affichage"</string>
<string name="done_label" msgid="7283767013231718521">"Terminé"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Curseur circulaire des heures"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Curseur circulaire des minutes"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 480a7b5..0888f2f 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Affichage en plein écran"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Pour quitter, balayez l\'écran du haut vers le bas."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Faites pivoter pour mieux voir"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Quittez l\'écran partagé pour mieux voir"</string>
<string name="done_label" msgid="7283767013231718521">"OK"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Curseur circulaire des heures"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Curseur circulaire des minutes"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 1e8063a..5576f42 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Vendo pantalla completa"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Para saír, pasa o dedo cara abaixo desde a parte superior."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Entendido"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Xira a pantalla para que se vexa mellor"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Sae da pantalla dividida para que se vexa mellor"</string>
<string name="done_label" msgid="7283767013231718521">"Feito"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Control desprazable circular das horas"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Control desprazable circular dos minutos"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 45f12d7..2665731 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"પૂર્ણ સ્ક્રીન પર જુઓ"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"બહાર નીકળવા માટે, ટોચ પરથી નીચે સ્વાઇપ કરો."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"સમજાઈ ગયું"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"બહેતર વ્યૂ માટે ફેરવો"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"બહેતર વ્યૂ માટે, વિભાજિત સ્ક્રીનમાંથી બહાર નીકળો"</string>
<string name="done_label" msgid="7283767013231718521">"થઈ ગયું"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"કલાકનું વર્તુળાકાર સ્લાઇડર"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"મિનિટનું વર્તુળાકાર સ્લાઇડર"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index f145a4c..b17eb42 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"आप पूरे स्क्रीन पर देख रहे हैं"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"बाहर निकलने के लिए, ऊपर से नीचे स्वाइप करें."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"ठीक है"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"बेहतर व्यू पाने के लिए, डिवाइस की स्क्रीन को घुमाएं"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"बेहतर व्यू पाने के लिए, स्प्लिट स्क्रीन मोड बंद करें"</string>
<string name="done_label" msgid="7283767013231718521">"हो गया"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"घंटो का चक्राकार स्लाइडर"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"मिनटों का चक्राकार स्लाइडर"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index e126941..0b0b8fe 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Gledanje preko cijelog zaslona"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Za izlaz prijeđite prstom od vrha prema dolje."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Shvaćam"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Zakrenite kako biste bolje vidjeli"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Zatvorite podijeljeni zaslon kako biste bolje vidjeli"</string>
<string name="done_label" msgid="7283767013231718521">"Gotovo"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Kružni klizač sati"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Kružni klizač minuta"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index a529352..d797230 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Megtekintése teljes képernyőn"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Kilépéshez csúsztassa ujját fentről lefelé."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Értem"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Forgassa el a jobb élmény érdekében"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Lépjen ki az osztott képernyős módból a jobb élmény érdekében"</string>
<string name="done_label" msgid="7283767013231718521">"Kész"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Óra kör alakú csúszkája"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Perc kör alakú csúszkája"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 825924c..5937faf 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Լիաէկրան դիտում"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Դուրս գալու համար վերևից սահահարվածեք դեպի ներքև:"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Պարզ է"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Պտտեք՝ դիտակերպը լավացնելու համար"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Դուրս եկեք կիսված էկրանի ռեժիմից՝ դիտակերպը լավացնելու համար"</string>
<string name="done_label" msgid="7283767013231718521">"Պատրաստ է"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Ժամերի ընտրություն թվատախտակից"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Րոպեների ընտրություն թվատախտակից"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 8768e4d..93b8a15 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Melihat layar penuh"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Untuk keluar, geser layar ke bawah dari atas."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Mengerti"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Putar posisi layar untuk mendapatkan tampilan yang lebih baik"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Keluar dari layar terpisah untuk mendapatkan tampilan yang lebih baik"</string>
<string name="done_label" msgid="7283767013231718521">"Selesai"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Penggeser putar jam"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Penggeser putar menit"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 9a27d8d..29c1a2a 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Notar allan skjáinn"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Strjúktu niður frá efri brún til að hætta."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Ég skil"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Snúðu til að sjá betur"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Lokaðu skjáskiptingu til að sjá betur"</string>
<string name="done_label" msgid="7283767013231718521">"Lokið"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Valskífa fyrir klukkustundir"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Valskífa fyrir mínútur"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 8a6ef85..d1d7d3b 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -992,7 +992,7 @@
<string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"Selettore utente"</string>
<string name="keyguard_accessibility_status" msgid="6792745049712397237">"Stato"</string>
<string name="keyguard_accessibility_camera" msgid="7862557559464986528">"Fotocamera"</string>
- <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Controlli media"</string>
+ <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Controlli multimediali"</string>
<string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"Riordino dei widget iniziato."</string>
<string name="keyguard_accessibility_widget_reorder_end" msgid="1083806817600593490">"Riordino dei widget terminato."</string>
<string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"Widget <xliff:g id="WIDGET_INDEX">%1$s</xliff:g> eliminato."</string>
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Visualizzazione a schermo intero"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Per uscire, scorri dall\'alto verso il basso."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Ruota per migliorare l\'anteprima"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Esci dallo schermo diviso per migliorare l\'anteprima"</string>
<string name="done_label" msgid="7283767013231718521">"Fine"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Dispositivo di scorrimento circolare per le ore"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Dispositivo di scorrimento circolare per i minuti"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 4ad7c2c..963d473 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"צפייה במסך מלא"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"כדי לצאת, פשוט מחליקים אצבע מלמעלה למטה."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"הבנתי"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"מסובבים כדי לראות טוב יותר"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"צריך לצאת מהמסך המפוצל כדי לראות טוב יותר"</string>
<string name="done_label" msgid="7283767013231718521">"סיום"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"מחוון שעות מעגלי"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"מחוון דקות מעגלי"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 917f911..f2bc9a9 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"全画面表示"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"終了するには、上から下にスワイプします。"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"画面を回転させて見やすくしましょう"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"分割画面を終了して見やすくしましょう"</string>
<string name="done_label" msgid="7283767013231718521">"完了"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"円形スライダー(時)"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"円形スライダー(分)"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index e8b84e2..3803a26 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -315,7 +315,7 @@
<string name="permgrouplab_microphone" msgid="2480597427667420076">"მიკროფონი"</string>
<string name="permgroupdesc_microphone" msgid="1047786732792487722">"აუდიოს ჩაწერა"</string>
<string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"ფიზიკური აქტივობა"</string>
- <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"ფიზიკური აქტივობაზე წვდომა"</string>
+ <string name="permgroupdesc_activityRecognition" msgid="4725624819457670704">"ფიზიკურ აქტივობაზე წვდომა"</string>
<string name="permgrouplab_camera" msgid="9090413408963547706">"კამერა"</string>
<string name="permgroupdesc_camera" msgid="7585150538459320326">"ფოტოებისა და ვიდეოების გადაღება"</string>
<string name="permgrouplab_nearby_devices" msgid="5529147543651181991">"ახლომახლო მოწყობილობები"</string>
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"სრულ ეკრანზე ნახვა"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"გამოსვლისათვის, გაასრიალეთ ზემოდან ქვემოთ."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"გასაგებია"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"შეატრიალეთ უკეთესი ხედისთვის"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"უკეთესი ხედვისთვის გამოდით გაყოფილი ეკრანიდან"</string>
<string name="done_label" msgid="7283767013231718521">"დასრულდა"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"საათების წრიული სლაიდერი"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"წუთების წრიული სლაიდერი"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 306af3a..2b11346 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Толық экранда көру"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Шығу үшін жоғарыдан төмен қарай сырғытыңыз."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Түсінікті"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Жақсырақ көру үшін бұрыңыз."</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Жақсырақ көру үшін экранды бөлу режимінен шығыңыз."</string>
<string name="done_label" msgid="7283767013231718521">"Дайын"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Сағаттар айналымының қозғалтқышы"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Минут айналымын қозғалтқыш"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 2f4b516..9cf498d 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"កំពុងមើលពេញអេក្រង់"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"ដើម្បីចាកចេញ សូមអូសពីលើចុះក្រោម។"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"យល់ហើយ"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"បង្វិលដើម្បីមើលបានកាន់តែច្បាស់"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"ចេញពីមុខងារបំបែកអេក្រង់ដើម្បីមើលបានកាន់តែច្បាស់"</string>
<string name="done_label" msgid="7283767013231718521">"រួចរាល់"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"គ្រាប់រំកិលរង្វង់ម៉ោង"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"គ្រាប់រំកិលរង្វង់នាទី"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index a04faa5..879f9e8 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"ಪೂರ್ಣ ಪರದೆಯನ್ನು ವೀಕ್ಷಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"ನಿರ್ಗಮಿಸಲು, ಮೇಲಿನಿಂದ ಕೆಳಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"ತಿಳಿಯಿತು"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"ಅತ್ಯುತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ತಿರುಗಿಸಿ"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"ಅತ್ಯುತ್ತಮ ವೀಕ್ಷಣೆಗಾಗಿ ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ನಿಂದ ನಿರ್ಗಮಿಸಿ"</string>
<string name="done_label" msgid="7283767013231718521">"ಮುಗಿದಿದೆ"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"ಗಂಟೆಗಳ ವೃತ್ತಾಕಾರ ಸ್ಲೈಡರ್"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"ನಿಮಿಷಗಳ ವೃತ್ತಾಕಾರ ಸ್ಲೈಡರ್"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 4974f95..7504943 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -596,7 +596,7 @@
<string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"너무 밝음"</string>
<string name="fingerprint_acquired_power_press" msgid="3107864151278434961">"전원 누름이 감지되었습니다."</string>
<string name="fingerprint_acquired_try_adjusting" msgid="3667006071003809364">"조정 시도"</string>
- <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"지문을 등록할 때마다 손가락을 조금씩 이동하세요"</string>
+ <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"지문이 인식될 때마다 손가락을 조금씩 이동하세요"</string>
<string-array name="fingerprint_acquired_vendor">
</string-array>
<string name="fingerprint_error_not_match" msgid="4599441812893438961">"지문이 인식되지 않았습니다."</string>
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"전체 화면 모드"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"종료하려면 위에서 아래로 스와이프합니다."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"확인"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"카메라 미리보기 화면이 잘 보이도록 회전하세요."</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"카메라 미리보기 화면이 잘 보이도록 화면 분할을 종료하세요."</string>
<string name="done_label" msgid="7283767013231718521">"완료"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"시간 원형 슬라이더"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"분 원형 슬라이더"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index cbc29dc..de2cdb7 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Толук экран режими"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Чыгуу үчүн экранды ылдый сүрүп коюңуз."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Түшүндүм"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Жакшыраак көрүү үчүн буруңуз"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Жакшыраак көрүү үчүн экранды бөлүү режиминен чыгыңыз"</string>
<string name="done_label" msgid="7283767013231718521">"Даяр"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Саат жебеси"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Мүнөт жебеси"</string>
@@ -1990,7 +1988,7 @@
<string name="app_category_social" msgid="2278269325488344054">"Социалдык жана коммуникация"</string>
<string name="app_category_news" msgid="1172762719574964544">"Жаңылыктар жана журналдар"</string>
<string name="app_category_maps" msgid="6395725487922533156">"Карталар жана чабыттоо"</string>
- <string name="app_category_productivity" msgid="1844422703029557883">"Өндүрүш категориясы"</string>
+ <string name="app_category_productivity" msgid="1844422703029557883">"Майнаптуулук"</string>
<string name="app_category_accessibility" msgid="6643521607848547683">"Атайын мүмкүнчүлүктөр"</string>
<string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Түзмөктүн сактагычы"</string>
<string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"USB аркылуу мүчүлүштүктөрдү аныктоо"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 09b8bfc..366881b 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"ການເບິ່ງເຕັມໜ້າຈໍ"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"ຫາກຕ້ອງການອອກ, ໃຫ້ຮູດຈາກທາງເທິງລົງມາທາງລຸ່ມ."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"ໄດ້ແລ້ວ"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"ໝຸນເພື່ອມຸມມອງທີ່ດີຂຶ້ນ"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"ອອກຈາກແບ່ງໜ້າຈໍເພື່ອມຸມມອງທີ່ດີຂຶ້ນ"</string>
<string name="done_label" msgid="7283767013231718521">"ແລ້ວໆ"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"ໂຕໝຸນປັບຊົ່ວໂມງ"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"ໂຕໝຸນປັບນາທີ"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 4edf968..cf858b7 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -243,7 +243,7 @@
<string name="global_actions" product="tv" msgid="3871763739487450369">"Android TV opcijas"</string>
<string name="global_actions" product="default" msgid="6410072189971495460">"Tālruņa opcijas"</string>
<string name="global_action_lock" msgid="6949357274257655383">"Ekrāna bloķētājs"</string>
- <string name="global_action_power_off" msgid="4404936470711393203">"Izslēgt strāvas padevi"</string>
+ <string name="global_action_power_off" msgid="4404936470711393203">"Izslēgt"</string>
<string name="global_action_power_options" msgid="1185286119330160073">"Barošana"</string>
<string name="global_action_restart" msgid="4678451019561687074">"Restartēt"</string>
<string name="global_action_emergency" msgid="1387617624177105088">"Ārkārtas situācija"</string>
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Skatīšanās pilnekrāna režīmā"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Lai izietu, no augšdaļas velciet lejup."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Labi"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Lai uzlabotu skatu, pagrieziet ekrānu."</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Lai uzlabotu skatu, izejiet no ekrāna sadalīšanas režīma."</string>
<string name="done_label" msgid="7283767013231718521">"Gatavs"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Stundu apļveida slīdnis"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Minūšu apļveida slīdnis"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index dbbd585..beed8e6 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Се прикажува на цел екран"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"За да излезете, повлечете одозгора надолу."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Сфатив"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Ротирајте за подобар приказ"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"За подобар приказ, излезете од поделениот екран"</string>
<string name="done_label" msgid="7283767013231718521">"Готово"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Приказ на часови во кружно движење"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Приказ на минути во кружно движење"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index b59c322..4ab4c07 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"പൂർണ്ണ സ്ക്രീനിൽ കാണുന്നു"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"അവസാനിപ്പിക്കാൻ, മുകളിൽ നിന്ന് താഴോട്ട് സ്വൈപ്പ് ചെയ്യുക."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"മനസ്സിലായി"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"മികച്ച കാഴ്ചയ്ക്കായി റൊട്ടേറ്റ് ചെയ്യുക"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"മികച്ച കാഴ്ചയ്ക്കായി സ്ക്രീൻ വിഭജന മോഡിൽ നിന്ന് പുറത്തുകടക്കുക"</string>
<string name="done_label" msgid="7283767013231718521">"പൂർത്തിയാക്കി"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"ചാക്രികമായി മണിക്കൂറുകൾ ദൃശ്യമാകുന്ന സ്ലൈഡർ"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"ചാക്രികമായി മിനിറ്റുകൾ ദൃശ്യമാകുന്ന സ്ലൈഡർ"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 029358b..5e98d53 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -303,7 +303,7 @@
<string name="permgrouplab_location" msgid="1858277002233964394">"Байршил"</string>
<string name="permgroupdesc_location" msgid="1995955142118450685">"энэ төхөөрөмжийн байршилд хандалт хийх"</string>
<string name="permgrouplab_calendar" msgid="6426860926123033230">"Календарь"</string>
- <string name="permgroupdesc_calendar" msgid="6762751063361489379">"Календарь руу хандах"</string>
+ <string name="permgroupdesc_calendar" msgid="6762751063361489379">"Календарьд хандах"</string>
<string name="permgrouplab_sms" msgid="795737735126084874">"Мессеж"</string>
<string name="permgroupdesc_sms" msgid="5726462398070064542">"SMS мессежийг илгээх, харах"</string>
<string name="permgrouplab_storage" msgid="17339216290379241">"Файлууд"</string>
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Бүтэн дэлгэцээр үзэж байна"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Гарахаар бол дээрээс нь доош нь чирнэ үү."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Ойлголоо"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Харагдах байдлыг сайжруулах бол эргүүлнэ үү"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Харагдах байдлыг сайжруулах бол дэлгэцийг хуваах горимоос гарна уу"</string>
<string name="done_label" msgid="7283767013231718521">"Дууссан"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Цаг гүйлгэгч"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Минут гүйлгэгч"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index ad22469..9b2f1be 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"पूर्ण स्क्रीनवर पाहत आहात"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"बाहेर पडण्यासाठी, वरून खाली स्वाइप करा."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"समजले"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"अधिक चांगल्या दृश्यासाठी फिरवा"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"अधिक चांगल्या दृश्यासाठी स्प्लिट स्क्रीनमधून बाहेर पडा"</string>
<string name="done_label" msgid="7283767013231718521">"पूर्ण झाले"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"तास परिपत्रक स्लायडर"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"मिनिटे परिपत्रक स्लायडर"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index c038505..fefa9f5 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Melihat skrin penuh"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Untuk keluar, leret dari atas ke bawah."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Faham"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Putar untuk mendapatkan paparan yang lebih baik"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Keluar daripada skrin pisah untuk mendapatkan paparan yang lebih baik"</string>
<string name="done_label" msgid="7283767013231718521">"Selesai"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Penggelangsar bulatan jam"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Penggelangsar bulatan minit"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index badd048..fa13d38 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"မျက်နှာပြင်အပြည့် ကြည့်နေသည်"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"ထွက်ရန် အပေါ်မှ အောက်သို့ ဆွဲချပါ။"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"ရပါပြီ"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"ပိုကောင်းသောမြင်ကွင်းအတွက် လှည့်ပါ"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"ပိုကောင်းသောမြင်ကွင်းအတွက် မျက်နှာပြင် ခွဲ၍ပြသခြင်းမှ ထွက်ပါ"</string>
<string name="done_label" msgid="7283767013231718521">"ပြီးပါပြီ"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"နာရီရွေးချက်စရာ"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"မိနစ်လှည့်သော ရွေ့လျားတန်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 023e48f..aec0089 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Visning i fullskjerm"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Sveip ned fra toppen for å avslutte."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Skjønner"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Roter for å få en bedre visning"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Avslutt delt skjerm for å få en bedre visning"</string>
<string name="done_label" msgid="7283767013231718521">"Ferdig"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Sirkulær glidebryter for timer"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Sirkulær glidebryter for minutter"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index f789ed6..5fa9b6c 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"पूरा पर्दा हेर्दै"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"बाहिर निस्कन, माथिबाट तल स्वाइप गर्नुहोस्।"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"बुझेँ"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"अझ राम्रो दृश्य हेर्न चाहनुहुन्छ भने रोटेट गर्नुहोस्"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"अझ राम्रो दृश्य हेर्न चाहनुहुन्छ भने \"स्प्लिट स्क्रिन\" बाट बाहिरिनुहोस्"</string>
<string name="done_label" msgid="7283767013231718521">"भयो"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"घन्टा गोलाकार स्लाइडर"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"मिनेट गोलाकार स्लाइडर"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 7cc02fe..c2639db 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Volledig scherm wordt getoond"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Swipe omlaag vanaf de bovenkant van het scherm om af te sluiten."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Ik snap het"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Draai voor een betere weergave"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Sluit het gesplitste scherm voor een betere weergave"</string>
<string name="done_label" msgid="7283767013231718521">"Klaar"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Ronde schuifregelaar voor uren"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Ronde schuifregelaar voor minuten"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index b8f017e..8619511 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନରେ ଦେଖାଯାଉଛି"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"ବାହାରିବା ପାଇଁ, ଉପରୁ ତଳକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ।"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"ବୁଝିଗଲି"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"ଏକ ଭଲ ଭ୍ୟୁ ପାଇଁ ରୋଟେଟ କରନ୍ତୁ"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"ଏକ ଭଲ ଭ୍ୟୁ ପାଇଁ ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନରୁ ବାହାରି ଯାଆନ୍ତୁ"</string>
<string name="done_label" msgid="7283767013231718521">"ହୋଇଗଲା"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"ଘଣ୍ଟା ସର୍କୁଲାର୍ ସ୍ଲାଇଡର୍"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"ମିନିଟ୍ସ ସର୍କୁଲାର୍ ସ୍ଲାଇଡର୍"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 66aca4f..57e187b 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"ਪੂਰੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦੇਖੋ"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"ਬਾਹਰ ਜਾਣ ਲਈ, ਉਪਰੋਂ ਹੇਠਾਂ ਸਵਾਈਪ ਕਰੋ।"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"ਸਮਝ ਲਿਆ"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਅਨੁਭਵ ਲਈ ਘੁਮਾਓ"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਅਨੁਭਵ ਲਈ ਸਪਲਿਟ ਸਕ੍ਰੀਨ ਤੋਂ ਬਾਹਰ ਆਓ"</string>
<string name="done_label" msgid="7283767013231718521">"ਹੋ ਗਿਆ"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"ਘੰਟੇ ਸਰਕੁਲਰ ਸਲਾਈਡਰ"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"ਮਿੰਟ ਸਰਕੁਲਰ ਸਲਾਈਡਰ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 61e3217..ae83e68 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1842,10 +1842,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Włączony pełny ekran"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Aby wyjść, przesuń palcem z góry na dół."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Obróć, aby lepiej widzieć"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Zamknij podzielony ekran, aby lepiej widzieć"</string>
<string name="done_label" msgid="7283767013231718521">"Gotowe"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Kołowy suwak godzin"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Kołowy suwak minut"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 91f0a07..524ad37 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Visualização em tela cheia"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize de cima para baixo."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Entendi"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Gire a tela para ter uma visualização melhor"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Saia da tela dividida para ter uma visualização melhor"</string>
<string name="done_label" msgid="7283767013231718521">"Concluído"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Controle deslizante circular das horas"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Controle deslizante circular dos minutos"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 03bc705..c2cf7ab 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Visualização de ecrã inteiro"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize rapidamente para baixo a partir da parte superior."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rode para uma melhor visualização"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Saia do ecrã dividido para uma melhor visualização"</string>
<string name="done_label" msgid="7283767013231718521">"Concluído"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Controlo de deslize circular das horas"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Controlo de deslize circular dos minutos"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 91f0a07..524ad37 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Visualização em tela cheia"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Para sair, deslize de cima para baixo."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Entendi"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Gire a tela para ter uma visualização melhor"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Saia da tela dividida para ter uma visualização melhor"</string>
<string name="done_label" msgid="7283767013231718521">"Concluído"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Controle deslizante circular das horas"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Controle deslizante circular dos minutos"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 38ba48d..301d14c 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1841,10 +1841,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Vizualizare pe ecran complet"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Pentru a ieși, glisează de sus în jos."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Am înțeles"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotește pentru o previzualizare mai bună"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Ieși din ecranul împărțit pentru o previzualizare mai bună"</string>
<string name="done_label" msgid="7283767013231718521">"Terminat"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Selector circular pentru ore"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Selector circular pentru minute"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 93cbae2..cf3677e 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1842,10 +1842,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Полноэкранный режим"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Чтобы выйти, проведите по экрану сверху вниз."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"ОК"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Поверните, чтобы лучше видеть."</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Выйдите из режима разделения экрана, чтобы лучше видеть."</string>
<string name="done_label" msgid="7283767013231718521">"Готово"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Выбор часов на циферблате"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Выбор минут на циферблате"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index d79d0b8..69d806a 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"මුළු තිරය බලමින්"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"ඉවත් වීමට, ඉහළ සිට පහළට ස්වයිප් කරන්න"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"වැටහුණි"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"වඩා හොඳ දසුනක් සඳහා කරකවන්න"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"වඩා හොඳ දර්ශනයක් සඳහා බෙදුම් තිරයෙන් පිටවන්න"</string>
<string name="done_label" msgid="7283767013231718521">"අවසන්"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"පැය කවාකාර සර්පනය"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"මිනිත්තු කවාකාර සර්පනය"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 092827f..6bbfe0a 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1842,10 +1842,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Zobrazenie na celú obrazovku"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Ukončíte potiahnutím zhora nadol."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Dobre"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Otočte zariadenie pre lepšie zobrazenie"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Ukončite rozdelenú obrazovku pre lepšie zobrazenie"</string>
<string name="done_label" msgid="7283767013231718521">"Hotovo"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Kruhový posúvač hodín"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Kruhový posúvač minút"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index f557823..1accd18 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1842,10 +1842,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Vklopljen je celozaslonski način"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Zaprete ga tako, da z vrha s prstom povlečete navzdol."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Razumem"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Zasukajte za boljši pregled."</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Zaprite razdeljeni zaslon za boljši pregled."</string>
<string name="done_label" msgid="7283767013231718521">"Dokončano"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Okrogli drsnik za ure"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Okrogli drsnik za minute"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 7875ea2..a8fc00e 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Po shikon ekranin e plotë"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Për të dalë, rrëshqit nga lart poshtë."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"E kuptova"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rrotullo për një pamje më të mirë"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Dil nga ekrani i ndarë për një pamje më të mirë"</string>
<string name="done_label" msgid="7283767013231718521">"U krye"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Rrëshqitësi rrethor i orëve"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Rrëshqitësi rrethor i minutave"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 7e920d8..177615d 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Visar på fullskärm"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Svep nedåt från skärmens överkant för att avsluta."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Rotera för att få en bättre vy"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Stäng delad skärm för att få en bättre vy"</string>
<string name="done_label" msgid="7283767013231718521">"Klart"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Cirkelreglage för timmar"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Cirkelreglage för minuter"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index d380436..1319d1b 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Unatazama skrini nzima"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Ili kuondoka, telezesha kidole kutoka juu hadi chini."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Nimeelewa"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Zungusha ili upate mwonekano bora"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Funga skrini iliyogawanywa ili upate mwonekano bora"</string>
<string name="done_label" msgid="7283767013231718521">"Imekamilika"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Kitelezi cha mviringo wa saa"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Kitelezi cha mviringo wa dakika"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index aee945d..6b53c8e 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"முழுத் திரையில் காட்டுகிறது"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"வெளியேற, மேலிருந்து கீழே ஸ்வைப் செய்யவும்"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"புரிந்தது"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"சிறந்த காட்சிக்கு சுழற்றுங்கள்"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"சிறந்த காட்சிக்கு திரைப் பிரிப்புப் பயன்முறையில் இருந்து வெளியேறுங்கள்"</string>
<string name="done_label" msgid="7283767013231718521">"முடிந்தது"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"மணிநேர வட்ட வடிவ ஸ்லைடர்"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"நிமிடங்களுக்கான வட்டவடிவ ஸ்லைடர்"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 7d6b3cd..cff6773 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"ఫుల్-స్క్రీన్లో వీక్షిస్తున్నారు"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"నిష్క్రమించడానికి, పై నుండి క్రిందికి స్వైప్ చేయండి."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"అర్థమైంది"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"మెరుగైన వీక్షణ కోసం తిప్పండి"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"మెరుగైన వీక్షణ కోసం స్ప్లిట్ స్క్రీన్ నుండి నిష్క్రమించండి"</string>
<string name="done_label" msgid="7283767013231718521">"పూర్తయింది"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"గంటల వృత్తాకార స్లయిడర్"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"నిమిషాల వృత్తాకార స్లయిడర్"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 852aef0..10ee520 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"กำลังดูแบบเต็มหน้าจอ"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"หากต้องการออก ให้เลื่อนลงจากด้านบน"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"รับทราบ"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"หมุนเพื่อรับมุมมองที่ดียิ่งขึ้น"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"ออกจากโหมดแยกหน้าจอเพื่อรับมุมมองที่ดียิ่งขึ้น"</string>
<string name="done_label" msgid="7283767013231718521">"เสร็จสิ้น"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"ตัวเลื่อนหมุนระบุชั่วโมง"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"ตัวเลื่อนหมุนระบุนาที"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 7540fcd..f4658cf 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Panonood sa full screen"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Upang lumabas, mag-swipe mula sa itaas pababa."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Nakuha ko"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"I-rotate para sa mas magandang view"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Lumabas sa split screen para sa mas magandang view"</string>
<string name="done_label" msgid="7283767013231718521">"Tapos na"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Pabilog na slider ng mga oras"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Pabilog na slider ng mga minuto"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 10501d6..0573fcd 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Tam ekran olarak görüntüleme"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Çıkmak için yukarıdan aşağıya doğru hızlıca kaydırın."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Anladım"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Daha iyi bir görünüm elde etmek için ekranı döndürün"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Daha iyi bir görünüm elde etmek için bölünmüş ekrandan çıkın"</string>
<string name="done_label" msgid="7283767013231718521">"Bitti"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Saat kaydırma çemberi"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Dakika kaydırma çemberi"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index eecc6e2..ad03757 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1842,10 +1842,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Перегляд на весь екран"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Щоб вийти, проведіть пальцем зверху вниз."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Оберніть для кращого огляду"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Для кращого огляду вийдіть із режиму розділення екрана"</string>
<string name="done_label" msgid="7283767013231718521">"Готово"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Вибір годин на циферблаті"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Вибір хвилин на циферблаті"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index feea70c..4e03774 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"پوری اسکرین میں دیکھ رہے ہیں"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"خارج ہونے کیلئے اوپر سے نیچے سوائپ کریں۔"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"سمجھ آ گئی"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"بہتر منظر کے لیے گھمائیں"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"بہتر منظر کے لیے اسپلٹ اسکرین سے باہر نکلیں"</string>
<string name="done_label" msgid="7283767013231718521">"ہو گیا"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"گھنٹوں کا سرکلر سلائیڈر"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"منٹس سرکلر سلائیڈر"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 05acca6..f30df3b 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Xem toàn màn hình"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Để thoát, hãy vuốt từ trên cùng xuống dưới."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"OK"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Xoay để xem dễ hơn"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Thoát chế độ chia đôi màn hình để xem dễ hơn"</string>
<string name="done_label" msgid="7283767013231718521">"Xong"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Thanh trượt giờ hình tròn"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Thanh trượt phút hình tròn"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 604e09a..60fae7e 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"目前处于全屏模式"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"要退出,请从顶部向下滑动。"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"知道了"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"旋转可改善预览效果"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"退出分屏可改善预览效果"</string>
<string name="done_label" msgid="7283767013231718521">"完成"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"小时转盘"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"分钟转盘"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 85124a4..ed5e989 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"開啟全螢幕"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"由頂部向下滑動即可退出。"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"知道了"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"旋轉以改善預覽效果"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"退出分割螢幕,以改善預覽效果"</string>
<string name="done_label" msgid="7283767013231718521">"完成"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"小時環形滑桿"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"分鐘環形滑桿"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 9207ce2..08b597a 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"以全螢幕檢視"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"如要退出,請從畫面頂端向下滑動。"</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"知道了"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"旋轉螢幕以瀏覽完整的檢視畫面"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"結束分割畫面以全螢幕瀏覽"</string>
<string name="done_label" msgid="7283767013231718521">"完成"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"小時數環狀滑桿"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"分鐘數環狀滑桿"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 5d5d14c..a8cdb4e 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1840,10 +1840,8 @@
<string name="immersive_cling_title" msgid="2307034298721541791">"Ukubuka isikrini esigcwele"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Ukuze uphume, swayiphela phansi kusuka phezulu."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Ngiyitholile"</string>
- <!-- no translation found for display_rotation_camera_compat_toast_after_rotation (7600891546249829854) -->
- <skip />
- <!-- no translation found for display_rotation_camera_compat_toast_in_split_screen (8393302456336805466) -->
- <skip />
+ <string name="display_rotation_camera_compat_toast_after_rotation" msgid="7600891546249829854">"Zungezisa ukuze uthole ukubuka okungcono"</string>
+ <string name="display_rotation_camera_compat_toast_in_split_screen" msgid="8393302456336805466">"Phuma ekuhlukaniseni isikrini ukuze ubuke kangcono"</string>
<string name="done_label" msgid="7283767013231718521">"Kwenziwe"</string>
<string name="hour_picker_description" msgid="5153757582093524635">"Amahora weslayidi esiyindingilizi"</string>
<string name="minute_picker_description" msgid="9029797023621927294">"Amaminithi weslayidi esiyindingilizi"</string>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index d5875f5..b83d3b4e 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -150,6 +150,8 @@
<color name="notification_default_color">#757575</color> <!-- Gray 600 -->
<color name="notification_action_button_text_color">@color/notification_default_color</color>
+ <item name="notification_action_disabled_content_alpha" format="float" type="dimen">0.38</item>
+ <item name="notification_action_disabled_container_alpha" format="float" type="dimen">0.12</item>
<color name="notification_progress_background_color">@color/notification_secondary_text_color_current</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 87ece55..dffd1cc 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6083,4 +6083,7 @@
<!-- Whether the lock screen is allowed to run its own live wallpaper,
different from the home screen wallpaper. -->
<bool name="config_independentLockscreenLiveWallpaper">false</bool>
+
+ <!-- Whether to show weather on the lock screen by default. -->
+ <bool name="config_lockscreenWeatherEnabledByDefault">false</bool>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ba54183..2091c05 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -975,6 +975,11 @@
<!-- Description for the capability of an accessibility service to take screenshot. [CHAR LIMIT=NONE] -->
<string name="capability_desc_canTakeScreenshot">Can take a screenshot of the display.</string>
+ <!-- Dream -->
+
+ <!-- The title to use when a dream is opened in preview mode. [CHAR LIMIT=NONE] -->
+ <string name="dream_preview_title">Preview, <xliff:g id="dream_name" example="Clock">%1$s</xliff:g></string>
+
<!-- Permissions -->
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 15c40c0..9cc8aa8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3304,7 +3304,10 @@
<java-symbol type="id" name="notification_action_list_margin_target" />
<java-symbol type="dimen" name="notification_actions_padding_start"/>
<java-symbol type="dimen" name="notification_actions_collapsed_priority_width"/>
+ <!--prefer to use disabled content and surface alpha values for disabled actions-->
<java-symbol type="dimen" name="notification_action_disabled_alpha" />
+ <java-symbol type="dimen" name="notification_action_disabled_content_alpha" />
+ <java-symbol type="dimen" name="notification_action_disabled_container_alpha" />
<java-symbol type="id" name="tag_margin_end_when_icon_visible" />
<java-symbol type="id" name="tag_margin_end_when_icon_gone" />
<java-symbol type="id" name="tag_uses_right_icon_drawable" />
@@ -3356,6 +3359,7 @@
<java-symbol type="string" name="unsupported_display_size_message" />
<java-symbol type="layout" name="notification_material_action_emphasized" />
+ <java-symbol type="layout" name="notification_material_action_emphasized_tombstone" />
<!-- Package name for the device provisioning package -->
<java-symbol type="string" name="config_deviceProvisioningPackage" />
@@ -4281,6 +4285,8 @@
<java-symbol type="string" name="capability_desc_canTakeScreenshot" />
<java-symbol type="string" name="capability_title_canTakeScreenshot" />
+ <java-symbol type="string" name="dream_preview_title" />
+
<java-symbol type="string" name="config_servicesExtensionPackage" />
<!-- For app process exit info tracking -->
@@ -4892,4 +4898,7 @@
<java-symbol type="id" name="language_picker_header" />
<java-symbol type="dimen" name="status_bar_height_default" />
+
+ <!-- Whether to show weather on the lockscreen by default. -->
+ <java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" />
</resources>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3e4b1cc..e96c642 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1414,6 +1414,7 @@
<activity android:name="com.android.internal.app.ChooserWrapperActivity"/>
<activity android:name="com.android.internal.app.ResolverWrapperActivity"/>
<activity android:name="com.android.internal.app.IntentForwarderActivityTest$IntentForwarderWrapperActivity"/>
+ <activity android:name="com.android.internal.accessibility.AccessibilityShortcutChooserActivityTest$TestAccessibilityShortcutChooserActivity"/>
<receiver android:name="android.app.activity.AbortReceiver"
android:exported="true">
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
new file mode 100644
index 0000000..973b904
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.accessibility;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.doubleClick;
+import static androidx.test.espresso.action.ViewActions.scrollTo;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.RootMatchers.isDialog;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.endsWith;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Collections;
+
+/**
+ * Tests for {@link AccessibilityShortcutChooserActivity}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityShortcutChooserActivityTest {
+ private static final String ONE_HANDED_MODE = "One-Handed mode";
+ private static final String TEST_LABEL = "TEST_LABEL";
+ private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("package", "class");
+
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Mock
+ private AccessibilityServiceInfo mAccessibilityServiceInfo;
+ @Mock
+ private ResolveInfo mResolveInfo;
+ @Mock
+ private ServiceInfo mServiceInfo;
+ @Mock
+ private ApplicationInfo mApplicationInfo;
+ @Mock
+ private IAccessibilityManager mAccessibilityManagerService;
+
+ @Test
+ public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist()
+ throws Exception {
+ configureTestService();
+ final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
+ ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+ scenario.moveToState(Lifecycle.State.CREATED);
+ scenario.moveToState(Lifecycle.State.STARTED);
+ scenario.moveToState(Lifecycle.State.RESUMED);
+
+ onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
+ isDialog()).check(matches(isDisplayed()));
+ onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
+ onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick());
+ onView(withId(R.id.accessibility_permission_enable_deny_button)).perform(scrollTo(),
+ click());
+
+ onView(withId(R.id.accessibility_permissionDialog_title)).inRoot(isDialog()).check(
+ doesNotExist());
+ scenario.moveToState(Lifecycle.State.DESTROYED);
+ }
+
+ @Test
+ public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() {
+ TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
+ final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
+ ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+ scenario.moveToState(Lifecycle.State.CREATED);
+ scenario.moveToState(Lifecycle.State.STARTED);
+ scenario.moveToState(Lifecycle.State.RESUMED);
+
+ onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
+ isDialog()).check(matches(isDisplayed()));
+ onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
+ onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(matches(isDisplayed()));
+ scenario.moveToState(Lifecycle.State.DESTROYED);
+ }
+
+ @Test
+ public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() {
+ TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false);
+ final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
+ ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+ scenario.moveToState(Lifecycle.State.CREATED);
+ scenario.moveToState(Lifecycle.State.STARTED);
+ scenario.moveToState(Lifecycle.State.RESUMED);
+
+ onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
+ isDialog()).check(matches(isDisplayed()));
+ onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
+ onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist());
+ scenario.moveToState(Lifecycle.State.DESTROYED);
+ }
+
+ private void configureTestService() throws Exception {
+ when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
+ mResolveInfo.serviceInfo = mServiceInfo;
+ mServiceInfo.applicationInfo = mApplicationInfo;
+ when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
+ when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
+ when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
+ anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo));
+
+ TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
+ }
+
+ /**
+ * Used for testing.
+ */
+ public static class TestAccessibilityShortcutChooserActivity extends
+ AccessibilityShortcutChooserActivity {
+ private static IAccessibilityManager sAccessibilityManagerService;
+
+ public static void setupForTesting(IAccessibilityManager accessibilityManagerService) {
+ sAccessibilityManagerService = accessibilityManagerService;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (Context.ACCESSIBILITY_SERVICE.equals(name)
+ && sAccessibilityManagerService != null) {
+ return new AccessibilityManager(this, new Handler(getMainLooper()),
+ sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true);
+ }
+
+ return super.getSystemService(name);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 6baf305..c92ae2c 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -21,6 +21,11 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -203,6 +208,17 @@
when(mAlertDialog.getWindow()).thenReturn(window);
when(mTextToSpeech.getVoice()).thenReturn(mVoice);
+
+ // Clears the sFrameworkShortcutFeaturesMap field which was not properly initialized
+ // during testing.
+ try {
+ Field field = AccessibilityShortcutController.class.getDeclaredField(
+ "sFrameworkShortcutFeaturesMap");
+ field.setAccessible(true);
+ field.set(window, null);
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to set sFrameworkShortcutFeaturesMap", e);
+ }
}
@AfterClass
@@ -428,11 +444,10 @@
}
@Test
- public void getFrameworkFeatureMap_shouldBeNonNullAndUnmodifiable() {
- Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo>
+ public void getFrameworkFeatureMap_shouldBeUnmodifiable() {
+ final Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo>
frameworkFeatureMap =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
- assertTrue("Framework features not supported", frameworkFeatureMap.size() > 0);
try {
frameworkFeatureMap.clear();
@@ -443,6 +458,39 @@
}
@Test
+ public void getFrameworkFeatureMap_containsExpectedDefaultKeys() {
+ final Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo>
+ frameworkFeatureMap =
+ AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
+
+ assertTrue(frameworkFeatureMap.containsKey(COLOR_INVERSION_COMPONENT_NAME));
+ assertTrue(frameworkFeatureMap.containsKey(DALTONIZER_COMPONENT_NAME));
+ assertTrue(frameworkFeatureMap.containsKey(REDUCE_BRIGHT_COLORS_COMPONENT_NAME));
+ }
+
+ @Test
+ public void getFrameworkFeatureMap_oneHandedModeEnabled_containsExpectedKey() {
+ TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
+
+ final Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo>
+ frameworkFeatureMap =
+ AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
+
+ assertTrue(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME));
+ }
+
+ @Test
+ public void getFrameworkFeatureMap_oneHandedModeDisabled_containsExpectedKey() {
+ TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false);
+
+ final Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo>
+ frameworkFeatureMap =
+ AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
+
+ assertFalse(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME));
+ }
+
+ @Test
public void testOnAccessibilityShortcut_forServiceWithNoSummary_doesNotCrash()
throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java b/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java
new file mode 100644
index 0000000..ff014ad
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.accessibility;
+
+import com.android.internal.os.RoSystemProperties;
+
+import java.lang.reflect.Field;
+
+/**
+ * Test utility methods.
+ */
+public class TestUtils {
+
+ /**
+ * Sets the {@code enabled} of the given OneHandedMode flags to simulate device behavior.
+ */
+ public static void setOneHandedModeEnabled(Object obj, boolean enabled) {
+ try {
+ final Field field = RoSystemProperties.class.getDeclaredField(
+ "SUPPORT_ONE_HANDED_MODE");
+ field.setAccessible(true);
+ field.setBoolean(obj, enabled);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/config/sysui/OWNERS b/core/tests/coretests/src/com/android/internal/config/sysui/OWNERS
new file mode 100644
index 0000000..2e96c97
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/config/sysui/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/config/sysui/SystemUiSystemPropertiesFlagsTest.java b/core/tests/coretests/src/com/android/internal/config/sysui/SystemUiSystemPropertiesFlagsTest.java
new file mode 100644
index 0000000..6b9d39c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/config/sysui/SystemUiSystemPropertiesFlagsTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.config.sysui;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver;
+
+import junit.framework.TestCase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@SmallTest
+public class SystemUiSystemPropertiesFlagsTest extends TestCase {
+
+ public class TestableDebugResolver extends SystemUiSystemPropertiesFlags.DebugResolver {
+ final Map<String, Boolean> mTestData = new HashMap<>();
+
+ @Override
+ public boolean getBoolean(String key, boolean defaultValue) {
+ Boolean testValue = mTestData.get(key);
+ return testValue == null ? defaultValue : testValue;
+ }
+
+ public void set(Flag flag, Boolean value) {
+ mTestData.put(flag.mSysPropKey, value);
+ }
+ }
+
+ private FlagResolver mProdResolver;
+ private TestableDebugResolver mDebugResolver;
+
+ private Flag mReleasedFlag;
+ private Flag mTeamfoodFlag;
+ private Flag mDevFlag;
+
+ public void setUp() {
+ mProdResolver = new SystemUiSystemPropertiesFlags.ProdResolver();
+ mDebugResolver = new TestableDebugResolver();
+ mReleasedFlag = SystemUiSystemPropertiesFlags.releasedFlag("mReleasedFlag");
+ mTeamfoodFlag = SystemUiSystemPropertiesFlags.teamfoodFlag("mTeamfoodFlag");
+ mDevFlag = SystemUiSystemPropertiesFlags.devFlag("mDevFlag");
+ }
+
+ public void tearDown() {
+ SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+ }
+
+ public void testProdResolverReturnsDefault() {
+ assertThat(mProdResolver.isEnabled(mReleasedFlag)).isTrue();
+ assertThat(mProdResolver.isEnabled(mTeamfoodFlag)).isFalse();
+ assertThat(mProdResolver.isEnabled(mDevFlag)).isFalse();
+ }
+
+ public void testDebugResolverAndReleasedFlag() {
+ assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isTrue();
+
+ mDebugResolver.set(mReleasedFlag, false);
+ assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isFalse();
+
+ mDebugResolver.set(mReleasedFlag, true);
+ assertThat(mDebugResolver.isEnabled(mReleasedFlag)).isTrue();
+ }
+
+ private void assertTeamfoodFlag(Boolean flagValue, Boolean teamfood, boolean expected) {
+ mDebugResolver.set(mTeamfoodFlag, flagValue);
+ mDebugResolver.set(SystemUiSystemPropertiesFlags.TEAMFOOD, teamfood);
+ assertThat(mDebugResolver.isEnabled(mTeamfoodFlag)).isEqualTo(expected);
+ }
+
+ public void testDebugResolverAndTeamfoodFlag() {
+ assertTeamfoodFlag(null, null, false);
+ assertTeamfoodFlag(true, null, true);
+ assertTeamfoodFlag(false, null, false);
+ assertTeamfoodFlag(null, true, true);
+ assertTeamfoodFlag(true, true, true);
+ assertTeamfoodFlag(false, true, false);
+ assertTeamfoodFlag(null, false, false);
+ assertTeamfoodFlag(true, false, true);
+ assertTeamfoodFlag(false, false, false);
+ }
+
+ public void testDebugResolverAndDevFlag() {
+ assertThat(mDebugResolver.isEnabled(mDevFlag)).isFalse();
+
+ mDebugResolver.set(mDevFlag, true);
+ assertThat(mDebugResolver.isEnabled(mDevFlag)).isTrue();
+
+ mDebugResolver.set(mDevFlag, false);
+ assertThat(mDebugResolver.isEnabled(mDevFlag)).isFalse();
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 52feac5..4c9b2b7 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -360,6 +360,7 @@
// map of ActivityManager process states and how long to simulate run time in each state
Map<Integer, Integer> stateRuntimeMap = new HashMap<Integer, Integer>();
stateRuntimeMap.put(ActivityManager.PROCESS_STATE_TOP, 1111);
+ stateRuntimeMap.put(ActivityManager.PROCESS_STATE_BOUND_TOP, 7382);
stateRuntimeMap.put(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 1234);
stateRuntimeMap.put(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 2468);
stateRuntimeMap.put(ActivityManager.PROCESS_STATE_TOP_SLEEPING, 7531);
@@ -396,7 +397,8 @@
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
elapsedTimeUs, STATS_SINCE_CHARGED);
- expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE)
+ + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING,
@@ -406,8 +408,7 @@
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND,
elapsedTimeUs, STATS_SINCE_CHARGED);
- expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
- + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+ expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
@@ -415,7 +416,8 @@
expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BACKUP)
+ stateRuntimeMap.get(ActivityManager.PROCESS_STATE_SERVICE)
- + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
+ + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER)
+ + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_TOP);
assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_CACHED,
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 354b937..2742861 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
@@ -78,9 +78,9 @@
batteryUsageStats.getUidBatteryConsumers();
final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND))
- .isEqualTo(60 * MINUTE_IN_MS);
+ .isEqualTo(20 * MINUTE_IN_MS);
assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND))
- .isEqualTo(10 * MINUTE_IN_MS);
+ .isEqualTo(40 * MINUTE_IN_MS);
assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
.isWithin(PRECISION).of(2.0);
assertThat(
@@ -121,22 +121,44 @@
private BatteryStatsImpl prepareBatteryStats() {
BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
- batteryStats.noteActivityResumedLocked(APP_UID,
- 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
- batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_TOP,
- 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
- batteryStats.noteActivityPausedLocked(APP_UID,
- 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
- batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_SERVICE,
- 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
- batteryStats.noteUidProcessStateLocked(APP_UID,
- ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
- 40 * MINUTE_IN_MS, 40 * MINUTE_IN_MS);
- batteryStats.noteUidProcessStateLocked(APP_UID,
- ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
- 50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
- batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_CACHED_EMPTY,
- 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+ mStatsRule.setTime(10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteActivityResumedLocked(APP_UID);
+ }
+
+ mStatsRule.setTime(10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_TOP);
+ }
+ mStatsRule.setTime(30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteActivityPausedLocked(APP_UID);
+ }
+ mStatsRule.setTime(30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID,
+ ActivityManager.PROCESS_STATE_SERVICE);
+ }
+ mStatsRule.setTime(40 * MINUTE_IN_MS, 40 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID,
+ ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ }
+ mStatsRule.setTime(50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID,
+ ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+ }
+ mStatsRule.setTime(60 * MINUTE_IN_MS, 60 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID,
+ ActivityManager.PROCESS_STATE_BOUND_TOP);
+ }
+ mStatsRule.setTime(70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
+ synchronized (batteryStats) {
+ batteryStats.noteUidProcessStateLocked(APP_UID,
+ ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ }
batteryStats.noteFlashlightOnLocked(APP_UID, 1000, 1000);
batteryStats.noteFlashlightOffLocked(APP_UID, 5000, 5000);
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index e0e13f5..6dcee6d 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -49,6 +49,7 @@
<permission name="android.permission.READ_FRAME_BUFFER"/>
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
<permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 58a2073..caa118a 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -306,6 +306,7 @@
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<!-- Permission required for UiModeManager CTS test -->
<permission name="android.permission.READ_PROJECTION_STATE"/>
+ <permission name="android.permission.READ_WALLPAPER_INTERNAL"/>
<permission name="android.permission.READ_WIFI_CREDENTIAL"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
index fa173072..d39d4b4 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -412,11 +412,11 @@
private static ParsedRequiresPermission parseRequiresPermissionRecursively(
MethodInvocationTree tree, VisitorState state) {
- if (ENFORCE_VIA_CONTEXT.matches(tree, state)) {
+ if (ENFORCE_VIA_CONTEXT.matches(tree, state) && tree.getArguments().size() > 0) {
final ParsedRequiresPermission res = new ParsedRequiresPermission();
res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(0))));
return res;
- } else if (ENFORCE_VIA_CHECKER.matches(tree, state)) {
+ } else if (ENFORCE_VIA_CHECKER.matches(tree, state) && tree.getArguments().size() > 1) {
final ParsedRequiresPermission res = new ParsedRequiresPermission();
res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(1))));
return res;
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
index 388988e..38831b1 100644
--- a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
@@ -415,4 +415,27 @@
"}")
.doTest();
}
+
+ @Test
+ public void testInvalidFunctions() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/annotation/SuppressLint.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceLines("Example.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.annotation.SuppressLint;",
+ "import android.content.Context;",
+ "class Foo extends Context {",
+ " private static final String RED = \"red\";",
+ " public void checkPermission() {",
+ " }",
+ " @RequiresPermission(RED)",
+ " // BUG: Diagnostic contains:",
+ " public void exampleScoped(Context context) {",
+ " checkPermission();",
+ " }",
+ "}")
+ .doTest();
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 87c2822..ff5f256 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -252,4 +252,37 @@
}
}
}
+
+ @Override
+ public void addRearDisplayPresentationStatusListener(
+ @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {
+ throw new UnsupportedOperationException(
+ "addRearDisplayPresentationStatusListener is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void removeRearDisplayPresentationStatusListener(
+ @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {
+ throw new UnsupportedOperationException(
+ "removeRearDisplayPresentationStatusListener is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void startRearDisplayPresentationSession(@NonNull Activity activity,
+ @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {
+ throw new UnsupportedOperationException(
+ "startRearDisplayPresentationSession is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void endRearDisplayPresentationSession() {
+ throw new UnsupportedOperationException(
+ "endRearDisplayPresentationSession is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public ExtensionWindowAreaPresentation getRearDisplayPresentation() {
+ throw new UnsupportedOperationException(
+ "getRearDisplayPresentation is not supported in API_VERSION=2");
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 8b3a471..569eb80 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -2156,4 +2156,30 @@
return configuration != null
&& configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
}
+
+ @Override
+ public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options,
+ @NonNull IBinder token) {
+ throw new UnsupportedOperationException(
+ "setLaunchingActivityStack is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void finishActivityStacks(@NonNull Set<IBinder> activityStackTokens) {
+ throw new UnsupportedOperationException(
+ "finishActivityStacks is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void invalidateTopVisibleSplitAttributes() {
+ throw new UnsupportedOperationException(
+ "invalidateTopVisibleSplitAttributes is not supported in API_VERSION=2");
+ }
+
+ @Override
+ public void updateSplitAttributes(@NonNull IBinder splitInfoToken,
+ @NonNull SplitAttributes splitAttributes) {
+ throw new UnsupportedOperationException(
+ "updateSplitAttributes is not supported in API_VERSION=2");
+ }
}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 7eee6da..6852320 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/drawable/caption_close_button.xml b/libs/WindowManager/Shell/res/drawable/caption_close_button.xml
new file mode 100644
index 0000000..e258564
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_close_button.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateY="4.0">
+ <path
+ android:fillColor="#FFFF0000"
+ android:pathData="M12.45,38.35 L9.65,35.55 21.2,24 9.65,12.45 12.45,9.65 24,21.2 35.55,9.65 38.35,12.45 26.8,24 38.35,35.55 35.55,38.35 24,26.8Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml b/libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml
new file mode 100644
index 0000000..166552d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_collapse_menu_button.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+>
+ <group android:scaleX="1.25"
+ android:scaleY="1.75"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M10.3937 6.93935L11.3337 5.99935L6.00033 0.666016L0.666992 5.99935L1.60699 6.93935L6.00033 2.55268"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml b/libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml
new file mode 100644
index 0000000..7c86888
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_screenshot_button.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateY="4.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M10,38V28.35H13V35H19.65V38ZM10,19.65V10H19.65V13H13V19.65ZM28.35,38V35H35V28.35H38V38ZM35,19.65V13H28.35V10H38V19.65Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_select_button.xml b/libs/WindowManager/Shell/res/drawable/caption_select_button.xml
new file mode 100644
index 0000000..8c60c84
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_select_button.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group
+ android:translateX="4.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M13.7021 12.5833L16.5676 15.5L15.426 16.7333L12.526 13.8333L10.4426 15.9167V10.5H15.9176L13.7021 12.5833ZM13.8343 3.83333H15.501V5.5H13.8343V3.83333ZM15.501 2.16667H13.8343V0.566667C14.751 0.566667 15.501 1.33333 15.501 2.16667ZM10.501 0.5H12.1676V2.16667H10.501V0.5ZM13.8343 7.16667H15.501V8.83333H13.8343V7.16667ZM5.50098 15.5H3.83431V13.8333H5.50098V15.5ZM2.16764 5.5H0.500977V3.83333H2.16764V5.5ZM2.16764 0.566667V2.16667H0.500977C0.500977 1.33333 1.33431 0.566667 2.16764 0.566667ZM2.16764 12.1667H0.500977V10.5H2.16764V12.1667ZM5.50098 2.16667H3.83431V0.5H5.50098V2.16667ZM8.83431 2.16667H7.16764V0.5H8.83431V2.16667ZM8.83431 15.5H7.16764V13.8333H8.83431V15.5ZM2.16764 8.83333H0.500977V7.16667H2.16764V8.83333ZM2.16764 15.5667C1.25098 15.5667 0.500977 14.6667 0.500977 13.8333H2.16764V15.5667Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index c9f2623..27e0b18 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -17,9 +17,10 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:tint="@color/decor_button_dark_color">
<group android:translateY="8.0">
<path
- android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+ android:fillColor="@android:color/white" android:pathData="M3,5V3H21V5Z"/>
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
index 416287d..9167382 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
@@ -18,4 +18,5 @@
xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/white" />
<corners android:radius="20dp" />
+ <stroke android:width="1dp" android:color="#b3b3b3"/>
</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
index 53a8bb1..ef30060 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
@@ -15,6 +15,8 @@
~ limitations under the License.
-->
<shape android:shape="rectangle"
+ android:tintMode="multiply"
+ android:tint="@color/decor_title_color"
xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/white" />
</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index 2994593..b3f8e801 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -25,12 +25,10 @@
android:fillAlpha="0.8"
android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
<group
- android:scaleX="0.8"
- android:scaleY="0.8"
- android:translateX="10"
- android:translateY="10">
+ android:translateX="12"
+ android:translateY="12">
<path
- android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
- android:fillColor="@color/compat_controls_text"/>
+ android:fillColor="@color/compat_controls_text"
+ android:pathData="M3,21V15H5V17.6L8.1,14.5L9.5,15.9L6.4,19H9V21ZM15,21V19H17.6L14.5,15.9L15.9,14.5L19,17.6V15H21V21ZM8.1,9.5 L5,6.4V9H3V3H9V5H6.4L9.5,8.1ZM15.9,9.5 L14.5,8.1 17.6,5H15V3H21V9H19V6.4Z"/>
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
index 8b4792a..f6e3f2e 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
@@ -1,49 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
- <!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
-xmlns:android="http://schemas.android.com/apk/res/android"
-android:id="@+id/handle_menu"
-android:layout_width="wrap_content"
-android:layout_height="wrap_content"
-android:gravity="center_horizontal"
-android:background="@drawable/desktop_mode_decor_menu_background">
- <Button
- style="@style/CaptionButtonStyle"
- android:id="@+id/fullscreen_button"
- android:contentDescription="@string/fullscreen_text"
- android:background="@drawable/caption_fullscreen_button"/>
- <Button
- style="@style/CaptionButtonStyle"
- android:id="@+id/split_screen_button"
- android:contentDescription="@string/split_screen_text"
- android:background="@drawable/caption_split_screen_button"/>
- <Button
- style="@style/CaptionButtonStyle"
- android:id="@+id/floating_button"
- android:contentDescription="@string/float_button_text"
- android:background="@drawable/caption_floating_button"/>
- <Button
- style="@style/CaptionButtonStyle"
- android:id="@+id/desktop_button"
- android:contentDescription="@string/desktop_text"
- android:background="@drawable/caption_desktop_button"/>
- <Button
- style="@style/CaptionButtonStyle"
- android:id="@+id/more_button"
- android:contentDescription="@string/more_button_text"
- android:background="@drawable/caption_more_button"/>
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/handle_menu"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@drawable/desktop_mode_decor_menu_background"
+ android:elevation="@dimen/caption_menu_elevation"
+ android:divider="?android:attr/dividerHorizontal"
+ android:showDividers="middle"
+ android:dividerPadding="18dip">
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <ImageView
+ android:id="@+id/application_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_margin="12dp"
+ android:contentDescription="@string/app_icon_text"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"/>
+ <TextView
+ android:id="@+id/application_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@+id/application_icon"
+ android:layout_toStartOf="@+id/collapse_menu_button"
+ android:textColor="#FF000000"
+ android:layout_centerVertical="true"/>
+ <Button
+ android:id="@+id/collapse_menu_button"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginEnd="10dp"
+ android:contentDescription="@string/collapse_menu_text"
+ android:layout_alignParentEnd="true"
+ android:background="@drawable/caption_collapse_menu_button"
+ android:layout_centerVertical="true"/>
+ </RelativeLayout>
+ <LinearLayout
+ android:id="@+id/windowing_mode_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal">
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="0.5" />
+ <Button
+ style="@style/CaptionWindowingButtonStyle"
+ android:id="@+id/fullscreen_button"
+ android:contentDescription="@string/fullscreen_text"
+ android:background="@drawable/caption_fullscreen_button"/>
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" />
+ <Button
+ style="@style/CaptionWindowingButtonStyle"
+ android:id="@+id/split_screen_button"
+ android:contentDescription="@string/split_screen_text"
+ android:background="@drawable/caption_split_screen_button"/>
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" />
+ <Button
+ style="@style/CaptionWindowingButtonStyle"
+ android:id="@+id/floating_button"
+ android:contentDescription="@string/float_button_text"
+ android:background="@drawable/caption_floating_button"/>
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1" />
+ <Button
+ style="@style/CaptionWindowingButtonStyle"
+ android:id="@+id/desktop_button"
+ android:contentDescription="@string/desktop_text"
+ android:background="@drawable/caption_desktop_button"/>
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="0.5" />
+
+ </LinearLayout>
+ <LinearLayout
+ android:id="@+id/menu_buttons_misc"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <Button
+ style="@style/CaptionMenuButtonStyle"
+ android:id="@+id/screenshot_button"
+ android:contentDescription="@string/screenshot_text"
+ android:text="@string/screenshot_text"
+ android:drawableStart="@drawable/caption_screenshot_button"/>
+ <Button
+ style="@style/CaptionMenuButtonStyle"
+ android:id="@+id/select_button"
+ android:contentDescription="@string/select_text"
+ android:text="@string/select_text"
+ android:drawableStart="@drawable/caption_select_button"/>
+ <Button
+ style="@style/CaptionMenuButtonStyle"
+ android:id="@+id/close_button"
+ android:contentDescription="@string/close_text"
+ android:text="@string/close_text"
+ android:drawableStart="@drawable/caption_close_button"
+ android:textColor="#FFFF0000"/>
+ </LinearLayout>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
index da31a46..29cf151 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
@@ -22,9 +22,20 @@
android:gravity="center_horizontal"
android:background="@drawable/desktop_mode_decor_title">
<Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/back_button"
+ android:contentDescription="@string/back_button_text"
+ android:background="@drawable/decor_back_button_dark"/>
+ <Button
android:id="@+id/caption_handle"
android:layout_width="128dp"
android:layout_height="32dp"
+ android:layout_margin="5dp"
android:contentDescription="@string/handle_text"
android:background="@drawable/decor_handle_dark"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/close_window"
+ android:contentDescription="@string/close_button_text"
+ android:background="@drawable/decor_close_button_dark"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index b6b99e5..4b7a5ecb 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ቦታውን ለመቀየር ከመተግበሪያው ውጪ ሁለቴ መታ ያድርጉ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ገባኝ"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ለተጨማሪ መረጃ ይዘርጉ።"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ለተሻለ ዕይታ እንደገና ይጀመር?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"በማያ ገጽዎ ላይ የተሻለ ሆኖ እንዲታይ መተግበሪያውን እንደገና ማስጀመር ይችላሉ ነገር ግን የደረሱበትን የሂደት ደረጃ ወይም ማናቸውንም ያልተቀመጡ ለውጦች ሊያጡ ይችላሉ"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ይቅር"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"እንደገና ያስጀምሩ"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ዳግም አታሳይ"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"አስፋ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"አሳንስ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 7b2aded..851d2d1 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"انقر مرّتين خارج تطبيق لتغيير موضعه."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"حسنًا"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"التوسيع للحصول على مزيد من المعلومات"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"هل تريد إعادة تشغيل التطبيق لعرضه بشكل أفضل؟"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"يمكنك إعادة تشغيل التطبيق حتى يظهر بشكل أفضل على شاشتك، ولكن قد تفقد تقدمك أو أي تغييرات غير محفوظة."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"إلغاء"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"إعادة التشغيل"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"عدم عرض مربّع حوار التأكيد مجددًا"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"تكبير"</string>
<string name="minimize_button_text" msgid="271592547935841753">"تصغير"</string>
<string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 11a7e32..f8c87ae 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"এপ্টোৰ স্থান সলনি কৰিবলৈ ইয়াৰ বাহিৰত দুবাৰ টিপক"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"বুজি পালোঁ"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"অধিক তথ্যৰ বাবে বিস্তাৰ কৰক।"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"উন্নতভাৱে দেখা পাবলৈ ৰিষ্টাৰ্ট কৰিবনে?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"আপোনাৰ স্ক্ৰীনত উন্নতভাৱে দেখা পাবলৈ আপুনি এপ্টো ৰিষ্টাৰ্ট কৰিব পাৰে, কিন্তু আপুনি আপোনাৰ অগ্ৰগতি অথবা ছেভ নকৰা যিকোনো সালসলনি হেৰুৱাব পাৰে"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"বাতিল কৰক"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ৰিষ্টাৰ্ট কৰক"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"পুনৰাই নেদেখুৱাব"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"সৰ্বাধিক মাত্ৰালৈ বঢ়াওক"</string>
<string name="minimize_button_text" msgid="271592547935841753">"মিনিমাইজ কৰক"</string>
<string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 0961f30..d2a790a 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Двойчы націсніце экран па-за праграмай, каб перамясціць яе"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Зразумела"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгарнуць для дадатковай інфармацыі"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Перазапусціць?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Вы можаце перазапусціць праграму, каб яна выглядала лепш на вашым экране, але пры гэтым могуць знікнуць даныя пра ваш прагрэс або незахаваныя змяненні"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Скасаваць"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перазапусціць"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Больш не паказваць"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Разгарнуць"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Згарнуць"</string>
<string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 6a0648b..cb76e0a 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Докоснете два пъти извън дадено приложение, за да промените позицията му"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Разбрах"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Разгъване за още информация."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Да се рестартира ли с цел подобряване на изгледа?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Можете да рестартирате приложението, за да изглежда по-добре на екрана. Възможно е обаче да загубите напредъка си или незапазените промени"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Отказ"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Рестартиране"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Да не се показва отново"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Увеличаване"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Намаляване"</string>
<string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 5e80104..f3d740b 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"কোনও অ্যাপের স্থান পরিবর্তন করতে তার বাইরে ডবল ট্যাপ করুন"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"বুঝেছি"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"আরও তথ্যের জন্য বড় করুন।"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"আরও ভালভাবে দেখার জন্য রিস্টার্ট করবেন?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"স্ক্রিনে আরও ভালভাবে দেখার জন্য আপনি অ্যাপ রিস্টার্ট করতে পারবেন, এর ফলে চলতে থাকা কোনও প্রক্রিয়া বা সেভ না করা পরিবর্তন হারিয়ে যেতে পারে"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"বাতিল করুন"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"রিস্টার্ট করুন"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"আর দেখতে চাই না"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"বড় করুন"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ছোট করুন"</string>
<string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 3b070d8..965a72d 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Fes doble toc fora d\'una aplicació per canviar-ne la posició"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entesos"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Desplega per obtenir més informació."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vols reiniciar per a una millor visualització?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Pots reiniciar l\'aplicació perquè es vegi millor en pantalla, però és possible que perdis el teu progrés o qualsevol canvi que no hagis desat"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancel·la"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reinicia"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No ho tornis a mostrar"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximitza"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimitza"</string>
<string name="close_button_text" msgid="2913281996024033299">"Tanca"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 3606eaa..8fa64fb 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvojitým klepnutím mimo aplikaci změníte její umístění"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozbalením zobrazíte další informace."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Restartovat pro lepší zobrazení?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Aplikaci můžete restartovat, aby na obrazovce vypadala lépe, ale můžete přijít o svůj postup nebo o neuložené změny"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Zrušit"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Restartovat"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Tuto zprávu příště nezobrazovat"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximalizovat"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimalizovat"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 487d412..e3da6e0 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryk to gange uden for en app for at justere dens placering"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Udvid for at få flere oplysninger."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vil du genstarte for at få en bedre visning?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Du kan genstarte appen, så den ser bedre ud på din skærm, men du mister muligvis dine fremskridt og de ændringer, der ikke gemt"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuller"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Genstart"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Vis ikke igen"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimér"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
<string name="close_button_text" msgid="2913281996024033299">"Luk"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 0ec59e7..39d7bab 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Außerhalb einer App doppeltippen, um die Position zu ändern"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ok"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Für weitere Informationen maximieren."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Für bessere Darstellung neu starten?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Du kannst die App neu starten, damit sie an die Bildschirmabmessungen deines Geräts angepasst dargestellt wird – jedoch können dadurch dein Fortschritt oder nicht gespeicherte Änderungen verloren gehen."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Abbrechen"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Neu starten"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nicht mehr anzeigen"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximieren"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimieren"</string>
<string name="close_button_text" msgid="2913281996024033299">"Schließen"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 9c5aa92..8c48876 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dos veces fuera de una aplicación para cambiarla de posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mostrar más información"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"¿Reiniciar para que se vea mejor?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Puedes reiniciar la aplicación para que se vea mejor en la pantalla, pero puedes perder tu progreso o cualquier cambio que no hayas guardado"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"No volver a mostrar"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index f7eafbd..71f32cd 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Topeltpuudutage rakendusest väljaspool, et selle asendit muuta"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Selge"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Laiendage lisateabe saamiseks."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Kas taaskäivitada parema vaate saavutamiseks?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Saate rakenduse taaskäivitada, et see näeks ekraanikuval parem välja, kuid võite kaotada edenemise või salvestamata muudatused"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Tühista"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Taaskäivita"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ära kuva uuesti"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimeeri"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimeeri"</string>
<string name="close_button_text" msgid="2913281996024033299">"Sule"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index ea8fafe..214330e 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"برای جابهجا کردن برنامه، بیرون از آن دوضربه بزنید"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"متوجهام"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"برای اطلاعات بیشتر، گسترده کنید."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"برای نمایش بهتر بازراهاندازی شود؟"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"میتوانید برنامه را بازراهاندازی کنید تا بهتر روی صفحهنمایش نشان داده شود، اما ممکن است پیشرفت یا تغییرات ذخیرهنشده را ازدست بدهید"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"لغو کردن"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"بازراهاندازی"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"دوباره نشان داده نشود"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"بزرگ کردن"</string>
<string name="minimize_button_text" msgid="271592547935841753">"کوچک کردن"</string>
<string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index 298de64..b6972c6 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kaksoisnapauta sovelluksen ulkopuolella, jos haluat siirtää sitä"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Katso lisätietoja laajentamalla."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Käynnistetäänkö sovellus uudelleen, niin saat paremman näkymän?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Voit käynnistää sovelluksen uudelleen, jotta se näyttää paremmalta näytöllä, mutta saatat menettää edistymisesi tai tallentamattomat muutokset"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Peru"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Käynnistä uudelleen"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Älä näytä uudelleen"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Suurenna"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Pienennä"</string>
<string name="close_button_text" msgid="2913281996024033299">"Sulje"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 36b40bd..6ed74e4 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Touchez deux fois à côté d\'une application pour la repositionner"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développer pour en savoir plus."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Redémarrer pour un meilleur affichage?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'application pour qu\'elle s\'affiche mieux sur votre écran, mais il se peut que vous perdiez votre progression ou toute modification non enregistrée"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 7f3f914..82f02ce2 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Appuyez deux fois en dehors d\'une appli pour la repositionner"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Développez pour obtenir plus d\'informations"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Redémarrer pour améliorer l\'affichage ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Vous pouvez redémarrer l\'appli pour en améliorer son aspect sur votre écran, mais vous risquez de perdre votre progression ou les modifications non enregistrées"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Annuler"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Redémarrer"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne plus afficher"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Agrandir"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Réduire"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 3d5265f..b83be3d 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toca dúas veces fóra da aplicación para cambiala de posición"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Entendido"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Despregar para obter máis información."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Queres reiniciar a aplicación para que se vexa mellor?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Podes reiniciar a aplicación para que se vexa mellor na pantalla, pero podes perder o progreso que levas feito ou calquera cambio que non gardases"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Non mostrar outra vez"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index b217978..ef4c2d9 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"किसी ऐप्लिकेशन की जगह बदलने के लिए, उसके बाहर दो बार टैप करें"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ठीक है"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ज़्यादा जानकारी के लिए बड़ा करें."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"बेहतर व्यू पाने के लिए ऐप्लिकेशन को रीस्टार्ट करना है?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"स्क्रीन पर ऐप्लिकेशन का बेहतर व्यू पाने के लिए उसे रीस्टार्ट करें. हालांकि, आपने जो बदलाव सेव नहीं किए हैं या अब तक जो काम किए हैं उनका डेटा, ऐप्लिकेशन रीस्टार्ट करने पर मिट सकता है"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"रद्द करें"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"रीस्टार्ट करें"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"फिर से न दिखाएं"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"बड़ा करें"</string>
<string name="minimize_button_text" msgid="271592547935841753">"विंडो छोटी करें"</string>
<string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 7d556b2..223ecf1 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Կրկնակի հպեք հավելվածի կողքին՝ այն տեղափոխելու համար"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Եղավ"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Ծավալեք՝ ավելին իմանալու համար։"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Վերագործարկե՞լ հավելվածը"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Դուք կարող եք վերագործարկել հավելվածը, որպեսզի այն ավելի լավ ցուցադրվի ձեր էկրանին, սակայն ձեր առաջընթացը և չպահված փոփոխությունները կկորեն"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Չեղարկել"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Վերագործարկել"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Այլևս ցույց չտալ"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Ծավալել"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Ծալել"</string>
<string name="close_button_text" msgid="2913281996024033299">"Փակել"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index b51dc00..72dece4 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketuk dua kali di luar aplikasi untuk mengubah posisinya"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Oke"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Luaskan untuk melihat informasi selengkapnya."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulai ulang untuk tampilan yang lebih baik?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Anda dapat memulai ulang aplikasi agar terlihat lebih baik di layar, tetapi Anda mungkin kehilangan progres atau perubahan yang belum disimpan"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulai ulang"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tampilkan lagi"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimalkan"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimalkan"</string>
<string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index d924c47..aaa4846 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ýttu tvisvar utan við forrit til að færa það"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ég skil"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Stækka til að sjá frekari upplýsingar."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Viltu endurræsa til að fá betri sýn?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Þú getur endurræst forritið svo það falli betur að skjánum en þú gætir tapað framvindunni eða óvistuðum breytingum"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Hætta við"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Endurræsa"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ekki sýna þetta aftur"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Stækka"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minnka"</string>
<string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index d116257..9d7c2c0 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"צריך להקיש הקשה כפולה מחוץ לאפליקציה כדי למקם אותה מחדש"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"הבנתי"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"מרחיבים כדי לקבל מידע נוסף."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"להפעיל מחדש לתצוגה טובה יותר?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"אפשר להפעיל מחדש את האפליקציה כדי שהיא תוצג באופן טוב יותר במסך, אבל ייתכן שההתקדמות שלך או כל שינוי שלא נשמר יאבדו"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ביטול"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"הפעלה מחדש"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"אין צורך להציג את זה שוב"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"הגדלה"</string>
<string name="minimize_button_text" msgid="271592547935841753">"מזעור"</string>
<string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 02560ca..2ce45e2 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Қолданбаның орнын өзгерту үшін одан тыс жерді екі рет түртіңіз."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Түсінікті"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Толығырақ ақпарат алу үшін терезені жайыңыз."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Көріністі жақсарту үшін өшіріп қосу керек пе?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Экранда жақсырақ көрінуі үшін қолданбаны өшіріп қосуыңызға болады, бірақ мұндайда ағымдағы прогресс өшіп қалуы немесе сақталмаған өзгерістерді жоғалтуыңыз мүмкін."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Бас тарту"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Өшіріп қосу"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Қайта көрсетілмесін"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Жаю"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Кішірейту"</string>
<string name="close_button_text" msgid="2913281996024033299">"Жабу"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 32ca8e3..de87261 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ចុចពីរដងនៅក្រៅកម្មវិធី ដើម្បីប្ដូរទីតាំងកម្មវិធីនោះ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"យល់ហើយ"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ពង្រីកដើម្បីទទួលបានព័ត៌មានបន្ថែម។"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ចាប់ផ្ដើមឡើងវិញ ដើម្បីទទួលបានទិដ្ឋភាពកាន់តែស្អាតឬ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"អ្នកអាចចាប់ផ្ដើមកម្មវិធីឡើងវិញ ដើម្បីឱ្យកម្មវិធីនេះមើលទៅស្អាតជាងមុននៅលើអេក្រង់របស់អ្នក ប៉ុន្តែអ្នកអាចនឹងបាត់បង់ដំណើរការរបស់អ្នក ឬការកែប្រែណាមួយដែលអ្នកមិនបានរក្សាទុក"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"បោះបង់"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ចាប់ផ្ដើមឡើងវិញ"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"កុំបង្ហាញម្ដងទៀត"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"ពង្រីក"</string>
<string name="minimize_button_text" msgid="271592547935841753">"បង្រួម"</string>
<string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 844a9fa..6fa6386 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"앱 위치를 조정하려면 앱 외부를 두 번 탭합니다."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"확인"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"추가 정보는 펼쳐서 확인하세요."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"화면에 맞게 보도록 다시 시작할까요?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"앱을 다시 시작하면 화면에 더 잘 맞게 볼 수는 있지만 진행 상황 또는 저장되지 않은 변경사항을 잃을 수도 있습니다."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"취소"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"다시 시작"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"다시 표시 안함"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"최대화"</string>
<string name="minimize_button_text" msgid="271592547935841753">"최소화"</string>
<string name="close_button_text" msgid="2913281996024033299">"닫기"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 6c1c0b5..bd1e31e 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Аппыг дахин байрлуулахын тулд гадна талд нь хоёр товшино"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Ойлголоо"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Нэмэлт мэдээлэл авах бол дэлгэнэ үү."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Харагдах байдлыг сайжруулахын тулд дахин эхлүүлэх үү?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Та аппыг дахин эхлүүлэх боломжтой бөгөөд ингэснээр энэ нь таны дэлгэцэд илүү сайн харагдах хэдий ч та явцаа эсвэл хадгалаагүй аливаа өөрчлөлтөө алдаж магадгүй"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Цуцлах"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Дахин эхлүүлэх"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Дахиж бүү харуул"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Томруулах"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Багасгах"</string>
<string name="close_button_text" msgid="2913281996024033299">"Хаах"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 80efab8..c9dc2eb 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Ketik dua kali di luar apl untuk menempatkan semula apl itu"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Kembangkan untuk mendapatkan maklumat lanjut."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Mulakan semula untuk mendapatkan paparan yang lebih baik?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Anda boleh memulakan semula apl supaya apl kelihatan lebih baik pada skrin anda tetapi anda mungkin akan hilang kemajuan anda atau apa-apa perubahan yang belum disimpan"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Batal"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Mulakan semula"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Jangan tunjukkan lagi"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimumkan"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimumkan"</string>
<string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index be0815e..5f0a412 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"နေရာပြန်ချရန် အက်ပ်အပြင်ဘက်ကို နှစ်ချက်တို့ပါ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"နားလည်ပြီ"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"နောက်ထပ်အချက်အလက်များအတွက် ချဲ့နိုင်သည်။"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ပိုကောင်းသောမြင်ကွင်းအတွက် ပြန်စမလား။"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"အက်ပ်ကို ပြန်စပါက ၎င်းသည် စခရင်ပေါ်တွင် ပိုကြည့်ကောင်းသွားသော်လည်း သင်၏ လုပ်ငန်းစဉ် (သို့) မသိမ်းရသေးသော အပြောင်းအလဲများကို ဆုံးရှုံးနိုင်သည်"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"မလုပ်တော့"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ပြန်စရန်"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"နောက်ထပ်မပြပါနှင့်"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"ချဲ့ရန်"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ချုံ့ရန်"</string>
<string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 8d15086..e84c95d 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dobbelttrykk utenfor en app for å flytte den"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Greit"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Vis for å få mer informasjon."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vil du starte på nytt for bedre visning?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Du kan starte appen på nytt, slik at den ser bedre ut på skjermen, men du kan miste fremdrift eller ulagrede endringer"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Avbryt"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Start på nytt"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ikke vis dette igjen"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimer"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimer"</string>
<string name="close_button_text" msgid="2913281996024033299">"Lukk"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 0a8882d..7ae576a 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ଏକ ଆପକୁ ରିପୋଜିସନ କରିବା ପାଇଁ ଏହାର ବାହାରେ ଦୁଇଥର-ଟାପ କରନ୍ତୁ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ବୁଝିଗଲି"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ଅଧିକ ସୂଚନା ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ।"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ଏକ ଭଲ ଭ୍ୟୁ ପାଇଁ ରିଷ୍ଟାର୍ଟ କରିବେ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ଆପଣ ଆପକୁ ରିଷ୍ଟାର୍ଟ କରିପାରିବେ ଯାହା ଫଳରେ ଏହା ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଆହୁରି ଭଲ ଦେଖାଯିବ, କିନ୍ତୁ ଆପଣ ଆପଣଙ୍କ ପ୍ରଗତି କିମ୍ବା ସେଭ ହୋଇନଥିବା ଯେ କୌଣସି ପରିବର୍ତ୍ତନ ହରାଇପାରନ୍ତି"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ବାତିଲ କରନ୍ତୁ"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ରିଷ୍ଟାର୍ଟ କରନ୍ତୁ"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ପୁଣି ଦେଖାନ୍ତୁ ନାହିଁ"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"ବଡ଼ କରନ୍ତୁ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ଛୋଟ କରନ୍ତୁ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 62f15bf..6784354 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ਕਿਸੇ ਐਪ ਦੀ ਜਗ੍ਹਾ ਬਦਲਣ ਲਈ ਉਸ ਦੇ ਬਾਹਰ ਡਬਲ ਟੈਪ ਕਰੋ"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ਸਮਝ ਲਿਆ"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"ਹੋਰ ਜਾਣਕਾਰੀ ਲਈ ਵਿਸਤਾਰ ਕਰੋ।"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"ਕੀ ਬਿਹਤਰ ਦ੍ਰਿਸ਼ ਲਈ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰਨਾ ਹੈ?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ਤੁਸੀਂ ਐਪ ਨੂੰ ਮੁੜ-ਸ਼ੁਰੂ ਕਰ ਸਕਦੇ ਹੋ ਤਾਂ ਜੋ ਇਹ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਬਿਹਤਰ ਦਿਸੇ, ਪਰ ਤੁਸੀਂ ਆਪਣੀ ਪ੍ਰਗਤੀ ਜਾਂ ਕਿਸੇ ਅਣਰੱਖਿਅਤ ਤਬਦੀਲੀ ਨੂੰ ਗੁਆ ਸਕਦੇ ਹੋ"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ਰੱਦ ਕਰੋ"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"ਮੁੜ-ਸ਼ੁਰੂ ਕਰੋ"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"ਦੁਬਾਰਾ ਨਾ ਦਿਖਾਓ"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"ਵੱਡਾ ਕਰੋ"</string>
<string name="minimize_button_text" msgid="271592547935841753">"ਛੋਟਾ ਕਰੋ"</string>
<string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index d82f60f6..962ba0d 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Kliknij dwukrotnie poza aplikacją, aby ją przenieść"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Rozwiń, aby wyświetlić więcej informacji."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Uruchomić ponownie dla lepszego wyglądu?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Możesz ponownie uruchomić aplikację, aby lepiej wyglądała na ekranie, ale istnieje ryzyko, że utracisz postępy i niezapisane zmiany"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Anuluj"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Uruchom ponownie"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Nie pokazuj ponownie"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksymalizuj"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimalizuj"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 7435faf..345a89e 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Toque duas vezes fora de uma app para a reposicionar"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expandir para obter mais informações"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Reiniciar para uma melhor visualização?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Pode reiniciar a app para ficar com um melhor aspeto no seu ecrã, mas pode perder o seu progresso ou eventuais alterações não guardadas"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Cancelar"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Reiniciar"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Não mostrar de novo"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maximizar"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimizar"</string>
<string name="close_button_text" msgid="2913281996024033299">"Fechar"</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index e408655..c2d7c1a 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"යෙදුමක් නැවත ස්ථානගත කිරීමට පිටතින් දෙවරක් තට්ටු කරන්න"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"තේරුණා"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"වැඩිදුර තොරතුරු සඳහා දිග හරින්න"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"වඩා හොඳ දසුනක් සඳහා යළි අරඹන්න ද?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ඔබට එය ඔබේ තිරයෙහි වඩා හොඳින් පෙනෙන පරිදි යෙදුම යළි ඇරඹිය හැකි නමුත්, ඔබට ඔබේ ප්රගතිය හෝ නොසුරකින ලද වෙනස්කම් අහිමි විය හැක"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"අවලංගු කරන්න"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"යළි අරඹන්න"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"නැවත නොපෙන්වන්න"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"විහිදන්න"</string>
<string name="minimize_button_text" msgid="271592547935841753">"කුඩා කරන්න"</string>
<string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index ac9c2be..a64c52f 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Dvakrat se dotaknite zunaj aplikacije, če jo želite prestaviti."</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"V redu"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Razširitev za več informacij"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Želite znova zagnati za boljši pregled?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Če znova zaženete aplikacijo, bo prikaz na zaslonu boljši, vendar lahko izgubite napredek ali neshranjene spremembe."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Prekliči"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Znova zaženi"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Ne prikaži več"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Maksimiraj"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimiraj"</string>
<string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index ba57851..6562f9d 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Tryck snabbt två gånger utanför en app för att flytta den"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Utöka för mer information."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Vill du starta om för en bättre vy?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Du kan starta om appen så den passar bättre på skärmen men du kan förlora dina framsteg och eventuella osparade ändringar."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Avbryt"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Starta om"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Visa inte igen"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Utöka"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Minimera"</string>
<string name="close_button_text" msgid="2913281996024033299">"Stäng"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 43b415c..56a1a32 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Gusa mara mbili nje ya programu ili uihamishe"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Nimeelewa"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Panua ili upate maelezo zaidi."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Ungependa kuzima kisha uwashe ili upate mwonekano bora zaidi?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Unaweza kuzima kisha uwashe programu yako ili ifanye kazi vizuri zaidi kwenye skrini yako, lakini unaweza kupoteza maendeleo yako au mabadiliko yoyote ambayo hayajahifadhiwa"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Ghairi"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Zima kisha uwashe"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Usionyeshe tena"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Panua"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Punguza"</string>
<string name="close_button_text" msgid="2913281996024033299">"Funga"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 3e65964..2f54264 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"ஆப்ஸை இடம் மாற்ற அதன் வெளியில் இருமுறை தட்டலாம்"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"சரி"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"கூடுதல் தகவல்களுக்கு விரிவாக்கலாம்."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"சரியான விதத்தில் காட்டப்பட மீண்டும் தொடங்கவா?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"ஆப்ஸை மீண்டும் தொடங்குவதன் மூலம் திரையில் அது சரியான விதத்தில் தோற்றமளிக்கும். ஆனால் செயல்நிலையையோ சேமிக்கப்படாமல் இருக்கும் மாற்றங்களையோ நீங்கள் இழக்கக்கூடும்."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"ரத்துசெய்"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"மீண்டும் தொடங்கு"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"மீண்டும் காட்டாதே"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"பெரிதாக்கும்"</string>
<string name="minimize_button_text" msgid="271592547935841753">"சிறிதாக்கும்"</string>
<string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 7d09fc4..6e49865 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"యాప్ స్థానాన్ని మార్చడానికి దాని వెలుపల డబుల్-ట్యాప్ చేయండి"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"అర్థమైంది"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"మరింత సమాచారం కోసం విస్తరించండి."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"మెరుగైన వీక్షణ కోసం రీస్టార్ట్ చేయాలా?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"మీరు యాప్ని రీస్టార్ట్ చేయవచ్చు, తద్వారా ఇది మీ స్క్రీన్పై మెరుగ్గా కనిపిస్తుంది, కానీ మీరు మీ ప్రోగ్రెస్ను గానీ లేదా సేవ్ చేయని ఏవైనా మార్పులను గానీ కోల్పోవచ్చు"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"రద్దు చేయండి"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"రీస్టార్ట్ చేయండి"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"మళ్లీ చూపవద్దు"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"గరిష్టీకరించండి"</string>
<string name="minimize_button_text" msgid="271592547935841753">"కుదించండి"</string>
<string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 015cfc8..55e36dd 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Yeniden konumlandırmak için uygulamanın dışına iki kez dokunun"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Anladım"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Daha fazla bilgi için genişletin."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Daha iyi bir görünüm için yeniden başlatılsın mı?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Ekranınızda daha iyi görünmesi için uygulamayı yeniden başlatabilirsiniz, ancak ilerlemenizi ve kaydedilmemiş değişikliklerinizi kaybedebilirsiniz"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"İptal"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Yeniden başlat"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Bir daha gösterme"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Ekranı Kapla"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Küçült"</string>
<string name="close_button_text" msgid="2913281996024033299">"Kapat"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 1ed9069..2b04b90 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Щоб перемістити додаток, двічі торкніться області поза ним"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"ОK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Розгорніть, щоб дізнатися більше."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Перезапустити для зручнішого перегляду?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Ви можете перезапустити додаток, щоб покращити його вигляд на екрані, але ваші досягнення або незбережені зміни може бути втрачено"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Скасувати"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Перезапустити"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Більше не показувати"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Збільшити"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Згорнути"</string>
<string name="close_button_text" msgid="2913281996024033299">"Закрити"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 0bbdf4f..a666cd9 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Qayta joylash uchun ilova tashqarisiga ikki marta bosing"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Batafsil axborot olish uchun kengaytiring."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Yaxshi koʻrinishi uchun qayta ishga tushirilsinmi?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Ilovani ekranda yaxshiroq koʻrinishi uchun qayta ishga tushirishingiz mumkin. Bunda jarayonlar yoki saqlanmagan oʻzgarishlar yoʻqolishi mumkin."</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Bekor qilish"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Qaytadan"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Boshqa chiqmasin"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Yoyish"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Kichraytirish"</string>
<string name="close_button_text" msgid="2913281996024033299">"Yopish"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index d8e1318..a78e01c 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Nhấn đúp bên ngoài ứng dụng để đặt lại vị trí"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"OK"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Mở rộng để xem thêm thông tin."</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"Khởi động lại để ứng dụng trông vừa vặn hơn?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"Bạn có thể khởi động lại ứng dụng để ứng dụng nhìn đẹp hơn trên màn hình. Tuy nhiên, nếu làm vậy, bạn có thể mất tiến trình hoặc mọi thay đổi chưa lưu"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"Huỷ"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"Khởi động lại"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"Không hiện lại"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"Phóng to"</string>
<string name="minimize_button_text" msgid="271592547935841753">"Thu nhỏ"</string>
<string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index 88d6aa6..bb3cf39 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -82,16 +82,11 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"在某个应用外连续点按两次,即可调整它的位置"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展开即可了解详情。"</string>
- <!-- no translation found for letterbox_restart_dialog_title (8543049527871033505) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_description (6096946078246557848) -->
- <skip />
- <!-- no translation found for letterbox_restart_cancel (1342209132692537805) -->
- <skip />
- <!-- no translation found for letterbox_restart_restart (8529976234412442973) -->
- <skip />
- <!-- no translation found for letterbox_restart_dialog_checkbox_title (5252918008140768386) -->
- <skip />
+ <string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"重启以改进外观?"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"您可以重启应用,使其在屏幕上的显示效果更好,但您可能会丢失进度或任何未保存的更改"</string>
+ <string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string>
+ <string name="letterbox_restart_restart" msgid="8529976234412442973">"重启"</string>
+ <string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不再显示"</string>
<string name="maximize_button_text" msgid="1650859196290301963">"最大化"</string>
<string name="minimize_button_text" msgid="271592547935841753">"最小化"</string>
<string name="close_button_text" msgid="2913281996024033299">"关闭"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 6bd05c6..724f012 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -83,7 +83,7 @@
<string name="letterbox_education_got_it" msgid="4057634570866051177">"知道了"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"展開即可查看詳情。"</string>
<string name="letterbox_restart_dialog_title" msgid="8543049527871033505">"要重新啟動改善檢視畫面嗎?"</string>
- <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"你可以重新啟動應用程式,讓系統更新檢視畫面。不過,系統可能不會儲存目前進度及你所做的任何變更"</string>
+ <string name="letterbox_restart_dialog_description" msgid="6096946078246557848">"您可重新啟動應用程式,讓系統更新檢視畫面;但系統可能不會儲存目前進度及您作出的任何變更"</string>
<string name="letterbox_restart_cancel" msgid="1342209132692537805">"取消"</string>
<string name="letterbox_restart_restart" msgid="8529976234412442973">"重新啟動"</string>
<string name="letterbox_restart_dialog_checkbox_title" msgid="5252918008140768386">"不要再顯示"</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 774f6c6..76eb094 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -105,6 +105,10 @@
1.777778
</item>
+ <!-- The aspect ratio that by which optimizations to large screen sizes are made.
+ Needs to be less that or equal to 1. -->
+ <item name="config_pipLargeScreenOptimizedAspectRatio" format="float" type="dimen">0.5625</item>
+
<!-- The default gravity for the picture-in-picture window.
Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
<integer name="config_defaultPictureInPictureGravity">0x55</integer>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8908959..6f31d06 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -228,7 +228,7 @@
<dimen name="bubble_user_education_stack_padding">16dp</dimen>
<!-- Bottom and end margin for compat buttons. -->
- <dimen name="compat_button_margin">16dp</dimen>
+ <dimen name="compat_button_margin">24dp</dimen>
<!-- The radius of the corners of the compat hint bubble. -->
<dimen name="compat_hint_corner_radius">28dp</dimen>
@@ -367,8 +367,12 @@
<!-- Width of buttons (32dp each) + padding (128dp total). -->
<dimen name="freeform_decor_caption_menu_width">256dp</dimen>
+ <dimen name="freeform_decor_caption_menu_height">250dp</dimen>
+
<dimen name="freeform_resize_handle">30dp</dimen>
<dimen name="freeform_resize_corner">44dp</dimen>
+ <dimen name="caption_menu_elevation">4dp</dimen>
+
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 250dac6..63992329 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -100,6 +100,15 @@
<!-- Accessibility action for moving docked stack divider to make the bottom screen full screen [CHAR LIMIT=NONE] -->
<string name="accessibility_action_divider_bottom_full">Bottom full screen</string>
+ <!-- Accessibility label for splitting to the left drop zone [CHAR LIMIT=NONE] -->
+ <string name="accessibility_split_left">Split left</string>
+ <!-- Accessibility label for splitting to the right drop zone [CHAR LIMIT=NONE] -->
+ <string name="accessibility_split_right">Split right</string>
+ <!-- Accessibility label for splitting to the top drop zone [CHAR LIMIT=NONE] -->
+ <string name="accessibility_split_top">Split top</string>
+ <!-- Accessibility label for splitting to the bottom drop zone [CHAR LIMIT=NONE] -->
+ <string name="accessibility_split_bottom">Split bottom</string>
+
<!-- One-Handed Tutorial title [CHAR LIMIT=60] -->
<string name="one_handed_tutorial_title">Using one-handed mode</string>
<!-- One-Handed Tutorial description [CHAR LIMIT=NONE] -->
@@ -216,6 +225,8 @@
<string name="back_button_text">Back</string>
<!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
<string name="handle_text">Handle</string>
+ <!-- Accessibility text for the handle menu app icon [CHAR LIMIT=NONE] -->
+ <string name="app_icon_text">App Icon</string>
<!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
<string name="fullscreen_text">Fullscreen</string>
<!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
@@ -226,4 +237,12 @@
<string name="more_button_text">More</string>
<!-- Accessibility text for the handle floating window button [CHAR LIMIT=NONE] -->
<string name="float_button_text">Float</string>
+ <!-- Accessibility text for the handle menu select button [CHAR LIMIT=NONE] -->
+ <string name="select_text">Select</string>
+ <!-- Accessibility text for the handle menu screenshot button [CHAR LIMIT=NONE] -->
+ <string name="screenshot_text">Screenshot</string>
+ <!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
+ <string name="close_text">Close</string>
+ <!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
+ <string name="collapse_menu_text">Close Menu</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index e8f340c..bae009a 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -37,6 +37,22 @@
<item name="android:padding">4dp</item>
</style>
+ <style name="CaptionWindowingButtonStyle">
+ <item name="android:layout_width">32dp</item>
+ <item name="android:layout_height">32dp</item>
+ <item name="android:padding">4dp</item>
+ <item name="android:layout_marginTop">5dp</item>
+ <item name="android:layout_marginBottom">5dp</item>
+ </style>
+
+ <style name="CaptionMenuButtonStyle" parent="@style/Widget.AppCompat.Button.Borderless">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">52dp</item>
+ <item name="android:layout_marginStart">10dp</item>
+ <item name="android:padding">4dp</item>
+ <item name="android:gravity">start|center_vertical</item>
+ </style>
+
<style name="DockedDividerBackground">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/split_divider_bar_width</item>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
index d276002..88525aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java
@@ -84,6 +84,15 @@
String[] groups = Arrays.copyOfRange(args, 1, args.length);
return mShellProtoLog.stopTextLogging(groups, pw) == 0;
}
+ case "save-for-bugreport": {
+ if (!mShellProtoLog.isProtoEnabled()) {
+ pw.println("Logging to proto is not enabled for WMShell.");
+ return false;
+ }
+ mShellProtoLog.stopProtoLog(pw, true /* writeToFile */);
+ mShellProtoLog.startProtoLog(pw);
+ return true;
+ }
default: {
pw.println("Invalid command: " + args[0]);
printShellCommandHelp(pw, "");
@@ -108,5 +117,7 @@
pw.println(prefix + " Enable logcat logging for given groups.");
pw.println(prefix + "disable-text [group...]");
pw.println(prefix + " Disable logcat logging for given groups.");
+ pw.println(prefix + "save-for-bugreport");
+ pw.println(prefix + " Flush proto logging to file, only if it's enabled.");
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 45b234a..f616e6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -699,19 +699,6 @@
return bounds.width() > bounds.height();
}
- /** Reverse the split position. */
- @SplitPosition
- public static int reversePosition(@SplitPosition int position) {
- switch (position) {
- case SPLIT_POSITION_TOP_OR_LEFT:
- return SPLIT_POSITION_BOTTOM_OR_RIGHT;
- case SPLIT_POSITION_BOTTOM_OR_RIGHT:
- return SPLIT_POSITION_TOP_OR_LEFT;
- default:
- return SPLIT_POSITION_UNDEFINED;
- }
- }
-
/**
* Return if this layout is landscape.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
new file mode 100644
index 0000000..042721c9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.split;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.wm.shell.ShellTaskOrganizer;
+
+/** Helper utility class for split screen components to use. */
+public class SplitScreenUtils {
+ /** Reverse the split position. */
+ @SplitScreenConstants.SplitPosition
+ public static int reverseSplitPosition(@SplitScreenConstants.SplitPosition int position) {
+ switch (position) {
+ case SPLIT_POSITION_TOP_OR_LEFT:
+ return SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ case SPLIT_POSITION_BOTTOM_OR_RIGHT:
+ return SPLIT_POSITION_TOP_OR_LEFT;
+ case SPLIT_POSITION_UNDEFINED:
+ default:
+ return SPLIT_POSITION_UNDEFINED;
+ }
+ }
+
+ /** Returns true if the task is valid for split screen. */
+ public static boolean isValidToSplit(ActivityManager.RunningTaskInfo taskInfo) {
+ return taskInfo != null && taskInfo.supportsMultiWindow
+ && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
+ && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
+ }
+
+ /** Retrieve package name from an intent */
+ @Nullable
+ public static String getPackageName(Intent intent) {
+ if (intent == null || intent.getComponent() == null) {
+ return null;
+ }
+ return intent.getComponent().getPackageName();
+ }
+
+ /** Retrieve package name from a PendingIntent */
+ @Nullable
+ public static String getPackageName(PendingIntent pendingIntent) {
+ if (pendingIntent == null || pendingIntent.getIntent() == null) {
+ return null;
+ }
+ return getPackageName(pendingIntent.getIntent());
+ }
+
+ /** Retrieve package name from a taskId */
+ @Nullable
+ public static String getPackageName(int taskId, ShellTaskOrganizer taskOrganizer) {
+ final ActivityManager.RunningTaskInfo taskInfo = taskOrganizer.getRunningTaskInfo(taskId);
+ return taskInfo != null ? getPackageName(taskInfo.baseIntent) : null;
+ }
+
+ /** Returns true if they are the same package. */
+ public static boolean samePackage(String packageName1, String packageName2) {
+ return packageName1 != null && packageName1.equals(packageName2);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 8022e9b..b0756a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -39,6 +39,7 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
import com.android.wm.shell.pip.tv.TvPipBoundsController;
import com.android.wm.shell.pip.tv.TvPipBoundsState;
@@ -69,6 +70,7 @@
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -88,6 +90,7 @@
shellInit,
shellController,
tvPipBoundsState,
+ pipSizeSpecHandler,
tvPipBoundsAlgorithm,
tvPipBoundsController,
pipAppOpsListener,
@@ -127,14 +130,23 @@
@WMSingleton
@Provides
static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
- TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
- return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm);
+ TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
+ PipSizeSpecHandler pipSizeSpecHandler) {
+ return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
+ pipSizeSpecHandler);
}
@WMSingleton
@Provides
- static TvPipBoundsState provideTvPipBoundsState(Context context) {
- return new TvPipBoundsState(context);
+ static TvPipBoundsState provideTvPipBoundsState(Context context,
+ PipSizeSpecHandler pipSizeSpecHandler) {
+ return new TvPipBoundsState(context, pipSizeSpecHandler);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
+ return new PipSizeSpecHandler(context);
}
// Handler needed for loadDrawableAsync() in PipControlsViewController
@@ -194,6 +206,7 @@
TvPipMenuController tvPipMenuController,
SyncTransactionQueue syncTransactionQueue,
TvPipBoundsState tvPipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipTransitionState pipTransitionState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
@@ -205,10 +218,11 @@
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new TvPipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, tvPipBoundsState, tvPipBoundsAlgorithm,
- tvPipMenuController, pipAnimationController, pipSurfaceTransactionHelper,
- pipTransitionController, pipParamsChangedForwarder, splitScreenControllerOptional,
- displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipSizeSpecHandler,
+ tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
+ pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+ splitScreenControllerOptional, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 512a4ef..efc7d1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -77,6 +77,7 @@
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -338,6 +339,7 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -354,17 +356,18 @@
return Optional.ofNullable(PipController.create(
context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
- pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
- phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler,
- pipTransitionController, windowManagerShellWrapper, taskStackListener,
- pipParamsChangedForwarder, displayInsetsController, oneHandedController,
- mainExecutor));
+ pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler, pipMotionHelper,
+ pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
+ pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
+ taskStackListener, pipParamsChangedForwarder, displayInsetsController,
+ oneHandedController, mainExecutor));
}
@WMSingleton
@Provides
- static PipBoundsState providePipBoundsState(Context context) {
- return new PipBoundsState(context);
+ static PipBoundsState providePipBoundsState(Context context,
+ PipSizeSpecHandler pipSizeSpecHandler) {
+ return new PipBoundsState(context, pipSizeSpecHandler);
}
@WMSingleton
@@ -381,11 +384,18 @@
@WMSingleton
@Provides
+ static PipSizeSpecHandler providePipSizeSpecHelper(Context context) {
+ return new PipSizeSpecHandler(context);
+ }
+
+ @WMSingleton
+ @Provides
static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
- PhonePipKeepClearAlgorithm pipKeepClearAlgorithm) {
+ PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipSizeSpecHandler pipSizeSpecHandler) {
return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
- pipKeepClearAlgorithm);
+ pipKeepClearAlgorithm, pipSizeSpecHandler);
}
// Handler is used by Icon.loadDrawableAsync
@@ -409,13 +419,14 @@
PhonePipMenuController menuPhoneController,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipTaskOrganizer pipTaskOrganizer,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
- pipBoundsState, pipTaskOrganizer, pipMotionHelper,
+ pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper,
floatingContentCoordinator, pipUiEventLogger, mainExecutor);
}
@@ -431,6 +442,7 @@
SyncTransactionQueue syncTransactionQueue,
PipTransitionState pipTransitionState,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipMenuController menuPhoneController,
PipAnimationController pipAnimationController,
@@ -442,10 +454,11 @@
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context,
- syncTransactionQueue, pipTransitionState, pipBoundsState, pipBoundsAlgorithm,
- menuPhoneController, pipAnimationController, pipSurfaceTransactionHelper,
- pipTransitionController, pipParamsChangedForwarder, splitScreenControllerOptional,
- displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
+ pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
+ pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+ splitScreenControllerOptional, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
}
@WMSingleton
@@ -460,13 +473,14 @@
static PipTransitionController providePipTransitionController(Context context,
ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions,
PipAnimationController pipAnimationController, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipTransitionState pipTransitionState,
- PhonePipMenuController pipMenuController,
+ PipBoundsState pipBoundsState, PipSizeSpecHandler pipSizeSpecHandler,
+ PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenOptional) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
- pipBoundsState, pipTransitionState, pipMenuController, pipBoundsAlgorithm,
- pipAnimationController, pipSurfaceTransactionHelper, splitScreenOptional);
+ pipBoundsState, pipSizeSpecHandler, pipTransitionState, pipMenuController,
+ pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper,
+ splitScreenOptional);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index a839a23..bc81710 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -253,12 +254,16 @@
*/
void showDesktopApps() {
// Bring apps to front, ignoring their visibility status to always ensure they are on top.
- WindowContainerTransaction wct = bringDesktopAppsToFront(true /* ignoreVisibility */);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ bringDesktopAppsToFront(wct);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */);
- } else {
- mShellTaskOrganizer.applyTransaction(wct);
+ if (!wct.isEmpty()) {
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ // TODO(b/268662477): add animation for the transition
+ mTransitions.startTransition(TRANSIT_NONE, wct, null /* handler */);
+ } else {
+ mShellTaskOrganizer.applyTransaction(wct);
+ }
}
}
@@ -267,9 +272,7 @@
return mDesktopModeTaskRepository.getVisibleTaskCount();
}
- @NonNull
- private WindowContainerTransaction bringDesktopAppsToFront(boolean force) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ private void bringDesktopAppsToFront(WindowContainerTransaction wct) {
final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
@@ -282,18 +285,11 @@
}
if (taskInfos.isEmpty()) {
- return wct;
+ return;
}
- if (!force) {
- final boolean allActiveTasksAreVisible = taskInfos.stream()
- .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
- if (allActiveTasksAreVisible) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "bringDesktopAppsToFront: active tasks are already in front, skipping.");
- return wct;
- }
- }
+ moveHomeTaskToFront(wct);
+
ProtoLog.d(WM_SHELL_DESKTOP_MODE,
"bringDesktopAppsToFront: reordering all active tasks to the front");
final List<Integer> allTasksInZOrder =
@@ -304,7 +300,15 @@
for (RunningTaskInfo task : taskInfos) {
wct.reorder(task.token, true);
}
- return wct;
+ }
+
+ private void moveHomeTaskToFront(WindowContainerTransaction wct) {
+ for (RunningTaskInfo task : mShellTaskOrganizer.getRunningTasks(mContext.getDisplayId())) {
+ if (task.getActivityType() == ACTIVITY_TYPE_HOME) {
+ wct.reorder(task.token, true /* onTop */);
+ return;
+ }
+ }
}
/**
@@ -363,7 +367,7 @@
if (wct == null) {
wct = new WindowContainerTransaction();
}
- wct.merge(bringDesktopAppsToFront(false /* ignoreVisibility */), true /* transfer */);
+ bringDesktopAppsToFront(wct);
wct.reorder(request.getTriggerTask().token, true /* onTop */);
return wct;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 2303325..fce0138 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -27,6 +27,7 @@
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionInfo
@@ -89,7 +90,8 @@
// Execute transaction if there are pending operations
if (!wct.isEmpty) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
+ // TODO(b/268662477): add animation for the transition
+ transitions.startTransition(TRANSIT_NONE, wct, null /* handler */)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 55378a8..3ade1ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -22,6 +22,10 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -315,6 +319,25 @@
// Switching between targets
mDropZoneView1.animateSwitch();
mDropZoneView2.animateSwitch();
+ // Announce for accessibility.
+ switch (target.type) {
+ case TYPE_SPLIT_LEFT:
+ mDropZoneView1.announceForAccessibility(
+ mContext.getString(R.string.accessibility_split_left));
+ break;
+ case TYPE_SPLIT_RIGHT:
+ mDropZoneView2.announceForAccessibility(
+ mContext.getString(R.string.accessibility_split_right));
+ break;
+ case TYPE_SPLIT_TOP:
+ mDropZoneView1.announceForAccessibility(
+ mContext.getString(R.string.accessibility_split_top));
+ break;
+ case TYPE_SPLIT_BOTTOM:
+ mDropZoneView2.announceForAccessibility(
+ mContext.getString(R.string.accessibility_split_bottom));
+ break;
+ }
}
mCurrentTarget = target;
}
@@ -424,12 +447,10 @@
}
private void animateHighlight(DragAndDropPolicy.Target target) {
- if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_LEFT
- || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_TOP) {
+ if (target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP) {
mDropZoneView1.setShowingHighlight(true);
mDropZoneView2.setShowingHighlight(false);
- } else if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT
- || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM) {
+ } else if (target.type == TYPE_SPLIT_RIGHT || target.type == TYPE_SPLIT_BOTTOM) {
mDropZoneView1.setShowingHighlight(false);
mDropZoneView2.setShowingHighlight(true);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index e91987d..ac13f96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.kidsmode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -68,7 +69,7 @@
private static final String TAG = "KidsModeTaskOrganizer";
private static final int[] CONTROLLED_ACTIVITY_TYPES =
- {ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD};
+ {ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_HOME};
private static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
@@ -93,6 +94,8 @@
private KidsModeSettingsObserver mKidsModeSettingsObserver;
private boolean mEnabled;
+ private ActivityManager.RunningTaskInfo mHomeTask;
+
private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -219,6 +222,13 @@
}
super.onTaskAppeared(taskInfo, leash);
+ // Only allow home to draw under system bars.
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+ final WindowContainerTransaction wct = getWindowContainerTransaction();
+ wct.setBounds(taskInfo.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight));
+ mSyncQueue.queue(wct);
+ mHomeTask = taskInfo;
+ }
mSyncQueue.runInSync(t -> {
// Reset several properties back to fullscreen (PiP, for example, leaves all these
// properties in a bad state).
@@ -291,6 +301,13 @@
}
mLaunchRootTask = null;
mLaunchRootLeash = null;
+ if (mHomeTask != null && mHomeTask.token != null) {
+ final WindowContainerToken homeToken = mHomeTask.token;
+ final WindowContainerTransaction wct = getWindowContainerTransaction();
+ wct.setBounds(homeToken, null);
+ mSyncQueue.queue(wct);
+ }
+ mHomeTask = null;
unregisterOrganizer();
}
@@ -320,7 +337,7 @@
final SurfaceControl rootLeash = mLaunchRootLeash;
mSyncQueue.runInSync(t -> {
t.setPosition(rootLeash, taskBounds.left, taskBounds.top);
- t.setWindowCrop(rootLeash, taskBounds.width(), taskBounds.height());
+ t.setWindowCrop(rootLeash, mDisplayWidth, mDisplayHeight);
});
}
}
@@ -351,7 +368,7 @@
final SurfaceControl finalLeash = mLaunchRootLeash;
mSyncQueue.runInSync(t -> {
t.setPosition(finalLeash, taskBounds.left, taskBounds.top);
- t.setWindowCrop(finalLeash, taskBounds.width(), taskBounds.height());
+ t.setWindowCrop(finalLeash, mDisplayWidth, mDisplayHeight);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 6728c00..d9ac76e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -28,6 +28,7 @@
import android.annotation.NonNull;
import android.app.TaskInfo;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -361,22 +362,26 @@
}
void setColorContentOverlay(Context context) {
- final SurfaceControl.Transaction tx =
- mSurfaceControlTransactionFactory.getTransaction();
- if (mContentOverlay != null) {
- mContentOverlay.detach(tx);
- }
- mContentOverlay = new PipContentOverlay.PipColorOverlay(context);
- mContentOverlay.attach(tx, mLeash);
+ reattachContentOverlay(new PipContentOverlay.PipColorOverlay(context));
}
void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
+ reattachContentOverlay(
+ new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint));
+ }
+
+ void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo) {
+ reattachContentOverlay(
+ new PipContentOverlay.PipAppIconOverlay(context, bounds, activityInfo));
+ }
+
+ private void reattachContentOverlay(PipContentOverlay overlay) {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
if (mContentOverlay != null) {
mContentOverlay.detach(tx);
}
- mContentOverlay = new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint);
+ mContentOverlay = overlay;
mContentOverlay.attach(tx, mLeash);
}
@@ -570,8 +575,9 @@
final Rect base = getBaseValue();
final Rect start = getStartValue();
final Rect end = getEndValue();
+ Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
if (mContentOverlay != null) {
- mContentOverlay.onAnimationUpdate(tx, fraction);
+ mContentOverlay.onAnimationUpdate(tx, bounds, fraction);
}
if (rotatedEndRect != null) {
// Animate the bounds in a different orientation. It only happens when
@@ -579,7 +585,6 @@
applyRotation(tx, leash, fraction, start, end);
return;
}
- Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
float angle = (1.0f - fraction) * startingAngle;
setCurrentValue(bounds);
if (inScaleTransition() || sourceHintRect == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index f6d67d8..867162b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -16,24 +16,19 @@
package com.android.wm.shell.pip;
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PictureInPictureParams;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.Size;
-import android.util.TypedValue;
import android.view.Gravity;
import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import java.io.PrintWriter;
@@ -45,33 +40,29 @@
private static final String TAG = PipBoundsAlgorithm.class.getSimpleName();
private static final float INVALID_SNAP_FRACTION = -1f;
- private final @NonNull PipBoundsState mPipBoundsState;
+ @NonNull private final PipBoundsState mPipBoundsState;
+ @NonNull protected final PipSizeSpecHandler mPipSizeSpecHandler;
private final PipSnapAlgorithm mSnapAlgorithm;
private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
- private float mDefaultSizePercent;
- private float mMinAspectRatioForMinSize;
- private float mMaxAspectRatioForMinSize;
private float mDefaultAspectRatio;
private float mMinAspectRatio;
private float mMaxAspectRatio;
private int mDefaultStackGravity;
- private int mDefaultMinSize;
- private int mOverridableMinSize;
- protected Point mScreenEdgeInsets;
public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
@NonNull PipSnapAlgorithm pipSnapAlgorithm,
- @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm) {
+ @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
mPipBoundsState = pipBoundsState;
mSnapAlgorithm = pipSnapAlgorithm;
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
reloadResources(context);
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
// resources as it would clobber mAspectRatio when entering PiP from fullscreen which
// triggers a configuration change and the resources to be reloaded.
mPipBoundsState.setAspectRatio(mDefaultAspectRatio);
- mPipBoundsState.setMinEdgeSize(mDefaultMinSize);
}
/**
@@ -83,27 +74,15 @@
R.dimen.config_pictureInPictureDefaultAspectRatio);
mDefaultStackGravity = res.getInteger(
R.integer.config_defaultPictureInPictureGravity);
- mDefaultMinSize = res.getDimensionPixelSize(
- R.dimen.default_minimal_size_pip_resizable_task);
- mOverridableMinSize = res.getDimensionPixelSize(
- R.dimen.overridable_minimal_size_pip_resizable_task);
final String screenEdgeInsetsDpString = res.getString(
R.string.config_defaultPictureInPictureScreenEdgeInsets);
final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
? Size.parseSize(screenEdgeInsetsDpString)
: null;
- mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
- : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
- dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
mMinAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
mMaxAspectRatio = res.getFloat(
com.android.internal.R.dimen.config_pictureInPictureMaxAspectRatio);
- mDefaultSizePercent = res.getFloat(
- R.dimen.config_pictureInPictureDefaultSizePercent);
- mMaxAspectRatioForMinSize = res.getFloat(
- R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
- mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
}
/**
@@ -180,8 +159,9 @@
if (windowLayout.minWidth > 0 && windowLayout.minHeight > 0) {
// If either dimension is smaller than the allowed minimum, adjust them
// according to mOverridableMinSize
- return new Size(Math.max(windowLayout.minWidth, mOverridableMinSize),
- Math.max(windowLayout.minHeight, mOverridableMinSize));
+ return new Size(
+ Math.max(windowLayout.minWidth, mPipSizeSpecHandler.getOverrideMinEdgeSize()),
+ Math.max(windowLayout.minHeight, mPipSizeSpecHandler.getOverrideMinEdgeSize()));
}
return null;
}
@@ -243,28 +223,13 @@
final float snapFraction = mSnapAlgorithm.getSnapFraction(stackBounds,
getMovementBounds(stackBounds), mPipBoundsState.getStashedState());
- final Size overrideMinSize = mPipBoundsState.getOverrideMinSize();
final Size size;
if (useCurrentMinEdgeSize || useCurrentSize) {
- // The default minimum edge size, or the override min edge size if set.
- final int defaultMinEdgeSize = overrideMinSize == null ? mDefaultMinSize
- : mPipBoundsState.getOverrideMinEdgeSize();
- final int minEdgeSize = useCurrentMinEdgeSize ? mPipBoundsState.getMinEdgeSize()
- : defaultMinEdgeSize;
- // Use the existing size but adjusted to the aspect ratio and min edge size.
- size = getSizeForAspectRatio(
- new Size(stackBounds.width(), stackBounds.height()), aspectRatio, minEdgeSize);
+ // Use the existing size but adjusted to the new aspect ratio.
+ size = mPipSizeSpecHandler.getSizeForAspectRatio(
+ new Size(stackBounds.width(), stackBounds.height()), aspectRatio);
} else {
- if (overrideMinSize != null) {
- // The override minimal size is set, use that as the default size making sure it's
- // adjusted to the aspect ratio.
- size = adjustSizeToAspectRatio(overrideMinSize, aspectRatio);
- } else {
- // Calculate the default size using the display size and default min edge size.
- final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
- size = getSizeForAspectRatio(aspectRatio, mDefaultMinSize,
- displayLayout.width(), displayLayout.height());
- }
+ size = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
}
final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
@@ -273,18 +238,6 @@
mSnapAlgorithm.applySnapFraction(stackBounds, getMovementBounds(stackBounds), snapFraction);
}
- /** Adjusts the given size to conform to the given aspect ratio. */
- private Size adjustSizeToAspectRatio(@NonNull Size size, float aspectRatio) {
- final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
- if (sizeAspectRatio > aspectRatio) {
- // Size is wider, fix the width and increase the height
- return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
- } else {
- // Size is taller, fix the height and adjust the width.
- return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
- }
- }
-
/**
* @return the default bounds to show the PIP, if a {@param snapFraction} and {@param size} are
* provided, then it will apply the default bounds to the provided snap fraction and size.
@@ -303,17 +256,9 @@
final Size defaultSize;
final Rect insetBounds = new Rect();
getInsetBounds(insetBounds);
- final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
- final Size overrideMinSize = mPipBoundsState.getOverrideMinSize();
- if (overrideMinSize != null) {
- // The override minimal size is set, use that as the default size making sure it's
- // adjusted to the aspect ratio.
- defaultSize = adjustSizeToAspectRatio(overrideMinSize, mDefaultAspectRatio);
- } else {
- // Calculate the default size using the display size and default min edge size.
- defaultSize = getSizeForAspectRatio(mDefaultAspectRatio,
- mDefaultMinSize, displayLayout.width(), displayLayout.height());
- }
+
+ // Calculate the default size
+ defaultSize = mPipSizeSpecHandler.getDefaultSize(mDefaultAspectRatio);
// Now that we have the default size, apply the snap fraction if valid or position the
// bounds using the default gravity.
@@ -335,12 +280,7 @@
* Populates the bounds on the screen that the PIP can be visible in.
*/
public void getInsetBounds(Rect outRect) {
- final DisplayLayout displayLayout = mPipBoundsState.getDisplayLayout();
- Rect insets = mPipBoundsState.getDisplayLayout().stableInsets();
- outRect.set(insets.left + mScreenEdgeInsets.x,
- insets.top + mScreenEdgeInsets.y,
- displayLayout.width() - insets.right - mScreenEdgeInsets.x,
- displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
+ outRect.set(mPipSizeSpecHandler.getInsetBounds());
}
/**
@@ -405,71 +345,11 @@
mSnapAlgorithm.applySnapFraction(stackBounds, movementBounds, snapFraction);
}
- public int getDefaultMinSize() {
- return mDefaultMinSize;
- }
-
/**
* @return the pixels for a given dp value.
*/
private int dpToPx(float dpValue, DisplayMetrics dm) {
- return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
- }
-
- /**
- * @return the size of the PiP at the given aspectRatio, ensuring that the minimum edge
- * is at least minEdgeSize.
- */
- public Size getSizeForAspectRatio(float aspectRatio, float minEdgeSize, int displayWidth,
- int displayHeight) {
- final int smallestDisplaySize = Math.min(displayWidth, displayHeight);
- final int minSize = (int) Math.max(minEdgeSize, smallestDisplaySize * mDefaultSizePercent);
-
- final int width;
- final int height;
- if (aspectRatio <= mMinAspectRatioForMinSize || aspectRatio > mMaxAspectRatioForMinSize) {
- // Beyond these points, we can just use the min size as the shorter edge
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
- } else {
- // Within these points, we ensure that the bounds fit within the radius of the limits
- // at the points
- final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
- final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
- height = (int) Math.round(Math.sqrt((radius * radius)
- / (aspectRatio * aspectRatio + 1)));
- width = Math.round(height * aspectRatio);
- }
- return new Size(width, height);
- }
-
- /**
- * @return the adjusted size so that it conforms to the given aspectRatio, ensuring that the
- * minimum edge is at least minEdgeSize.
- */
- public Size getSizeForAspectRatio(Size size, float aspectRatio, float minEdgeSize) {
- final int smallestSize = Math.min(size.getWidth(), size.getHeight());
- final int minSize = (int) Math.max(minEdgeSize, smallestSize);
-
- final int width;
- final int height;
- if (aspectRatio <= 1) {
- // Portrait, width is the minimum size.
- width = minSize;
- height = Math.round(width / aspectRatio);
- } else {
- // Landscape, height is the minimum size
- height = minSize;
- width = Math.round(height * aspectRatio);
- }
- return new Size(width, height);
+ return PipUtils.dpToPx(dpValue, dm);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 5376ae3..5be18d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -37,6 +37,7 @@
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.io.PrintWriter;
@@ -83,13 +84,10 @@
private int mStashedState = STASH_TYPE_NONE;
private int mStashOffset;
private @Nullable PipReentryState mPipReentryState;
+ private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
private @Nullable ComponentName mLastPipComponentName;
private int mDisplayId = Display.DEFAULT_DISPLAY;
private final @NonNull DisplayLayout mDisplayLayout = new DisplayLayout();
- /** The current minimum edge size of PIP. */
- private int mMinEdgeSize;
- /** The preferred minimum (and default) size specified by apps. */
- private @Nullable Size mOverrideMinSize;
private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
private boolean mIsImeShowing;
private int mImeHeight;
@@ -122,9 +120,10 @@
private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
- public PipBoundsState(@NonNull Context context) {
+ public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler) {
mContext = context;
reloadResources();
+ mPipSizeSpecHandler = pipSizeSpecHandler;
}
/** Reloads the resources. */
@@ -312,10 +311,10 @@
mDisplayLayout.set(displayLayout);
}
- /** Get the display layout. */
+ /** Get a copy of the display layout. */
@NonNull
public DisplayLayout getDisplayLayout() {
- return mDisplayLayout;
+ return new DisplayLayout(mDisplayLayout);
}
@VisibleForTesting
@@ -323,20 +322,10 @@
mPipReentryState = null;
}
- /** Set the PIP minimum edge size. */
- public void setMinEdgeSize(int minEdgeSize) {
- mMinEdgeSize = minEdgeSize;
- }
-
- /** Returns the PIP's current minimum edge size. */
- public int getMinEdgeSize() {
- return mMinEdgeSize;
- }
-
/** Sets the preferred size of PIP as specified by the activity in PIP mode. */
public void setOverrideMinSize(@Nullable Size overrideMinSize) {
- final boolean changed = !Objects.equals(overrideMinSize, mOverrideMinSize);
- mOverrideMinSize = overrideMinSize;
+ final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize());
+ mPipSizeSpecHandler.setOverrideMinSize(overrideMinSize);
if (changed && mOnMinimalSizeChangeCallback != null) {
mOnMinimalSizeChangeCallback.run();
}
@@ -345,13 +334,12 @@
/** Returns the preferred minimal size specified by the activity in PIP. */
@Nullable
public Size getOverrideMinSize() {
- return mOverrideMinSize;
+ return mPipSizeSpecHandler.getOverrideMinSize();
}
/** Returns the minimum edge size of the override minimum size, or 0 if not set. */
public int getOverrideMinEdgeSize() {
- if (mOverrideMinSize == null) return 0;
- return Math.min(mOverrideMinSize.getWidth(), mOverrideMinSize.getHeight());
+ return mPipSizeSpecHandler.getOverrideMinEdgeSize();
}
/** Get the state of the bounds in motion. */
@@ -581,11 +569,8 @@
pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
- pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
pw.println(innerPrefix + "mStashedState=" + mStashedState);
pw.println(innerPrefix + "mStashOffset=" + mStashOffset);
- pw.println(innerPrefix + "mMinEdgeSize=" + mMinEdgeSize);
- pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 283b1ec..480bf93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -16,11 +16,21 @@
package com.android.wm.shell.pip;
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.TaskSnapshot;
@@ -51,9 +61,11 @@
* Animates the internal {@link #mLeash} by a given fraction.
* @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
* call apply on this transaction, it should be applied on the caller side.
+ * @param currentBounds {@link Rect} of the current animation bounds.
* @param fraction progress of the animation ranged from 0f to 1f.
*/
- public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction);
+ public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+ Rect currentBounds, float fraction);
/**
* Callback when reaches the end of animation on the internal {@link #mLeash}.
@@ -66,13 +78,15 @@
/** A {@link PipContentOverlay} uses solid color. */
public static final class PipColorOverlay extends PipContentOverlay {
+ private static final String TAG = PipColorOverlay.class.getSimpleName();
+
private final Context mContext;
public PipColorOverlay(Context context) {
mContext = context;
mLeash = new SurfaceControl.Builder(new SurfaceSession())
- .setCallsite("PipAnimation")
- .setName(PipColorOverlay.class.getSimpleName())
+ .setCallsite(TAG)
+ .setName(TAG)
.setColorLayer()
.build();
}
@@ -88,7 +102,8 @@
}
@Override
- public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) {
+ public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+ Rect currentBounds, float fraction) {
atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
}
@@ -114,6 +129,8 @@
/** A {@link PipContentOverlay} uses {@link TaskSnapshot}. */
public static final class PipSnapshotOverlay extends PipContentOverlay {
+ private static final String TAG = PipSnapshotOverlay.class.getSimpleName();
+
private final TaskSnapshot mSnapshot;
private final Rect mSourceRectHint;
@@ -121,8 +138,8 @@
mSnapshot = snapshot;
mSourceRectHint = new Rect(sourceRectHint);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
- .setCallsite("PipAnimation")
- .setName(PipSnapshotOverlay.class.getSimpleName())
+ .setCallsite(TAG)
+ .setName(TAG)
.build();
}
@@ -143,7 +160,8 @@
}
@Override
- public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, float fraction) {
+ public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+ Rect currentBounds, float fraction) {
// Do nothing. Keep the snapshot till animation ends.
}
@@ -152,4 +170,113 @@
atomicTx.remove(mLeash);
}
}
+
+ /** A {@link PipContentOverlay} shows app icon on solid color background. */
+ public static final class PipAppIconOverlay extends PipContentOverlay {
+ private static final String TAG = PipAppIconOverlay.class.getSimpleName();
+ private static final int APP_ICON_SIZE_DP = 48;
+
+ private final Context mContext;
+ private final int mAppIconSizePx;
+ private final Rect mAppBounds;
+ private final Matrix mTmpTransform = new Matrix();
+ private final float[] mTmpFloat9 = new float[9];
+
+ private Bitmap mBitmap;
+
+ public PipAppIconOverlay(Context context, Rect appBounds, ActivityInfo activityInfo) {
+ mContext = context;
+ mAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, APP_ICON_SIZE_DP,
+ context.getResources().getDisplayMetrics());
+ mAppBounds = new Rect(appBounds);
+ mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(),
+ Bitmap.Config.ARGB_8888);
+ prepareAppIconOverlay(activityInfo);
+ mLeash = new SurfaceControl.Builder(new SurfaceSession())
+ .setCallsite(TAG)
+ .setName(TAG)
+ .build();
+ }
+
+ @Override
+ public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
+ tx.show(mLeash);
+ tx.setLayer(mLeash, Integer.MAX_VALUE);
+ tx.setBuffer(mLeash, mBitmap.getHardwareBuffer());
+ tx.reparent(mLeash, parentLeash);
+ tx.apply();
+ }
+
+ @Override
+ public void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
+ Rect currentBounds, float fraction) {
+ mTmpTransform.reset();
+ // Scale back the bitmap with the pivot point at center.
+ mTmpTransform.postScale(
+ (float) mAppBounds.width() / currentBounds.width(),
+ (float) mAppBounds.height() / currentBounds.height(),
+ mAppBounds.centerX(),
+ mAppBounds.centerY());
+ atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9)
+ .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
+ }
+
+ @Override
+ public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
+ atomicTx.remove(mLeash);
+ }
+
+ @Override
+ public void detach(SurfaceControl.Transaction tx) {
+ super.detach(tx);
+ if (mBitmap != null && !mBitmap.isRecycled()) {
+ mBitmap.recycle();
+ }
+ }
+
+ private void prepareAppIconOverlay(ActivityInfo activityInfo) {
+ final Canvas canvas = new Canvas();
+ canvas.setBitmap(mBitmap);
+ final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+ android.R.attr.colorBackground });
+ try {
+ int colorAccent = ta.getColor(0, 0);
+ canvas.drawRGB(
+ Color.red(colorAccent),
+ Color.green(colorAccent),
+ Color.blue(colorAccent));
+ } finally {
+ ta.recycle();
+ }
+ final Drawable appIcon = loadActivityInfoIcon(activityInfo,
+ mContext.getResources().getConfiguration().densityDpi);
+ final Rect appIconBounds = new Rect(
+ mAppBounds.centerX() - mAppIconSizePx / 2,
+ mAppBounds.centerY() - mAppIconSizePx / 2,
+ mAppBounds.centerX() + mAppIconSizePx / 2,
+ mAppBounds.centerY() + mAppIconSizePx / 2);
+ appIcon.setBounds(appIconBounds);
+ appIcon.draw(canvas);
+ mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */);
+ }
+
+ // Copied from com.android.launcher3.icons.IconProvider#loadActivityInfoIcon
+ private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) {
+ final int iconRes = ai.getIconResource();
+ Drawable icon = null;
+ // Get the preferred density icon from the app's resources
+ if (density != 0 && iconRes != 0) {
+ try {
+ final Resources resources = mContext.getPackageManager()
+ .getResourcesForApplication(ai.applicationInfo);
+ icon = resources.getDrawableForDensity(iconRes, density);
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException exc) { }
+ }
+ // Get the default density icon
+ if (icon == null) {
+ icon = ai.loadIcon(mContext.getPackageManager());
+ }
+ return icon;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 8ba2583..ffb2893 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -62,6 +62,7 @@
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.Log;
import android.view.Choreographer;
import android.view.Display;
@@ -78,11 +79,13 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.pip.phone.PipMotionHelper;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
@@ -125,6 +128,7 @@
private final Context mContext;
private final SyncTransactionQueue mSyncTransactionQueue;
private final PipBoundsState mPipBoundsState;
+ private final PipSizeSpecHandler mPipSizeSpecHandler;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final @NonNull PipMenuController mPipMenuController;
private final PipAnimationController mPipAnimationController;
@@ -312,6 +316,7 @@
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@@ -327,6 +332,7 @@
mSyncTransactionQueue = syncTransactionQueue;
mPipTransitionState = pipTransitionState;
mPipBoundsState = pipBoundsState;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
mPipBoundsAlgorithm = boundsHandler;
mPipMenuController = pipMenuController;
mPipTransitionController = pipTransitionController;
@@ -1568,7 +1574,13 @@
// Similar to auto-enter-pip transition, we use content overlay when there is no
// source rect hint to enter PiP use bounds animation.
if (sourceHintRect == null) {
- animator.setColorContentOverlay(mContext);
+ if (SystemProperties.getBoolean(
+ "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+ animator.setAppIconContentOverlay(
+ mContext, currentBounds, mTaskInfo.topActivityInfo);
+ } else {
+ animator.setColorContentOverlay(mContext);
+ }
} else {
final TaskSnapshot snapshot = PipUtils.getTaskSnapshot(
mTaskInfo.launchIntoPipHostTaskId, false /* isLowResolution */);
@@ -1594,7 +1606,12 @@
private @Nullable Rect computeRotatedBounds(int rotationDelta, int direction,
Rect outDestinationBounds, Rect sourceHintRect) {
if (direction == TRANSITION_DIRECTION_TO_PIP) {
- mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), mNextRotation);
+ DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+ layoutCopy.rotateTo(mContext.getResources(), mNextRotation);
+ mPipBoundsState.setDisplayLayout(layoutCopy);
+ mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+
final Rect displayBounds = mPipBoundsState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
// Transform the destination bounds to current display coordinates.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 83158ff..e5c0570 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -50,6 +50,7 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.SystemProperties;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -64,6 +65,8 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
@@ -82,6 +85,7 @@
private final Context mContext;
private final PipTransitionState mPipTransitionState;
+ private final PipSizeSpecHandler mPipSizeSpecHandler;
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
@@ -112,6 +116,7 @@
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipTransitionState pipTransitionState,
PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -122,6 +127,7 @@
pipBoundsAlgorithm, pipAnimationController);
mContext = context;
mPipTransitionState = pipTransitionState;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
@@ -307,7 +313,12 @@
// initial state under the new rotation.
int rotationDelta = deltaRotation(startRotation, endRotation);
if (rotationDelta != Surface.ROTATION_0) {
- mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
+ DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+ layoutCopy.rotateTo(mContext.getResources(), endRotation);
+ mPipBoundsState.setDisplayLayout(layoutCopy);
+ mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
wct.setBounds(mRequestedEnterTask, destinationBounds);
return true;
@@ -792,7 +803,13 @@
if (sourceHintRect == null) {
// We use content overlay when there is no source rect hint to enter PiP use bounds
// animation.
- animator.setColorContentOverlay(mContext);
+ if (SystemProperties.getBoolean(
+ "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+ animator.setAppIconContentOverlay(
+ mContext, currentBounds, taskInfo.topActivityInfo);
+ } else {
+ animator.setColorContentOverlay(mContext);
+ }
}
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
@@ -817,7 +834,12 @@
/** Computes destination bounds in old rotation and updates source hint rect if available. */
private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation,
TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) {
- mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), endRotation);
+ DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+ layoutCopy.rotateTo(mContext.getResources(), endRotation);
+ mPipBoundsState.setDisplayLayout(layoutCopy);
+ mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
+
final Rect displayBounds = mPipBoundsState.getDisplayBounds();
outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
// Transform the destination bounds to current display coordinates.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
index fa00619..8b98790 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
@@ -26,8 +27,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.os.RemoteException;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
+import android.util.TypedValue;
import android.window.TaskSnapshot;
import com.android.internal.protolog.common.ProtoLog;
@@ -70,6 +73,13 @@
}
/**
+ * @return the pixels for a given dp value.
+ */
+ public static int dpToPx(float dpValue, DisplayMetrics dm) {
+ return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, dm);
+ }
+
+ /**
* @return true if the aspect ratios differ
*/
public static boolean aspectRatioChanged(float aspectRatio1, float aspectRatio2) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 525beb1..4958b25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -137,6 +137,7 @@
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
+ private PipSizeSpecHandler mPipSizeSpecHandler;
private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
@@ -380,6 +381,7 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -401,11 +403,11 @@
return new PipController(context, shellInit, shellCommandHandler, shellController,
displayController, pipAnimationController, pipAppOpsListener,
- pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
- pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
- taskStackListener, pipParamsChangedForwarder, displayInsetsController,
- oneHandedController, mainExecutor)
+ pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
+ pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipTransitionState, pipTouchHandler, pipTransitionController,
+ windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
+ displayInsetsController, oneHandedController, mainExecutor)
.mImpl;
}
@@ -419,6 +421,7 @@
PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
@@ -444,6 +447,7 @@
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
mPipTransitionState = pipTransitionState;
@@ -512,7 +516,10 @@
// Ensure that we have the display info in case we get calls to update the bounds before the
// listener calls back
mPipBoundsState.setDisplayId(mContext.getDisplayId());
- mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
+
+ DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+ mPipSizeSpecHandler.setDisplayLayout(layout);
+ mPipBoundsState.setDisplayLayout(layout);
try {
mWindowManagerShellWrapper.addPinnedStackListener(mPinnedTaskListener);
@@ -686,6 +693,7 @@
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTouchHandler.onConfigurationChanged();
mPipBoundsState.onConfigurationChanged();
+ mPipSizeSpecHandler.onConfigurationChanged();
}
@Override
@@ -711,7 +719,11 @@
Runnable updateDisplayLayout = () -> {
final boolean fromRotation = Transitions.ENABLE_SHELL_TRANSITIONS
&& mPipBoundsState.getDisplayLayout().rotation() != layout.rotation();
+
+ // update the internal state of objects subscribed to display changes
+ mPipSizeSpecHandler.setDisplayLayout(layout);
mPipBoundsState.setDisplayLayout(layout);
+
final WindowContainerTransaction wct =
fromRotation ? new WindowContainerTransaction() : null;
updateMovementBounds(null /* toBounds */,
@@ -1020,7 +1032,11 @@
private void onDisplayRotationChangedNotInPip(Context context, int toRotation) {
// Update the display layout, note that we have to do this on every rotation even if we
// aren't in PIP since we need to update the display layout to get the right resources
- mPipBoundsState.getDisplayLayout().rotateTo(context.getResources(), toRotation);
+ DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+ layoutCopy.rotateTo(context.getResources(), toRotation);
+ mPipBoundsState.setDisplayLayout(layoutCopy);
+ mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
}
/**
@@ -1057,7 +1073,11 @@
mPipBoundsState.getStashedState());
// Update the display layout
- mPipBoundsState.getDisplayLayout().rotateTo(context.getResources(), toRotation);
+ DisplayLayout layoutCopy = mPipBoundsState.getDisplayLayout();
+
+ layoutCopy.rotateTo(context.getResources(), toRotation);
+ mPipBoundsState.setDisplayLayout(layoutCopy);
+ mPipSizeSpecHandler.setDisplayLayout(layoutCopy);
// Calculate the stack bounds in the new orientation based on same fraction along the
// rotated movement bounds.
@@ -1083,6 +1103,7 @@
mPipTaskOrganizer.dump(pw, innerPrefix);
mPipBoundsState.dump(pw, innerPrefix);
mPipInputConsumer.dump(pw, innerPrefix);
+ mPipSizeSpecHandler.dump(pw, innerPrefix);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
new file mode 100644
index 0000000..d03d075
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.phone;
+
+import static com.android.wm.shell.pip.PipUtils.dpToPx;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.SystemProperties;
+import android.util.Size;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.DisplayLayout;
+
+import java.io.PrintWriter;
+
+/**
+ * Acts as a source of truth for appropriate size spec for PIP.
+ */
+public class PipSizeSpecHandler {
+ private static final String TAG = PipSizeSpecHandler.class.getSimpleName();
+
+ @NonNull private final DisplayLayout mDisplayLayout = new DisplayLayout();
+
+ @VisibleForTesting
+ final SizeSpecSource mSizeSpecSourceImpl;
+
+ /** The preferred minimum (and default minimum) size specified by apps. */
+ @Nullable private Size mOverrideMinSize;
+ private int mOverridableMinSize;
+
+ /** Used to store values obtained from resource files. */
+ private Point mScreenEdgeInsets;
+ private float mMinAspectRatioForMinSize;
+ private float mMaxAspectRatioForMinSize;
+ private int mDefaultMinSize;
+
+ @NonNull private final Context mContext;
+
+ private interface SizeSpecSource {
+ /** Returns max size allowed for the PIP window */
+ Size getMaxSize(float aspectRatio);
+
+ /** Returns default size for the PIP window */
+ Size getDefaultSize(float aspectRatio);
+
+ /** Returns min size allowed for the PIP window */
+ Size getMinSize(float aspectRatio);
+
+ /** Returns the adjusted size based on current size and target aspect ratio */
+ Size getSizeForAspectRatio(Size size, float aspectRatio);
+
+ /** Updates internal resources on configuration changes */
+ default void reloadResources() {}
+ }
+
+ /**
+ * Determines PIP window size optimized for large screens and aspect ratios close to 1:1
+ */
+ private class SizeSpecLargeScreenOptimizedImpl implements SizeSpecSource {
+ private static final float DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16;
+
+ /** Default and minimum percentages for the PIP size logic. */
+ private final float mDefaultSizePercent;
+ private final float mMinimumSizePercent;
+
+ /** Aspect ratio that the PIP size spec logic optimizes for. */
+ private float mOptimizedAspectRatio;
+
+ private SizeSpecLargeScreenOptimizedImpl() {
+ mDefaultSizePercent = Float.parseFloat(SystemProperties
+ .get("com.android.wm.shell.pip.phone.def_percentage", "0.6"));
+ mMinimumSizePercent = Float.parseFloat(SystemProperties
+ .get("com.android.wm.shell.pip.phone.min_percentage", "0.5"));
+ }
+
+ @Override
+ public void reloadResources() {
+ final Resources res = mContext.getResources();
+
+ mOptimizedAspectRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio);
+ // make sure the optimized aspect ratio is valid with a default value to fall back to
+ if (mOptimizedAspectRatio > 1) {
+ mOptimizedAspectRatio = DEFAULT_OPTIMIZED_ASPECT_RATIO;
+ }
+ }
+
+ /**
+ * Calculates the max size of PIP.
+ *
+ * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
+ * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
+ * whole screen. A linear function is used to calculate these sizes.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the max size of the PIP
+ */
+ @Override
+ public Size getMaxSize(float aspectRatio) {
+ final int totalHorizontalPadding = getInsetBounds().left
+ + (getDisplayBounds().width() - getInsetBounds().right);
+ final int totalVerticalPadding = getInsetBounds().top
+ + (getDisplayBounds().height() - getInsetBounds().bottom);
+
+ final int shorterLength = (int) (1f * Math.min(
+ getDisplayBounds().width() - totalHorizontalPadding,
+ getDisplayBounds().height() - totalVerticalPadding));
+
+ int maxWidth, maxHeight;
+
+ // use the optimized max sizing logic only within a certain aspect ratio range
+ if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
+ // this formula and its derivation is explained in b/198643358#comment16
+ maxWidth = (int) (mOptimizedAspectRatio * shorterLength
+ + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
+ + aspectRatio));
+ maxHeight = (int) (maxWidth / aspectRatio);
+ } else {
+ if (aspectRatio > 1f) {
+ maxWidth = shorterLength;
+ maxHeight = (int) (maxWidth / aspectRatio);
+ } else {
+ maxHeight = shorterLength;
+ maxWidth = (int) (maxHeight * aspectRatio);
+ }
+ }
+
+ return new Size(maxWidth, maxHeight);
+ }
+
+ /**
+ * Decreases the dimensions by a percentage relative to max size to get default size.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the default size of the PIP
+ */
+ @Override
+ public Size getDefaultSize(float aspectRatio) {
+ Size minSize = this.getMinSize(aspectRatio);
+
+ if (mOverrideMinSize != null) {
+ return minSize;
+ }
+
+ Size maxSize = this.getMaxSize(aspectRatio);
+
+ int defaultWidth = Math.max((int) (maxSize.getWidth() * mDefaultSizePercent),
+ minSize.getWidth());
+ int defaultHeight = Math.max((int) (maxSize.getHeight() * mDefaultSizePercent),
+ minSize.getHeight());
+
+ return new Size(defaultWidth, defaultHeight);
+ }
+
+ /**
+ * Decreases the dimensions by a certain percentage relative to max size to get min size.
+ *
+ * @param aspectRatio aspect ratio of the PIP window
+ * @return dimensions of the min size of the PIP
+ */
+ @Override
+ public Size getMinSize(float aspectRatio) {
+ // if there is an overridden min size provided, return that
+ if (mOverrideMinSize != null) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio);
+ }
+
+ Size maxSize = this.getMaxSize(aspectRatio);
+
+ int minWidth = (int) (maxSize.getWidth() * mMinimumSizePercent);
+ int minHeight = (int) (maxSize.getHeight() * mMinimumSizePercent);
+
+ // make sure the calculated min size is not smaller than the allowed default min size
+ if (aspectRatio > 1f) {
+ minHeight = (int) Math.max(minHeight, mDefaultMinSize);
+ minWidth = (int) (minHeight * aspectRatio);
+ } else {
+ minWidth = (int) Math.max(minWidth, mDefaultMinSize);
+ minHeight = (int) (minWidth / aspectRatio);
+ }
+ return new Size(minWidth, minHeight);
+ }
+
+ /**
+ * Returns the size for target aspect ratio making sure new size conforms with the rules.
+ *
+ * <p>Recalculates the dimensions such that the target aspect ratio is achieved, while
+ * maintaining the same maximum size to current size ratio.
+ *
+ * @param size current size
+ * @param aspectRatio target aspect ratio
+ */
+ @Override
+ public Size getSizeForAspectRatio(Size size, float aspectRatio) {
+ // getting the percentage of the max size that current size takes
+ float currAspectRatio = (float) size.getWidth() / size.getHeight();
+ Size currentMaxSize = getMaxSize(currAspectRatio);
+ float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth();
+
+ // getting the max size for the target aspect ratio
+ Size updatedMaxSize = getMaxSize(aspectRatio);
+
+ int width = (int) (updatedMaxSize.getWidth() * currentPercent);
+ int height = (int) (updatedMaxSize.getHeight() * currentPercent);
+
+ // adjust the dimensions if below allowed min edge size
+ if (width < getMinEdgeSize() && aspectRatio <= 1) {
+ width = getMinEdgeSize();
+ height = (int) (width / aspectRatio);
+ } else if (height < getMinEdgeSize() && aspectRatio > 1) {
+ height = getMinEdgeSize();
+ width = (int) (height * aspectRatio);
+ }
+
+ // reduce the dimensions of the updated size to the calculated percentage
+ return new Size(width, height);
+ }
+ }
+
+ private class SizeSpecDefaultImpl implements SizeSpecSource {
+ private float mDefaultSizePercent;
+ private float mMinimumSizePercent;
+
+ @Override
+ public void reloadResources() {
+ final Resources res = mContext.getResources();
+
+ mMaxAspectRatioForMinSize = res.getFloat(
+ R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
+ mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
+
+ mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent);
+ mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
+ }
+
+ @Override
+ public Size getMaxSize(float aspectRatio) {
+ final int shorterLength = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height());
+
+ final int totalHorizontalPadding = getInsetBounds().left
+ + (getDisplayBounds().width() - getInsetBounds().right);
+ final int totalVerticalPadding = getInsetBounds().top
+ + (getDisplayBounds().height() - getInsetBounds().bottom);
+
+ final int maxWidth, maxHeight;
+
+ if (aspectRatio > 1f) {
+ maxWidth = (int) Math.max(getDefaultSize(aspectRatio).getWidth(),
+ shorterLength - totalHorizontalPadding);
+ maxHeight = (int) (maxWidth / aspectRatio);
+ } else {
+ maxHeight = (int) Math.max(getDefaultSize(aspectRatio).getHeight(),
+ shorterLength - totalVerticalPadding);
+ maxWidth = (int) (maxHeight * aspectRatio);
+ }
+
+ return new Size(maxWidth, maxHeight);
+ }
+
+ @Override
+ public Size getDefaultSize(float aspectRatio) {
+ if (mOverrideMinSize != null) {
+ return this.getMinSize(aspectRatio);
+ }
+
+ final int smallestDisplaySize = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height());
+ final int minSize = (int) Math.max(getMinEdgeSize(),
+ smallestDisplaySize * mDefaultSizePercent);
+
+ final int width;
+ final int height;
+
+ if (aspectRatio <= mMinAspectRatioForMinSize
+ || aspectRatio > mMaxAspectRatioForMinSize) {
+ // Beyond these points, we can just use the min size as the shorter edge
+ if (aspectRatio <= 1) {
+ // Portrait, width is the minimum size
+ width = minSize;
+ height = Math.round(width / aspectRatio);
+ } else {
+ // Landscape, height is the minimum size
+ height = minSize;
+ width = Math.round(height * aspectRatio);
+ }
+ } else {
+ // Within these points, ensure that the bounds fit within the radius of the limits
+ // at the points
+ final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
+ final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
+ height = (int) Math.round(Math.sqrt((radius * radius)
+ / (aspectRatio * aspectRatio + 1)));
+ width = Math.round(height * aspectRatio);
+ }
+
+ return new Size(width, height);
+ }
+
+ @Override
+ public Size getMinSize(float aspectRatio) {
+ if (mOverrideMinSize != null) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio);
+ }
+
+ final int shorterLength = Math.min(getDisplayBounds().width(),
+ getDisplayBounds().height());
+ final int minWidth, minHeight;
+
+ if (aspectRatio > 1f) {
+ minWidth = (int) Math.min(getDefaultSize(aspectRatio).getWidth(),
+ shorterLength * mMinimumSizePercent);
+ minHeight = (int) (minWidth / aspectRatio);
+ } else {
+ minHeight = (int) Math.min(getDefaultSize(aspectRatio).getHeight(),
+ shorterLength * mMinimumSizePercent);
+ minWidth = (int) (minHeight * aspectRatio);
+ }
+
+ return new Size(minWidth, minHeight);
+ }
+
+ @Override
+ public Size getSizeForAspectRatio(Size size, float aspectRatio) {
+ final int smallestSize = Math.min(size.getWidth(), size.getHeight());
+ final int minSize = Math.max(getMinEdgeSize(), smallestSize);
+
+ final int width;
+ final int height;
+ if (aspectRatio <= 1) {
+ // Portrait, width is the minimum size.
+ width = minSize;
+ height = Math.round(width / aspectRatio);
+ } else {
+ // Landscape, height is the minimum size
+ height = minSize;
+ width = Math.round(height * aspectRatio);
+ }
+
+ return new Size(width, height);
+ }
+ }
+
+ public PipSizeSpecHandler(Context context) {
+ mContext = context;
+
+ boolean enablePipSizeLargeScreen = SystemProperties
+ .getBoolean("persist.wm.debug.enable_pip_size_large_screen", false);
+
+ // choose between two implementations of size spec logic
+ if (enablePipSizeLargeScreen) {
+ mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
+ } else {
+ mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
+ }
+
+ reloadResources();
+ }
+
+ /** Reloads the resources */
+ public void onConfigurationChanged() {
+ reloadResources();
+ }
+
+ private void reloadResources() {
+ final Resources res = mContext.getResources();
+
+ mDefaultMinSize = res.getDimensionPixelSize(
+ R.dimen.default_minimal_size_pip_resizable_task);
+ mOverridableMinSize = res.getDimensionPixelSize(
+ R.dimen.overridable_minimal_size_pip_resizable_task);
+
+ final String screenEdgeInsetsDpString = res.getString(
+ R.string.config_defaultPictureInPictureScreenEdgeInsets);
+ final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
+ ? Size.parseSize(screenEdgeInsetsDpString)
+ : null;
+ mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
+ : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
+ dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
+
+ // update the internal resources of the size spec source's stub
+ mSizeSpecSourceImpl.reloadResources();
+ }
+
+ /** Returns the display's bounds. */
+ @NonNull
+ public Rect getDisplayBounds() {
+ return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
+ }
+
+ /** Update the display layout. */
+ public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
+ mDisplayLayout.set(displayLayout);
+ }
+
+ public Point getScreenEdgeInsets() {
+ return mScreenEdgeInsets;
+ }
+
+ /**
+ * Returns the inset bounds the PIP window can be visible in.
+ */
+ public Rect getInsetBounds() {
+ Rect insetBounds = new Rect();
+ Rect insets = mDisplayLayout.stableInsets();
+ insetBounds.set(insets.left + mScreenEdgeInsets.x,
+ insets.top + mScreenEdgeInsets.y,
+ mDisplayLayout.width() - insets.right - mScreenEdgeInsets.x,
+ mDisplayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
+ return insetBounds;
+ }
+
+ /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
+ public void setOverrideMinSize(@Nullable Size overrideMinSize) {
+ mOverrideMinSize = overrideMinSize;
+ }
+
+ /** Returns the preferred minimal size specified by the activity in PIP. */
+ @Nullable
+ public Size getOverrideMinSize() {
+ if (mOverrideMinSize != null
+ && (mOverrideMinSize.getWidth() < mOverridableMinSize
+ || mOverrideMinSize.getHeight() < mOverridableMinSize)) {
+ return new Size(mOverridableMinSize, mOverridableMinSize);
+ }
+
+ return mOverrideMinSize;
+ }
+
+ /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
+ public int getOverrideMinEdgeSize() {
+ if (mOverrideMinSize == null) return 0;
+ return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight());
+ }
+
+ public int getMinEdgeSize() {
+ return mOverrideMinSize == null ? mDefaultMinSize : getOverrideMinEdgeSize();
+ }
+
+ /**
+ * Returns the size for the max size spec.
+ */
+ public Size getMaxSize(float aspectRatio) {
+ return mSizeSpecSourceImpl.getMaxSize(aspectRatio);
+ }
+
+ /**
+ * Returns the size for the default size spec.
+ */
+ public Size getDefaultSize(float aspectRatio) {
+ return mSizeSpecSourceImpl.getDefaultSize(aspectRatio);
+ }
+
+ /**
+ * Returns the size for the min size spec.
+ */
+ public Size getMinSize(float aspectRatio) {
+ return mSizeSpecSourceImpl.getMinSize(aspectRatio);
+ }
+
+ /**
+ * Returns the adjusted size so that it conforms to the given aspectRatio.
+ *
+ * @param size current size
+ * @param aspectRatio target aspect ratio
+ */
+ public Size getSizeForAspectRatio(@NonNull Size size, float aspectRatio) {
+ if (size.equals(mOverrideMinSize)) {
+ return adjustOverrideMinSizeToAspectRatio(aspectRatio);
+ }
+
+ return mSizeSpecSourceImpl.getSizeForAspectRatio(size, aspectRatio);
+ }
+
+ /**
+ * Returns the adjusted overridden min size if it is set; otherwise, returns null.
+ *
+ * <p>Overridden min size needs to be adjusted in its own way while making sure that the target
+ * aspect ratio is maintained
+ *
+ * @param aspectRatio target aspect ratio
+ */
+ @Nullable
+ @VisibleForTesting
+ Size adjustOverrideMinSizeToAspectRatio(float aspectRatio) {
+ if (mOverrideMinSize == null) {
+ return null;
+ }
+ final Size size = getOverrideMinSize();
+ final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
+ if (sizeAspectRatio > aspectRatio) {
+ // Size is wider, fix the width and increase the height
+ return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
+ } else {
+ // Size is taller, fix the height and adjust the width.
+ return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
+ }
+ }
+
+ /** Dumps internal state. */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl.toString());
+ pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
+ pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 850c561..0e8d13d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -78,7 +78,8 @@
private boolean mEnableResize;
private final Context mContext;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
- private final @NonNull PipBoundsState mPipBoundsState;
+ @NonNull private final PipBoundsState mPipBoundsState;
+ @NonNull private final PipSizeSpecHandler mPipSizeSpecHandler;
private final PipUiEventLogger mPipUiEventLogger;
private final PipDismissTargetHandler mPipDismissTargetHandler;
private final PipTaskOrganizer mPipTaskOrganizer;
@@ -99,7 +100,6 @@
// The reference inset bounds, used to determine the dismiss fraction
private final Rect mInsetBounds = new Rect();
- private int mExpandedShortestEdgeSize;
// Used to workaround an issue where the WM rotation happens before we are notified, allowing
// us to send stale bounds
@@ -120,7 +120,6 @@
private float mSavedSnapFraction = -1f;
private boolean mSendingHoverAccessibilityEvents;
private boolean mMovementWithinDismiss;
- private float mMinimumSizePercent;
// Touch state
private final PipTouchState mTouchState;
@@ -174,6 +173,7 @@
PhonePipMenuController menuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@NonNull PipBoundsState pipBoundsState,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler,
PipTaskOrganizer pipTaskOrganizer,
PipMotionHelper pipMotionHelper,
FloatingContentCoordinator floatingContentCoordinator,
@@ -184,6 +184,7 @@
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
mPipTaskOrganizer = pipTaskOrganizer;
mMenuController = menuController;
mPipUiEventLogger = pipUiEventLogger;
@@ -271,10 +272,7 @@
private void reloadResources() {
final Resources res = mContext.getResources();
mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
- mExpandedShortestEdgeSize = res.getDimensionPixelSize(
- R.dimen.pip_expanded_shortest_edge_size);
mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
- mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
mPipDismissTargetHandler.updateMagneticTargetSize();
}
@@ -407,10 +405,7 @@
// Calculate the expanded size
float aspectRatio = (float) normalBounds.width() / normalBounds.height();
- Point displaySize = new Point();
- mContext.getDisplay().getRealSize(displaySize);
- Size expandedSize = mPipBoundsAlgorithm.getSizeForAspectRatio(
- aspectRatio, mExpandedShortestEdgeSize, displaySize.x, displaySize.y);
+ Size expandedSize = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
mPipBoundsState.setExpandedBounds(
new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
Rect expandedMovementBounds = new Rect();
@@ -418,7 +413,7 @@
mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
bottomOffset);
- updatePipSizeConstraints(insetBounds, normalBounds, aspectRatio);
+ updatePipSizeConstraints(normalBounds, aspectRatio);
// The extra offset does not really affect the movement bounds, but are applied based on the
// current state (ime showing, or shelf offset) when we need to actually shift
@@ -496,14 +491,14 @@
* @param aspectRatio aspect ratio to use for the calculation of min/max size
*/
public void updateMinMaxSize(float aspectRatio) {
- updatePipSizeConstraints(mInsetBounds, mPipBoundsState.getNormalBounds(),
+ updatePipSizeConstraints(mPipBoundsState.getNormalBounds(),
aspectRatio);
}
- private void updatePipSizeConstraints(Rect insetBounds, Rect normalBounds,
+ private void updatePipSizeConstraints(Rect normalBounds,
float aspectRatio) {
if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
- updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio);
+ updatePinchResizeSizeConstraints(aspectRatio);
} else {
mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
@@ -511,26 +506,13 @@
}
}
- private void updatePinchResizeSizeConstraints(Rect insetBounds, Rect normalBounds,
- float aspectRatio) {
- final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(),
- mPipBoundsState.getDisplayBounds().height());
- final int totalHorizontalPadding = insetBounds.left
- + (mPipBoundsState.getDisplayBounds().width() - insetBounds.right);
- final int totalVerticalPadding = insetBounds.top
- + (mPipBoundsState.getDisplayBounds().height() - insetBounds.bottom);
+ private void updatePinchResizeSizeConstraints(float aspectRatio) {
final int minWidth, minHeight, maxWidth, maxHeight;
- if (aspectRatio > 1f) {
- minWidth = (int) Math.min(normalBounds.width(), shorterLength * mMinimumSizePercent);
- minHeight = (int) (minWidth / aspectRatio);
- maxWidth = (int) Math.max(normalBounds.width(), shorterLength - totalHorizontalPadding);
- maxHeight = (int) (maxWidth / aspectRatio);
- } else {
- minHeight = (int) Math.min(normalBounds.height(), shorterLength * mMinimumSizePercent);
- minWidth = (int) (minHeight * aspectRatio);
- maxHeight = (int) Math.max(normalBounds.height(), shorterLength - totalVerticalPadding);
- maxWidth = (int) (maxHeight * aspectRatio);
- }
+
+ minWidth = mPipSizeSpecHandler.getMinSize(aspectRatio).getWidth();
+ minHeight = mPipSizeSpecHandler.getMinSize(aspectRatio).getHeight();
+ maxWidth = mPipSizeSpecHandler.getMaxSize(aspectRatio).getWidth();
+ maxHeight = mPipSizeSpecHandler.getMaxSize(aspectRatio).getHeight();
mPipResizeGestureHandler.updateMinSize(minWidth, minHeight);
mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight);
@@ -1064,11 +1046,6 @@
mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
mMotionHelper.onMovementBoundsChanged();
-
- boolean isMenuExpanded = mMenuState == MENU_STATE_FULL;
- mPipBoundsState.setMinEdgeSize(
- isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize
- : mPipBoundsAlgorithm.getDefaultMinSize());
}
private Rect getMovementBounds(Rect curBounds) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 1ff77f7..22feb43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -41,6 +41,7 @@
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
import com.android.wm.shell.pip.PipSnapAlgorithm;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -63,9 +64,10 @@
public TvPipBoundsAlgorithm(Context context,
@NonNull TvPipBoundsState tvPipBoundsState,
- @NonNull PipSnapAlgorithm pipSnapAlgorithm) {
+ @NonNull PipSnapAlgorithm pipSnapAlgorithm,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
super(context, tvPipBoundsState, pipSnapAlgorithm,
- new PipKeepClearAlgorithmInterface() {});
+ new PipKeepClearAlgorithmInterface() {}, pipSizeSpecHandler);
this.mTvPipBoundsState = tvPipBoundsState;
this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
reloadResources(context);
@@ -370,7 +372,8 @@
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_HORIZONTAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
- int maxHeight = displayLayout.height() - (2 * mScreenEdgeInsets.y)
+ int maxHeight = displayLayout.height()
+ - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().y)
- pipDecorations.top - pipDecorations.bottom;
float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
@@ -393,7 +396,8 @@
if (mTvPipBoundsState.getTvFixedPipOrientation() == ORIENTATION_VERTICAL) {
expandedSize = mTvPipBoundsState.getTvExpandedSize();
} else {
- int maxWidth = displayLayout.width() - (2 * mScreenEdgeInsets.x)
+ int maxWidth = displayLayout.width()
+ - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().x)
- pipDecorations.left - pipDecorations.right;
float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
if (maxWidth > aspectRatioWidth) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index ca22882..4e3ee51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -30,6 +30,7 @@
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -64,8 +65,9 @@
private @NonNull Insets mPipMenuPermanentDecorInsets = Insets.NONE;
private @NonNull Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
- public TvPipBoundsState(@NonNull Context context) {
- super(context);
+ public TvPipBoundsState(@NonNull Context context,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
+ super(context, pipSizeSpecHandler);
mIsTvExpandedPipSupported = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_EXPANDED_PICTURE_IN_PICTURE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 4e1b046..6bc666f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -50,6 +50,7 @@
import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellController;
@@ -102,6 +103,7 @@
private final ShellController mShellController;
private final TvPipBoundsState mTvPipBoundsState;
+ private final PipSizeSpecHandler mPipSizeSpecHandler;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final TvPipBoundsController mTvPipBoundsController;
private final PipAppOpsListener mAppOpsListener;
@@ -133,6 +135,7 @@
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -151,6 +154,7 @@
shellInit,
shellController,
tvPipBoundsState,
+ pipSizeSpecHandler,
tvPipBoundsAlgorithm,
tvPipBoundsController,
pipAppOpsListener,
@@ -171,6 +175,7 @@
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipAppOpsListener pipAppOpsListener,
@@ -189,9 +194,13 @@
mShellController = shellController;
mDisplayController = displayController;
+ DisplayLayout layout = new DisplayLayout(context, context.getDisplay());
+
mTvPipBoundsState = tvPipBoundsState;
+ mTvPipBoundsState.setDisplayLayout(layout);
mTvPipBoundsState.setDisplayId(context.getDisplayId());
- mTvPipBoundsState.setDisplayLayout(new DisplayLayout(context, context.getDisplay()));
+ mPipSizeSpecHandler = pipSizeSpecHandler;
+ mPipSizeSpecHandler.setDisplayLayout(layout);
mTvPipBoundsAlgorithm = tvPipBoundsAlgorithm;
mTvPipBoundsController = tvPipBoundsController;
mTvPipBoundsController.setListener(this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 42fd1aa..be9b936 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -36,6 +36,7 @@
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.PipUtils;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.Objects;
@@ -50,6 +51,7 @@
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@@ -61,10 +63,11 @@
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
ShellExecutor mainExecutor) {
- super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, boundsHandler,
- pipMenuController, pipAnimationController, surfaceTransactionHelper,
- pipTransitionController, pipParamsChangedForwarder, splitScreenOptional,
- displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ super(context, syncTransactionQueue, pipTransitionState, pipBoundsState, pipSizeSpecHandler,
+ boundsHandler, pipMenuController, pipAnimationController,
+ surfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
+ splitScreenOptional, displayController, pipUiEventLogger, shellTaskOrganizer,
+ mainExecutor);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 8490f9f..0d9faa3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -308,7 +308,6 @@
rawMapping.put(taskInfo.taskId, taskInfo);
}
- boolean desktopModeActive = DesktopModeStatus.isActive(mContext);
ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
// Pull out the pairs as we iterate back in the list
@@ -320,7 +319,7 @@
continue;
}
- if (desktopModeActive && mDesktopModeTaskRepository.isPresent()
+ if (DesktopModeStatus.isProto2Enabled() && mDesktopModeTaskRepository.isPresent()
&& mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
// Freeform tasks will be added as a separate entry
freeformTasks.add(taskInfo);
@@ -328,7 +327,7 @@
}
final int pairedTaskId = mSplitTasks.get(taskInfo.taskId);
- if (!desktopModeActive && pairedTaskId != INVALID_TASK_ID && rawMapping.contains(
+ if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(
pairedTaskId)) {
final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
rawMapping.remove(pairedTaskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 56aa742..81e118a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -122,9 +122,9 @@
* Start a pair of intents using legacy transition system.
*/
oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1,
- in Bundle options1, in PendingIntent pendingIntent2, in Bundle options2,
- int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
- in InstanceId instanceId) = 18;
+ in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2,
+ in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18;
/**
* Start a pair of intents in one transition.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 21eeaa2..36da4cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -28,6 +28,9 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
+import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
+import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
@@ -39,11 +42,8 @@
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.graphics.Rect;
import android.os.Bundle;
@@ -82,8 +82,8 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -318,10 +318,6 @@
return mStageCoordinator;
}
- public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
- return mStageCoordinator.isValidToEnterSplitScreen(taskInfo);
- }
-
@Nullable
public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) {
@@ -480,39 +476,54 @@
@Override
public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user) {
- IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to invoke onAnimationFinished", e);
- }
- final WindowContainerTransaction evictWct = new WindowContainerTransaction();
- mStageCoordinator.prepareEvictNonOpeningChildTasks(position, apps, evictWct);
- mSyncQueue.queue(evictWct);
+ if (options == null) options = new Bundle();
+ final ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+
+ if (samePackage(packageName, getPackageName(reverseSplitPosition(position)))) {
+ if (supportMultiInstancesSplit(packageName)) {
+ activityOptions.setApplyMultipleTaskFlagForShortcut(true);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition("startShortcut");
+ return;
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ return;
}
- @Override
- public void onAnimationCancelled(boolean isKeyguardOccluded) {
- }
- };
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
- null /* wct */);
- RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
- 0 /* duration */, 0 /* statusBarTransitionDelay */);
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
- try {
- LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
- launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
- activityOptions.toBundle(), user);
- } catch (ActivityNotFoundException e) {
- Slog.e(TAG, "Failed to launch shortcut", e);
}
+
+ mStageCoordinator.startShortcut(packageName, shortcutId, position,
+ activityOptions.toBundle(), user);
+ }
+
+ void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ if (options1 == null) options1 = new Bundle();
+ final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+
+ final String packageName1 = shortcutInfo.getPackage();
+ final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(shortcutInfo.getPackage())) {
+ activityOptions.setApplyMultipleTaskFlagForShortcut(true);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ taskId = INVALID_TASK_ID;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ mStageCoordinator.startShortcutAndTaskWithLegacyTransition(shortcutInfo,
+ activityOptions.toBundle(), taskId, options2, splitPosition, splitRatio, adapter,
+ instanceId);
}
/**
@@ -530,8 +541,10 @@
@SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
Intent fillInIntent = null;
- if (launchSameAppAdjacently(pendingIntent, taskId)) {
- if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
+ final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -551,8 +564,10 @@
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
Intent fillInIntent = null;
- if (launchSameAppAdjacently(pendingIntent, taskId)) {
- if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
+ final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -568,13 +583,16 @@
}
private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
- @Nullable Bundle options1, PendingIntent pendingIntent2,
- @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
Intent fillInIntent1 = null;
Intent fillInIntent2 = null;
- if (launchSameAppAdjacently(pendingIntent1, pendingIntent2)) {
- if (supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
+ final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
+ final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
fillInIntent2 = new Intent();
@@ -588,9 +606,9 @@
Toast.LENGTH_SHORT).show();
}
}
- mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
- pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
- instanceId);
+ mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1,
+ shortcutInfo1, options1, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
+ splitPosition, splitRatio, adapter, instanceId);
}
@Override
@@ -602,13 +620,15 @@
if (fillInIntent == null) fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
- if (launchSameAppAdjacently(position, intent)) {
- final ComponentName launching = intent.getIntent().getComponent();
- if (supportMultiInstancesSplit(launching)) {
+ final String packageName1 = SplitScreenUtils.getPackageName(intent);
+ final String packageName2 = getPackageName(reverseSplitPosition(position));
+ if (SplitScreenUtils.samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
// To prevent accumulating large number of instances in the background, reuse task
// in the background with priority.
final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.findTaskInBackground(launching))
+ .map(recentTasks -> recentTasks.findTaskInBackground(
+ intent.getIntent().getComponent()))
.orElse(null);
if (taskInfo != null) {
startTask(taskInfo.taskId, position, options);
@@ -636,63 +656,32 @@
mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
+ /** Retrieve package name of a specific split position if split screen is activated, otherwise
+ * returns the package name of the top running task. */
@Nullable
- private String getPackageName(Intent intent) {
- if (intent == null || intent.getComponent() == null) {
- return null;
- }
- return intent.getComponent().getPackageName();
- }
-
- private boolean launchSameAppAdjacently(@SplitPosition int position,
- PendingIntent pendingIntent) {
- ActivityManager.RunningTaskInfo adjacentTaskInfo = null;
+ private String getPackageName(@SplitPosition int position) {
+ ActivityManager.RunningTaskInfo taskInfo;
if (isSplitScreenVisible()) {
- adjacentTaskInfo = getTaskInfo(SplitLayout.reversePosition(position));
+ taskInfo = getTaskInfo(position);
} else {
- adjacentTaskInfo = mRecentTasksOptional
- .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null);
- if (!isValidToEnterSplitScreen(adjacentTaskInfo)) {
- return false;
+ taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.getTopRunningTask())
+ .orElse(null);
+ if (!isValidToSplit(taskInfo)) {
+ return null;
}
}
- if (adjacentTaskInfo == null) {
- return false;
- }
-
- final String targetPackageName = getPackageName(pendingIntent.getIntent());
- final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent);
- return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
- }
-
- private boolean launchSameAppAdjacently(PendingIntent pendingIntent, int taskId) {
- final ActivityManager.RunningTaskInfo adjacentTaskInfo =
- mTaskOrganizer.getRunningTaskInfo(taskId);
- if (adjacentTaskInfo == null) {
- return false;
- }
- final String targetPackageName = getPackageName(pendingIntent.getIntent());
- final String adjacentPackageName = getPackageName(adjacentTaskInfo.baseIntent);
- return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
- }
-
- private boolean launchSameAppAdjacently(PendingIntent pendingIntent1,
- PendingIntent pendingIntent2) {
- final String targetPackageName = getPackageName(pendingIntent1.getIntent());
- final String adjacentPackageName = getPackageName(pendingIntent2.getIntent());
- return targetPackageName != null && targetPackageName.equals(adjacentPackageName);
+ return taskInfo != null ? SplitScreenUtils.getPackageName(taskInfo.baseIntent) : null;
}
@VisibleForTesting
- /** Returns {@code true} if the component supports multi-instances split. */
- boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
- if (launching == null) return false;
-
- final String packageName = launching.getPackageName();
- for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
- if (mAppsSupportMultiInstances[i].equals(packageName)) {
- return true;
+ boolean supportMultiInstancesSplit(String packageName) {
+ if (packageName != null) {
+ for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
+ if (mAppsSupportMultiInstances[i].equals(packageName)) {
+ return true;
+ }
}
}
@@ -1011,7 +1000,7 @@
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startShortcutAndTaskWithLegacyTransition", (controller) ->
- controller.mStageCoordinator.startShortcutAndTaskWithLegacyTransition(
+ controller.startShortcutAndTaskWithLegacyTransition(
shortcutInfo, options1, taskId, options2, splitPosition,
splitRatio, adapter, instanceId));
}
@@ -1049,13 +1038,14 @@
@Override
public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
- @Nullable Bundle options1, PendingIntent pendingIntent2, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
(controller) ->
- controller.startIntentsWithLegacyTransition(
- pendingIntent1, options1, pendingIntent2, options2, splitPosition,
+ controller.startIntentsWithLegacyTransition(pendingIntent1, shortcutInfo1,
+ options1, pendingIntent2, shortcutInfo2, options2, splitPosition,
splitRatio, adapter, instanceId)
);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 39cf5f1..0f18dda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -36,12 +36,11 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -76,8 +75,10 @@
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -87,6 +88,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import android.view.Choreographer;
@@ -370,7 +372,7 @@
int sideStagePosition;
if (stageType == STAGE_TYPE_MAIN) {
targetStage = mMainStage;
- sideStagePosition = SplitLayout.reversePosition(stagePosition);
+ sideStagePosition = reverseSplitPosition(stagePosition);
} else if (stageType == STAGE_TYPE_SIDE) {
targetStage = mSideStage;
sideStagePosition = stagePosition;
@@ -428,6 +430,72 @@
return mLogger;
}
+ void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
+ Bundle options, UserHandle user) {
+ final boolean isEnteringSplit = !isSplitActive();
+
+ IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ final IRemoteAnimationFinishedCallback finishedCallback) {
+ boolean openingToSide = false;
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING
+ && mSideStage.containsTask(apps[i].taskId)) {
+ openingToSide = true;
+ break;
+ }
+ }
+ }
+
+ if (isEnteringSplit && !openingToSide) {
+ mMainExecutor.execute(() -> exitSplitScreen(
+ mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+ EXIT_REASON_UNKNOWN));
+ }
+
+ if (finishedCallback != null) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error finishing legacy transition: ", e);
+ }
+ }
+
+ if (!isEnteringSplit && openingToSide) {
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+ }
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ if (isEnteringSplit) {
+ mMainExecutor.execute(() -> exitSplitScreen(
+ mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+ EXIT_REASON_UNKNOWN));
+ }
+ }
+ };
+ options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
+ null /* wct */);
+ RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
+ 0 /* duration */, 0 /* statusBarTransitionDelay */);
+ ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+ activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
+ try {
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
+ activityOptions.toBundle(), user);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "Failed to launch shortcut", e);
+ }
+ }
+
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
@@ -624,9 +692,11 @@
/** Starts a pair of intents using legacy transition. */
void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1,
- @Nullable Bundle options1, PendingIntent pendingIntent2, Intent fillInIntent2,
- @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ @Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
+ @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (pendingIntent2 == null) {
@@ -635,15 +705,23 @@
activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
options1 = activityOptions.toBundle();
addActivityOptions(options1, null /* launchTarget */);
- wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ if (shortcutInfo1 != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+ } else {
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ }
mSyncQueue.queue(wct);
return;
}
addActivityOptions(options1, mSideStage);
- wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
- startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
- splitRatio, adapter, instanceId);
+ if (shortcutInfo1 != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+ } else {
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ }
+ startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
+ splitPosition, splitRatio, adapter, instanceId);
}
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
@@ -695,18 +773,19 @@
private void startWithLegacyTransition(WindowContainerTransaction wct,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
- @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
- mainOptions, sidePosition, splitRatio, adapter, instanceId);
+ mainShortcutInfo, mainOptions, sidePosition, splitRatio, adapter, instanceId);
}
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
@Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
- null /* mainFillInIntent */, mainOptions, sidePosition, splitRatio, adapter,
- instanceId);
+ null /* mainFillInIntent */, null /* mainShortcutInfo */, mainOptions, sidePosition,
+ splitRatio, adapter, instanceId);
}
/**
@@ -716,8 +795,9 @@
*/
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
- @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options,
+ @SplitPosition int sidePosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
if (!isSplitScreenVisible()) {
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
}
@@ -741,15 +821,19 @@
mMainStage.activate(wct, false /* reparent */);
}
- if (mainOptions == null) mainOptions = new Bundle();
- addActivityOptions(mainOptions, mMainStage);
- mainOptions = wrapAsSplitRemoteAnimation(adapter, mainOptions);
+ if (options == null) options = new Bundle();
+ addActivityOptions(options, mMainStage);
+ options = wrapAsSplitRemoteAnimation(adapter, options);
updateWindowBounds(mSplitLayout, wct);
- if (mainTaskId == INVALID_TASK_ID) {
- wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
+
+ // TODO(b/268008375): Merge APIs to start a split pair into one.
+ if (mainTaskId != INVALID_TASK_ID) {
+ wct.startTask(mainTaskId, options);
+ } else if (mainShortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
} else {
- wct.startTask(mainTaskId, mainOptions);
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
}
wct.reorder(mRootTaskInfo.token, true);
@@ -899,7 +983,7 @@
case STAGE_TYPE_MAIN: {
if (position != SPLIT_POSITION_UNDEFINED) {
// Set the side stage opposite of what we want to the main stage.
- setSideStagePosition(SplitLayout.reversePosition(position), wct);
+ setSideStagePosition(reverseSplitPosition(position), wct);
} else {
position = getMainStagePosition();
}
@@ -923,7 +1007,7 @@
@SplitPosition
int getMainStagePosition() {
- return SplitLayout.reversePosition(mSideStagePosition);
+ return reverseSplitPosition(mSideStagePosition);
}
int getTaskId(@SplitPosition int splitPosition) {
@@ -950,7 +1034,7 @@
mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash,
insets -> {
WindowContainerTransaction wct = new WindowContainerTransaction();
- setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), wct);
+ setSideStagePosition(reverseSplitPosition(mSideStagePosition), wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(st -> {
updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
@@ -1694,12 +1778,6 @@
}
}
- boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
- return taskInfo.supportsMultiWindow
- && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
- && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
- }
-
@Override
public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
final boolean mainStageToTop =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 606cf28..44e4a31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -156,6 +156,7 @@
}
decoration.relayout(taskInfo);
+ setupCaptionColor(taskInfo, decoration);
}
@Override
@@ -227,7 +228,11 @@
public void onClick(View v) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
- if (id == R.id.caption_handle) {
+ if (id == R.id.close_window || id == R.id.close_button) {
+ mTaskOperations.closeTask(mTaskToken);
+ } else if (id == R.id.back_button) {
+ mTaskOperations.injectBackKey();
+ } else if (id == R.id.caption_handle) {
decoration.createHandleMenu();
} else if (id == R.id.desktop_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
@@ -238,6 +243,8 @@
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
decoration.setButtonVisibility(false);
+ } else if (id == R.id.collapse_menu_button) {
+ decoration.closeHandleMenu();
}
}
@@ -301,18 +308,11 @@
mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
if (e.getRawY(dragPointerIdx) <= statusBarHeight) {
- if (DesktopModeStatus.isProto2Enabled()) {
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- // Switch a single task to fullscreen
- mDesktopTasksController.ifPresent(
- c -> c.moveToFullscreen(taskInfo));
- }
- } else if (DesktopModeStatus.isProto1Enabled()) {
- if (DesktopModeStatus.isActive(mContext)) {
- // Turn off desktop mode
- mDesktopModeController.ifPresent(
- c -> c.setDesktopModeActive(false));
- }
+ if (DesktopModeStatus.isProto2Enabled()
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ // Switch a single task to fullscreen
+ mDesktopTasksController.ifPresent(
+ c -> c.moveToFullscreen(taskInfo));
}
}
break;
@@ -402,10 +402,6 @@
|| focusedDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
handleCaptionThroughStatusBar(ev);
}
- } else if (DesktopModeStatus.isProto1Enabled()) {
- if (!DesktopModeStatus.isActive(mContext)) {
- handleCaptionThroughStatusBar(ev);
- }
}
handleEventOutsideFocusedCaption(ev);
// Prevent status bar from reacting to a caption drag.
@@ -451,9 +447,6 @@
// In proto2 any full screen task can be dragged to freeform
dragFromStatusBarAllowed = focusedDecor.mTaskInfo.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN;
- } else if (DesktopModeStatus.isProto1Enabled()) {
- // In proto1 task can be dragged to freeform when not in desktop mode
- dragFromStatusBarAllowed = !DesktopModeStatus.isActive(mContext);
}
if (dragFromStatusBarAllowed && focusedDecor.checkTouchEventInHandle(ev)) {
@@ -522,9 +515,16 @@
}
}
+ private void setupCaptionColor(RunningTaskInfo taskInfo,
+ DesktopModeWindowDecoration decoration) {
+ if (taskInfo == null || taskInfo.taskDescription == null) return;
+ final int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
+ decoration.setCaptionColor(statusBarColor);
+ }
+
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
- return DesktopModeStatus.isAnyEnabled()
+ return DesktopModeStatus.isProto2Enabled()
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
@@ -560,6 +560,7 @@
windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT);
+ setupCaptionColor(taskInfo, windowDecoration);
incrementEventReceiverTasks(taskInfo.displayId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 744c18f..245cc8d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -20,18 +20,25 @@
import android.app.ActivityManager;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PointF;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
+import android.util.Log;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
+import android.widget.ImageView;
+import android.widget.TextView;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.R;
@@ -48,6 +55,7 @@
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
*/
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
+ private static final String TAG = "DesktopModeWindowDecoration";
private final Handler mHandler;
private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
@@ -59,12 +67,14 @@
private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
- private final int mCaptionMenuWidthId = R.dimen.freeform_decor_caption_menu_width;
+ private final int mCaptionMenuHeightId = R.dimen.freeform_decor_caption_menu_height;
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
new WindowDecoration.RelayoutResult<>();
private boolean mDesktopActive;
private AdditionalWindow mHandleMenu;
+ private final int mHandleMenuWidthId = R.dimen.freeform_decor_caption_menu_width;
+ private PointF mHandleMenuPosition = new PointF();
DesktopModeWindowDecoration(
Context context,
@@ -211,21 +221,46 @@
final View fullscreen = menu.findViewById(R.id.fullscreen_button);
fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
final View desktop = menu.findViewById(R.id.desktop_button);
- desktop.setOnClickListener(mOnCaptionButtonClickListener);
+ if (DesktopModeStatus.isProto2Enabled()) {
+ desktop.setOnClickListener(mOnCaptionButtonClickListener);
+ } else if (DesktopModeStatus.isProto1Enabled()) {
+ desktop.setVisibility(View.GONE);
+ }
final View split = menu.findViewById(R.id.split_screen_button);
split.setOnClickListener(mOnCaptionButtonClickListener);
+ final View close = menu.findViewById(R.id.close_button);
+ close.setOnClickListener(mOnCaptionButtonClickListener);
+ final View collapse = menu.findViewById(R.id.collapse_menu_button);
+ collapse.setOnClickListener(mOnCaptionButtonClickListener);
+ menu.setOnTouchListener(mOnCaptionTouchListener);
+
+ String packageName = mTaskInfo.baseActivity.getPackageName();
+ PackageManager pm = mContext.getApplicationContext().getPackageManager();
+ // TODO(b/268363572): Use IconProvider or BaseIconCache to set drawable/name.
+ try {
+ ApplicationInfo applicationInfo = pm.getApplicationInfo(packageName,
+ PackageManager.ApplicationInfoFlags.of(0));
+ final ImageView appIcon = menu.findViewById(R.id.application_icon);
+ appIcon.setImageDrawable(pm.getApplicationIcon(applicationInfo));
+ final TextView appName = menu.findViewById(R.id.application_name);
+ appName.setText(pm.getApplicationLabel(applicationInfo));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Package not found: " + packageName, e);
+ }
}
/**
* Sets caption visibility based on task focus.
- *
+ * Note: Only applicable to Desktop Proto 1; Proto 2 only closes handle menu on focus loss
* @param visible whether or not the caption should be visible
*/
private void setCaptionVisibility(boolean visible) {
+ if (!visible) closeHandleMenu();
+ if (!DesktopModeStatus.isProto1Enabled()) return;
final int v = visible ? View.VISIBLE : View.GONE;
final View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
captionView.setVisibility(v);
- if (!visible) closeHandleMenu();
+
}
/**
@@ -244,8 +279,13 @@
* Show or hide buttons
*/
void setButtonVisibility(boolean visible) {
- final int visibility = visible ? View.VISIBLE : View.GONE;
+ final int visibility = visible && DesktopModeStatus.isProto1Enabled()
+ ? View.VISIBLE : View.GONE;
final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+ final View back = caption.findViewById(R.id.back_button);
+ final View close = caption.findViewById(R.id.close_window);
+ back.setVisibility(visibility);
+ close.setVisibility(visibility);
final int buttonTintColorRes =
mDesktopActive ? R.color.decor_button_dark_color
: R.color.decor_button_light_color;
@@ -254,13 +294,33 @@
final View handle = caption.findViewById(R.id.caption_handle);
final VectorDrawable handleBackground = (VectorDrawable) handle.getBackground();
handleBackground.setTintList(buttonTintColor);
- caption.getBackground().setTint(visible ? Color.WHITE : Color.TRANSPARENT);
}
boolean isHandleMenuActive() {
return mHandleMenu != null;
}
+ void setCaptionColor(int captionColor) {
+ if (mResult.mRootView == null) {
+ return;
+ }
+
+ final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
+ final GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
+ captionDrawable.setColor(captionColor);
+
+ final int buttonTintColorRes =
+ Color.valueOf(captionColor).luminance() < 0.5
+ ? R.color.decor_button_light_color
+ : R.color.decor_button_dark_color;
+ final ColorStateList buttonTintColor =
+ caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
+
+ final View handle = caption.findViewById(R.id.caption_handle);
+ final Drawable handleBackground = handle.getBackground();
+ handleBackground.setTintList(buttonTintColor);
+ }
+
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
@@ -277,16 +337,21 @@
final Resources resources = mDecorWindowContext.getResources();
final int captionWidth = mTaskInfo.getConfiguration()
.windowConfiguration.getBounds().width();
- final int menuWidth = loadDimensionPixelSize(
- resources, mCaptionMenuWidthId);
- final int height = loadDimensionPixelSize(
- resources, mRelayoutParams.mCaptionHeightId);
+ final int menuWidth = loadDimensionPixelSize(resources, mHandleMenuWidthId);
+ final int menuHeight = loadDimensionPixelSize(resources, mCaptionMenuHeightId);
+
+ // Elevation gives the appearance of a changed x/y coordinate; this is to fix that
+ int elevationOffset = 2 * loadDimensionPixelSize(resources,
+ R.dimen.caption_menu_elevation);
+
final int x = mRelayoutParams.mCaptionX + (captionWidth / 2) - (menuWidth / 2)
- - mResult.mDecorContainerOffsetX;
- final int y = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY;
+ - mResult.mDecorContainerOffsetX - elevationOffset;
+ final int y =
+ mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY - elevationOffset;
+ mHandleMenuPosition.set(x, y);
String namePrefix = "Caption Menu";
mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t, x, y,
- menuWidth, height);
+ menuWidth, menuHeight, 2 * elevationOffset);
mSyncQueue.runInSync(transaction -> {
transaction.merge(t);
t.close();
@@ -315,10 +380,17 @@
* @param ev the tapped point to compare against
*/
void closeHandleMenuIfNeeded(MotionEvent ev) {
- if (isHandleMenuActive()) {
- if (!checkEventInCaptionView(ev, R.id.desktop_mode_caption)) {
- closeHandleMenu();
- }
+ if (!isHandleMenuActive()) return;
+
+ // When this is called before the layout is fully inflated, width will be 0.
+ // Menu is not visible in this scenario, so skip the check if that is the case.
+ if (mHandleMenu.mWindowViewHost.getView().getWidth() == 0) return;
+
+ PointF inputPoint = offsetCaptionLocation(ev);
+ if (!pointInView(mHandleMenu.mWindowViewHost.getView(),
+ inputPoint.x - mHandleMenuPosition.x - mResult.mDecorContainerOffsetX,
+ inputPoint.y - mHandleMenuPosition.y - mResult.mDecorContainerOffsetY)) {
+ closeHandleMenu();
}
}
@@ -380,12 +452,8 @@
final int menuX = mRelayoutParams.mCaptionX + (captionWidth / 2)
- (menu.getWidth() / 2);
final PointF inputPoint = new PointF(ev.getX() - menuX, ev.getY());
- final View fullscreen = menu.findViewById(R.id.fullscreen_button);
- if (clickIfPointInView(inputPoint, fullscreen)) return;
- final View desktop = menu.findViewById(R.id.desktop_button);
- if (clickIfPointInView(inputPoint, desktop)) return;
- final View split = menu.findViewById(R.id.split_screen_button);
- if (clickIfPointInView(inputPoint, split)) return;
+ final View collapse = menu.findViewById(R.id.collapse_menu_button);
+ if (clickIfPointInView(inputPoint, collapse)) return;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 62b72f3..ae685ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -391,10 +391,11 @@
* @param yPos y position of new window
* @param width width of new window
* @param height height of new window
+ * @param cropPadding padding to add to window crop to ensure shadows display properly
* @return
*/
- AdditionalWindow addWindow(int layoutId, String namePrefix,
- SurfaceControl.Transaction t, int xPos, int yPos, int width, int height) {
+ AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
+ int xPos, int yPos, int width, int height, int cropPadding) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
SurfaceControl windowSurfaceControl = builder
.setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -405,7 +406,7 @@
t.setPosition(
windowSurfaceControl, xPos, yPos)
- .setWindowCrop(windowSurfaceControl, width, height)
+ .setWindowCrop(windowSurfaceControl, width + cropPadding, height + cropPadding)
.show(windowSurfaceControl);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(width, height,
@@ -426,6 +427,7 @@
RunningTaskInfo mRunningTaskInfo;
int mLayoutResId;
int mCaptionHeightId;
+ int mCaptionWidthId;
int mShadowRadiusId;
int mOutsetTopId;
@@ -451,6 +453,7 @@
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
+ mCaptionWidthId = Resources.ID_NULL;
mShadowRadiusId = Resources.ID_NULL;
mOutsetTopId = Resources.ID_NULL;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 7997a7e..43f8f7b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
@@ -446,7 +447,7 @@
final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
WindowContainerTransaction.class);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), arg.capture(), any());
+ verify(mTransitions).startTransition(eq(TRANSIT_NONE), arg.capture(), any());
} else {
verify(mShellTaskOrganizer).applyTransaction(arg.capture());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index f16beee..95e78a8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -26,6 +26,8 @@
import android.os.Binder
import android.testing.AndroidTestingRunner
import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionRequestInfo
@@ -55,6 +57,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
import org.mockito.Mockito
@@ -141,7 +144,7 @@
controller.showDesktopApps()
- val wct = getLatestWct()
+ val wct = getLatestWct(expectTransition = TRANSIT_NONE)
assertThat(wct.hierarchyOps).hasSize(3)
// Expect order to be from bottom: home, task1, task2
wct.assertReorderAt(index = 0, homeTask)
@@ -159,7 +162,7 @@
controller.showDesktopApps()
- val wct = getLatestWct()
+ val wct = getLatestWct(expectTransition = TRANSIT_NONE)
assertThat(wct.hierarchyOps).hasSize(3)
// Expect order to be from bottom: home, task1, task2
wct.assertReorderAt(index = 0, homeTask)
@@ -177,7 +180,7 @@
controller.showDesktopApps()
- val wct = getLatestWct()
+ val wct = getLatestWct(expectTransition = TRANSIT_NONE)
assertThat(wct.hierarchyOps).hasSize(3)
// Expect order to be from bottom: home, task1, task2
wct.assertReorderAt(index = 0, homeTask)
@@ -191,7 +194,7 @@
controller.showDesktopApps()
- val wct = getLatestWct()
+ val wct = getLatestWct(expectTransition = TRANSIT_NONE)
assertThat(wct.hierarchyOps).hasSize(1)
wct.assertReorderAt(index = 0, homeTask)
}
@@ -221,7 +224,7 @@
fun moveToDesktop() {
val task = setUpFullscreenTask()
controller.moveToDesktop(task)
- val wct = getLatestWct()
+ val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
}
@@ -241,7 +244,7 @@
controller.moveToDesktop(fullscreenTask)
- with(getLatestWct()) {
+ with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
assertThat(hierarchyOps).hasSize(3)
assertReorderSequence(homeTask, freeformTask, fullscreenTask)
assertThat(changes[fullscreenTask.token.asBinder()]?.windowingMode)
@@ -253,7 +256,7 @@
fun moveToFullscreen() {
val task = setUpFreeformTask()
controller.moveToFullscreen(task)
- val wct = getLatestWct()
+ val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FULLSCREEN)
}
@@ -415,10 +418,12 @@
desktopModeTaskRepository.updateVisibleFreeformTasks(task.taskId, visible = false)
}
- private fun getLatestWct(): WindowContainerTransaction {
+ private fun getLatestWct(
+ @WindowManager.TransitionType expectTransition: Int = TRANSIT_OPEN
+ ): WindowContainerTransaction {
val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
if (ENABLE_SHELL_TRANSITIONS) {
- verify(transitions).startTransition(anyInt(), arg.capture(), isNull())
+ verify(transitions).startTransition(eq(expectTransition), arg.capture(), isNull())
} else {
verify(shellTaskOrganizer).applyTransaction(arg.capture())
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 298d0a6..ec264a6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -32,6 +32,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import org.junit.Before;
import org.junit.Test;
@@ -57,17 +58,22 @@
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private DisplayInfo mDefaultDisplayInfo;
private PipBoundsState mPipBoundsState;
+ private PipSizeSpecHandler mPipSizeSpecHandler;
@Before
public void setUp() throws Exception {
initializeMockResources();
- mPipBoundsState = new PipBoundsState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
- new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {});
+ new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
+ mPipSizeSpecHandler);
- mPipBoundsState.setDisplayLayout(
- new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true));
+ DisplayLayout layout =
+ new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
+ mPipBoundsState.setDisplayLayout(layout);
+ mPipSizeSpecHandler.setDisplayLayout(layout);
}
private void initializeMockResources() {
@@ -120,9 +126,7 @@
@Test
public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
- final Size defaultSize = mPipBoundsAlgorithm.getSizeForAspectRatio(DEFAULT_ASPECT_RATIO,
- DEFAULT_MIN_EDGE_SIZE, mDefaultDisplayInfo.logicalWidth,
- mDefaultDisplayInfo.logicalHeight);
+ final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO);
mPipBoundsState.setOverrideMinSize(null);
final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
@@ -296,9 +300,9 @@
(MAX_ASPECT_RATIO + DEFAULT_ASPECT_RATIO) / 2
};
final Size[] minimalSizes = new Size[] {
- new Size((int) (100 * aspectRatios[0]), 100),
- new Size((int) (100 * aspectRatios[1]), 100),
- new Size((int) (100 * aspectRatios[2]), 100)
+ new Size((int) (200 * aspectRatios[0]), 200),
+ new Size((int) (200 * aspectRatios[1]), 200),
+ new Size((int) (200 * aspectRatios[2]), 200)
};
for (int i = 0; i < aspectRatios.length; i++) {
final float aspectRatio = aspectRatios[i];
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index 8e30f65..341a451 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -33,6 +33,7 @@
import com.android.internal.util.function.TriConsumer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import org.junit.Before;
import org.junit.Test;
@@ -57,7 +58,7 @@
@Before
public void setUp() {
- mPipBoundsState = new PipBoundsState(mContext);
+ mPipBoundsState = new PipBoundsState(mContext, new PipSizeSpecHandler(mContext));
mTestComponentName1 = new ComponentName(mContext, "component1");
mTestComponentName2 = new ComponentName(mContext, "component2");
}
@@ -161,10 +162,10 @@
@Test
public void testSetOverrideMinSize_notChanged_callbackNotInvoked() {
final Runnable callback = mock(Runnable.class);
- mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+ mPipBoundsState.setOverrideMinSize(new Size(100, 150));
mPipBoundsState.setOnMinimalSizeChangeCallback(callback);
- mPipBoundsState.setOverrideMinSize(new Size(5, 5));
+ mPipBoundsState.setOverrideMinSize(new Size(100, 150));
verify(callback, never()).run();
}
@@ -174,11 +175,11 @@
mPipBoundsState.setOverrideMinSize(null);
assertEquals(0, mPipBoundsState.getOverrideMinEdgeSize());
- mPipBoundsState.setOverrideMinSize(new Size(5, 10));
- assertEquals(5, mPipBoundsState.getOverrideMinEdgeSize());
+ mPipBoundsState.setOverrideMinSize(new Size(100, 110));
+ assertEquals(100, mPipBoundsState.getOverrideMinEdgeSize());
- mPipBoundsState.setOverrideMinSize(new Size(15, 10));
- assertEquals(10, mPipBoundsState.getOverrideMinEdgeSize());
+ mPipBoundsState.setOverrideMinSize(new Size(150, 200));
+ assertEquals(150, mPipBoundsState.getOverrideMinEdgeSize());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 17e7d74..e907cd3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -53,6 +53,7 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
+import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
import com.android.wm.shell.splitscreen.SplitScreenController;
import org.junit.Before;
@@ -86,6 +87,7 @@
private PipBoundsState mPipBoundsState;
private PipTransitionState mPipTransitionState;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private PipSizeSpecHandler mPipSizeSpecHandler;
private ComponentName mComponent1;
private ComponentName mComponent2;
@@ -95,13 +97,15 @@
MockitoAnnotations.initMocks(this);
mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2");
- mPipBoundsState = new PipBoundsState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
mPipTransitionState = new PipTransitionState();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
- new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {});
+ new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
+ mPipSizeSpecHandler);
mMainExecutor = new TestShellExecutor();
- mPipTaskOrganizer = new PipTaskOrganizer(mContext,
- mMockSyncTransactionQueue, mPipTransitionState, mPipBoundsState,
+ mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue,
+ mPipTransitionState, mPipBoundsState, mPipSizeSpecHandler,
mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
mMockPipParamsChangedForwarder, mMockOptionalSplitScreen, mMockDisplayController,
@@ -253,8 +257,10 @@
private void preparePipTaskOrg() {
final DisplayInfo info = new DisplayInfo();
- mPipBoundsState.setDisplayLayout(new DisplayLayout(info,
- mContext.getResources(), true, true));
+ DisplayLayout layout = new DisplayLayout(info,
+ mContext.getResources(), true, true);
+ mPipBoundsState.setDisplayLayout(layout);
+ mPipSizeSpecHandler.setDisplayLayout(layout);
mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
mPipTaskOrganizer.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 35c09a1..4a68287 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -65,7 +65,6 @@
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
-import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -108,6 +107,7 @@
@Mock private PipMotionHelper mMockPipMotionHelper;
@Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
@Mock private PipBoundsState mMockPipBoundsState;
+ @Mock private PipSizeSpecHandler mMockPipSizeSpecHandler;
@Mock private TaskStackListenerImpl mMockTaskStackListener;
@Mock private ShellExecutor mMockExecutor;
@Mock private Optional<OneHandedController> mMockOneHandedController;
@@ -130,11 +130,12 @@
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
- mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
- mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mMockPipParamsChangedForwarder,
- mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor);
+ mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
+ mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
+ mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
+ mMockWindowManagerShellWrapper, mMockTaskStackListener,
+ mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
+ mMockOneHandedController, mMockExecutor);
mShellInit.init();
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper);
@@ -220,11 +221,12 @@
assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
mShellController, mMockDisplayController, mMockPipAnimationController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
- mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
- mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
- mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
- mMockTaskStackListener, mMockPipParamsChangedForwarder,
- mMockDisplayInsetsController, mMockOneHandedController, mMockExecutor));
+ mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipMotionHelper,
+ mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer,
+ mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController,
+ mMockWindowManagerShellWrapper, mMockTaskStackListener,
+ mMockPipParamsChangedForwarder, mMockDisplayInsetsController,
+ mMockOneHandedController, mMockExecutor));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index c1993b2..c7b9eb3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -85,15 +85,18 @@
private PipBoundsState mPipBoundsState;
+ private PipSizeSpecHandler mPipSizeSpecHandler;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPipBoundsState = new PipBoundsState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
new PipKeepClearAlgorithmInterface() {};
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
- mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm);
+ mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipSizeSpecHandler);
final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
new file mode 100644
index 0000000..d9ff7d1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.phone;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.testing.AndroidTestingRunner;
+import android.util.Size;
+import android.view.DisplayInfo;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.exceptions.misusing.InvalidUseOfMatchersException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Unit test against {@link PipSizeSpecHandler} with feature flag on.
+ */
+@RunWith(AndroidTestingRunner.class)
+public class PipSizeSpecHandlerTest extends ShellTestCase {
+ /** A sample overridden min edge size. */
+ private static final int OVERRIDE_MIN_EDGE_SIZE = 40;
+ /** A sample default min edge size */
+ private static final int DEFAULT_MIN_EDGE_SIZE = 40;
+ /** Display edge size */
+ private static final int DISPLAY_EDGE_SIZE = 1000;
+ /** Default sizing percentage */
+ private static final float DEFAULT_PERCENT = 0.6f;
+ /** Minimum sizing percentage */
+ private static final float MIN_PERCENT = 0.5f;
+ /** Aspect ratio that the new PIP size spec logic optimizes for. */
+ private static final float OPTIMIZED_ASPECT_RATIO = 9f / 16;
+
+ /** A map of aspect ratios to be tested to expected sizes */
+ private static Map<Float, Size> sExpectedMaxSizes;
+ private static Map<Float, Size> sExpectedDefaultSizes;
+ private static Map<Float, Size> sExpectedMinSizes;
+ /** A static mockito session object to mock {@link SystemProperties} */
+ private static StaticMockitoSession sStaticMockitoSession;
+
+ @Mock private Context mContext;
+ @Mock private Resources mResources;
+
+ private PipSizeSpecHandler mPipSizeSpecHandler;
+
+ /**
+ * Sets up static Mockito session for SystemProperties and mocks necessary static methods.
+ */
+ private static void setUpStaticSystemPropertiesSession() {
+ sStaticMockitoSession = mockitoSession()
+ .mockStatic(SystemProperties.class).startMocking();
+ // make sure the feature flag is on
+ when(SystemProperties.getBoolean(anyString(), anyBoolean())).thenReturn(true);
+ when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
+ String property = invocation.getArgument(0);
+ if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
+ return Float.toString(DEFAULT_PERCENT);
+ } else if (property.equals("com.android.wm.shell.pip.phone.min_percentage")) {
+ return Float.toString(MIN_PERCENT);
+ }
+
+ // throw an exception if illegal arguments are used for these tests
+ throw new InvalidUseOfMatchersException(
+ String.format("Argument %s does not match", property)
+ );
+ });
+ }
+
+ /**
+ * Initializes the map with the aspect ratios to be tested and corresponding expected max sizes.
+ */
+ private static void initExpectedSizes() {
+ sExpectedMaxSizes = new HashMap<>();
+ sExpectedDefaultSizes = new HashMap<>();
+ sExpectedMinSizes = new HashMap<>();
+
+ sExpectedMaxSizes.put(16f / 9, new Size(1000, 562));
+ sExpectedDefaultSizes.put(16f / 9, new Size(600, 337));
+ sExpectedMinSizes.put(16f / 9, new Size(499, 281));
+
+ sExpectedMaxSizes.put(4f / 3, new Size(892, 669));
+ sExpectedDefaultSizes.put(4f / 3, new Size(535, 401));
+ sExpectedMinSizes.put(4f / 3, new Size(445, 334));
+
+ sExpectedMaxSizes.put(3f / 4, new Size(669, 892));
+ sExpectedDefaultSizes.put(3f / 4, new Size(401, 535));
+ sExpectedMinSizes.put(3f / 4, new Size(334, 445));
+
+ sExpectedMaxSizes.put(9f / 16, new Size(562, 999));
+ sExpectedDefaultSizes.put(9f / 16, new Size(337, 599));
+ sExpectedMinSizes.put(9f / 16, new Size(281, 499));
+ }
+
+ private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
+ Function<Float, Size> callback) {
+ for (Map.Entry<Float, Size> expectedSizesEntry : expectedSizes.entrySet()) {
+ float aspectRatio = expectedSizesEntry.getKey();
+ Size expectedSize = expectedSizesEntry.getValue();
+
+ Assert.assertEquals(expectedSize, callback.apply(aspectRatio));
+ }
+ }
+
+ @Before
+ public void setUp() {
+ initExpectedSizes();
+ setUpStaticSystemPropertiesSession();
+
+ when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE);
+ when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO);
+ when(mResources.getString(anyInt())).thenReturn("0x0");
+ when(mResources.getDisplayMetrics())
+ .thenReturn(getContext().getResources().getDisplayMetrics());
+
+ // set up the mock context for spec handler specifically
+ when(mContext.getResources()).thenReturn(mResources);
+
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+
+ // no overridden min edge size by default
+ mPipSizeSpecHandler.setOverrideMinSize(null);
+
+ DisplayInfo displayInfo = new DisplayInfo();
+ displayInfo.logicalWidth = DISPLAY_EDGE_SIZE;
+ displayInfo.logicalHeight = DISPLAY_EDGE_SIZE;
+
+ // use the parent context (not the mocked one) to obtain the display layout
+ // this is done to avoid unnecessary mocking while allowing for custom display dimensions
+ DisplayLayout displayLayout = new DisplayLayout(displayInfo, getContext().getResources(),
+ false, false);
+ mPipSizeSpecHandler.setDisplayLayout(displayLayout);
+ }
+
+ @After
+ public void cleanUp() {
+ sStaticMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testGetMaxSize() {
+ forEveryTestCaseCheck(sExpectedMaxSizes,
+ (aspectRatio) -> mPipSizeSpecHandler.getMaxSize(aspectRatio));
+ }
+
+ @Test
+ public void testGetDefaultSize() {
+ forEveryTestCaseCheck(sExpectedDefaultSizes,
+ (aspectRatio) -> mPipSizeSpecHandler.getDefaultSize(aspectRatio));
+ }
+
+ @Test
+ public void testGetMinSize() {
+ forEveryTestCaseCheck(sExpectedMinSizes,
+ (aspectRatio) -> mPipSizeSpecHandler.getMinSize(aspectRatio));
+ }
+
+ @Test
+ public void testGetSizeForAspectRatio_noOverrideMinSize() {
+ // an initial size with 16:9 aspect ratio
+ Size initSize = new Size(600, 337);
+
+ Size expectedSize = new Size(337, 599);
+ Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+
+ Assert.assertEquals(expectedSize, actualSize);
+ }
+
+ @Test
+ public void testGetSizeForAspectRatio_withOverrideMinSize() {
+ // an initial size with a 1:1 aspect ratio
+ mPipSizeSpecHandler.setOverrideMinSize(new Size(OVERRIDE_MIN_EDGE_SIZE,
+ OVERRIDE_MIN_EDGE_SIZE));
+ // make sure initial size is same as override min size
+ Size initSize = mPipSizeSpecHandler.getOverrideMinSize();
+
+ Size expectedSize = new Size(40, 71);
+ Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+
+ Assert.assertEquals(expectedSize, actualSize);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 8ad2932..5c4863f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -25,6 +25,7 @@
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.Size;
import androidx.test.filters.SmallTest;
@@ -90,6 +91,7 @@
private PipSnapAlgorithm mPipSnapAlgorithm;
private PipMotionHelper mMotionHelper;
private PipResizeGestureHandler mPipResizeGestureHandler;
+ private PipSizeSpecHandler mPipSizeSpecHandler;
private DisplayLayout mDisplayLayout;
private Rect mInsetBounds;
@@ -103,16 +105,17 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mPipBoundsState = new PipBoundsState(mContext);
+ mPipSizeSpecHandler = new PipSizeSpecHandler(mContext);
+ mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler);
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
- new PipKeepClearAlgorithmInterface() {});
+ new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler);
PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
- mPipBoundsAlgorithm, mPipBoundsState, mPipTaskOrganizer, pipMotionHelper,
- mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
+ mPipBoundsAlgorithm, mPipBoundsState, mPipSizeSpecHandler, mPipTaskOrganizer,
+ pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
// We aren't actually using ShellInit, so just call init directly
mPipTouchHandler.onInit();
mMotionHelper = Mockito.spy(mPipTouchHandler.getMotionHelper());
@@ -122,6 +125,7 @@
mDisplayLayout = new DisplayLayout(mContext, mContext.getDisplay());
mPipBoundsState.setDisplayLayout(mDisplayLayout);
+ mPipSizeSpecHandler.setDisplayLayout(mDisplayLayout);
mInsetBounds = new Rect(mPipBoundsState.getDisplayBounds().left + INSET,
mPipBoundsState.getDisplayBounds().top + INSET,
mPipBoundsState.getDisplayBounds().right - INSET,
@@ -154,13 +158,17 @@
mPipTouchHandler.onMovementBoundsChanged(mInsetBounds, mPipBounds, mCurBounds,
mFromImeAdjustment, mFromShelfAdjustment, mDisplayRotation);
+ // getting the expected min and max size
+ float aspectRatio = (float) mPipBounds.width() / mPipBounds.height();
+ Size expectedMinSize = mPipSizeSpecHandler.getMinSize(aspectRatio);
+ Size expectedMaxSize = mPipSizeSpecHandler.getMaxSize(aspectRatio);
+
assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds());
verify(mPipResizeGestureHandler, times(1))
- .updateMinSize(mPipBounds.width(), mPipBounds.height());
+ .updateMinSize(expectedMinSize.getWidth(), expectedMinSize.getHeight());
verify(mPipResizeGestureHandler, times(1))
- .updateMaxSize(shorterLength - 2 * mInsetBounds.left,
- shorterLength - 2 * mInsetBounds.left);
+ .updateMaxSize(expectedMaxSize.getWidth(), expectedMaxSize.getHeight());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 82392ad9..b542fae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -253,10 +253,10 @@
}
@Test
- public void testGetRecentTasks_groupActiveFreeformTasks() {
+ public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopModeStatus.class).startMocking();
- when(DesktopModeStatus.isActive(any())).thenReturn(true);
+ when(DesktopModeStatus.isProto2Enabled()).thenReturn(true);
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -293,6 +293,39 @@
}
@Test
+ public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() {
+ StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
+ DesktopModeStatus.class).startMocking();
+ when(DesktopModeStatus.isProto2Enabled()).thenReturn(false);
+
+ ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+ ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+ ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+ setRawList(t1, t2, t3, t4);
+
+ when(mDesktopModeTaskRepository.isActiveTask(1)).thenReturn(true);
+ when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
+
+ ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
+ MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+
+ // Expect no grouping of tasks
+ assertEquals(4, recentTasks.size());
+ assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(0).getType());
+ assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(1).getType());
+ assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(2).getType());
+ assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(3).getType());
+
+ assertEquals(t1, recentTasks.get(0).getTaskInfo1());
+ assertEquals(t2, recentTasks.get(1).getTaskInfo1());
+ assertEquals(t3, recentTasks.get(2).getTaskInfo1());
+ assertEquals(t4, recentTasks.get(3).getTaskInfo1());
+
+ mockitoSession.finishMocking();
+ }
+
+ @Test
public void testRemovedTaskRemovesSplit() {
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index ea3af9d..d0e2601 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -27,8 +27,6 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -201,7 +199,6 @@
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
- doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
@@ -222,7 +219,6 @@
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
- doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 2f5263c..b80edce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -431,7 +431,7 @@
verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
verify(additionalWindowSurfaceBuilder).build();
verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
- verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, 432, 64);
+ verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, 442, 74);
verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
.create(any(), eq(defaultDisplay), any());
@@ -565,7 +565,7 @@
mMockSurfaceControlAddWindowT,
x - mRelayoutResult.mDecorContainerOffsetX,
y - mRelayoutResult.mDecorContainerOffsetY,
- width, height);
+ width, height, 10);
return additionalWindow;
}
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 75d3ff7..602554a 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -537,7 +537,7 @@
const auto inputEventId =
static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId));
native_window_set_frame_timeline_info(
- mNativeSurface->getNativeWindow(), vsyncId, inputEventId,
+ mNativeSurface->getNativeWindow(), frameCompleteNr, vsyncId, inputEventId,
mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime));
}
}
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index ded9597..a5757b9 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -1275,7 +1275,10 @@
|| (preset == MediaRecorder.AudioSource.VOICE_UPLINK)
|| (preset == MediaRecorder.AudioSource.VOICE_CALL)
|| (preset == MediaRecorder.AudioSource.ECHO_REFERENCE)
- || (preset == MediaRecorder.AudioSource.ULTRASOUND)) {
+ || (preset == MediaRecorder.AudioSource.ULTRASOUND)
+ // AUDIO_SOURCE_INVALID is used by convention on default initialized
+ // audio attributes
+ || (preset == MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID)) {
mSource = preset;
} else {
setCapturePreset(preset);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 2c139b7..67efe3a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1409,7 +1409,7 @@
public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) {
Preconditions.checkNotNull(attributes, "Audio Attributes must not be null");
return AudioProductStrategy.getVolumeGroupIdForAudioAttributes(attributes,
- /* fallbackOnDefault= */ false);
+ /* fallbackOnDefault= */ true);
}
/**
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 1bf56c0..9d398ad 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -193,7 +193,7 @@
<string name="tts_lang_use_system" msgid="6312945299804012406">"استفاده از زبان سیستم"</string>
<string name="tts_lang_not_selected" msgid="7927823081096056147">"زبان انتخاب نشده است"</string>
<string name="tts_default_lang_summary" msgid="9042620014800063470">"صدای خاص یک زبان را برای متن گفتاری تنظیم میکند"</string>
- <string name="tts_play_example_title" msgid="1599468547216481684">"به نمونهای گوش کنید"</string>
+ <string name="tts_play_example_title" msgid="1599468547216481684">"شنیدن نمونه"</string>
<string name="tts_play_example_summary" msgid="634044730710636383">"قسمت کوتاهی از ترکیب گفتار پخش شود"</string>
<string name="tts_install_data_title" msgid="1829942496472751703">"نصب دادههای صوتی"</string>
<string name="tts_install_data_summary" msgid="3608874324992243851">"نصب دادههای صوتی مورد نیاز برای ترکیب گفتار"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index d69f0cb..09586be 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -190,7 +190,7 @@
<string name="tts_default_pitch_title" msgid="6988592215554485479">"Ton"</string>
<string name="tts_default_pitch_summary" msgid="9132719475281551884">"Afecta ao ton da voz sintetizada"</string>
<string name="tts_default_lang_title" msgid="4698933575028098940">"Idioma"</string>
- <string name="tts_lang_use_system" msgid="6312945299804012406">"Utiliza o idioma do sistema"</string>
+ <string name="tts_lang_use_system" msgid="6312945299804012406">"Utilizar o idioma do sistema"</string>
<string name="tts_lang_not_selected" msgid="7927823081096056147">"Idioma non seleccionado"</string>
<string name="tts_default_lang_summary" msgid="9042620014800063470">"Define a voz específica do idioma para o texto falado"</string>
<string name="tts_play_example_title" msgid="1599468547216481684">"Escoitar un exemplo"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 7f9d858..d548222 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -325,7 +325,7 @@
<string name="select_usb_configuration_dialog_title" msgid="3579567144722589237">"Chọn cấu hình USB"</string>
<string name="allow_mock_location" msgid="2102650981552527884">"Cho phép vị trí mô phỏng"</string>
<string name="allow_mock_location_summary" msgid="179780881081354579">"Cho phép vị trí mô phỏng"</string>
- <string name="debug_view_attributes" msgid="3539609843984208216">"Cho phép kiểm tra thuộc tính của chế độ xem"</string>
+ <string name="debug_view_attributes" msgid="3539609843984208216">"Cho phép kiểm tra thuộc tính khung hiển thị"</string>
<string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Luôn bật dữ liệu di động ngay cả khi Wi-Fi đang hoạt động (để chuyển đổi mạng nhanh)."</string>
<string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Sử dụng tính năng tăng tốc phần cứng khi chia sẻ Internet nếu có"</string>
<string name="adb_warning_title" msgid="7708653449506485728">"Cho phép gỡ lỗi qua USB?"</string>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 663e8e4..ef664e9 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -660,4 +660,18 @@
<!-- Content descriptions for each of the images in the avatar_images array. -->
<string-array name="avatar_image_descriptions"/>
+ <string-array name="entries_font_size">
+ <item msgid="6490061470416867723">Small</item>
+ <item msgid="3579015730662088893">Default</item>
+ <item msgid="1678068858001018666">Large</item>
+ <item msgid="490158884605093126">Largest</item>
+ </string-array>
+
+ <string-array name="entryvalues_font_size" translatable="false">
+ <item>0.85</item>
+ <item>1.0</item>
+ <item>1.15</item>
+ <item>1.30</item>
+ </string-array>
+
</resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 211030a..2afcf71 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -130,6 +130,7 @@
Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS,
Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
Settings.Secure.VR_DISPLAY_MODE,
Settings.Secure.NOTIFICATION_BADGING,
Settings.Secure.NOTIFICATION_DISMISS_RTL,
@@ -218,6 +219,7 @@
Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED,
Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
+ Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 0539f09..53ae926 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -189,6 +189,8 @@
VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR);
@@ -350,5 +352,6 @@
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_CODE, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 3b862ff..3915012 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -356,6 +356,9 @@
<!-- Permission needed to test wallpaper dimming -->
<uses-permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT" />
+ <!-- Permission needed to test wallpaper read methods -->
+ <uses-permission android:name="android.permission.READ_WALLPAPER_INTERNAL" />
+
<!-- Permission required to test ContentResolver caching. -->
<uses-permission android:name="android.permission.CACHE_CONTENT" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2529157..ed62c5f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -204,6 +204,45 @@
path: "tests/utils/src",
}
+filegroup {
+ name: "SystemUI-tests-robolectric-pilots",
+ srcs: [
+ // data
+ "tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt",
+ "tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt",
+ "tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt",
+ "tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt",
+ "tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt",
+ "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt",
+ "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt",
+ "tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt",
+ "tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt",
+ "tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt",
+ "tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt",
+ "tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt",
+ // domain
+ "tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt",
+ "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
+ "tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt",
+ "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
+ "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt",
+ "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt",
+ "tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt",
+ "tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt",
+ "tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
+ "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
+ "tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
+ // ui
+ "tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt",
+ "tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt",
+ "tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt",
+ "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt",
+ "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt",
+ "tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt",
+ ],
+ path: "tests/src",
+}
+
java_library {
name: "SystemUI-tests-concurrency",
srcs: [
@@ -315,8 +354,16 @@
defaults: [
"platform_app_defaults",
"SystemUI_app_defaults",
+ "SystemUI_compose_defaults",
],
manifest: "tests/AndroidManifest-base.xml",
+
+ srcs: [
+ "src/**/*.kt",
+ "src/**/*.java",
+ "src/**/I*.aidl",
+ ":ReleaseJavaFiles",
+ ],
static_libs: [
"SystemUI-tests-base",
],
@@ -330,6 +377,9 @@
certificate: "platform",
privileged: true,
resource_dirs: [],
+ kotlincflags: ["-Xjvm-default=all"],
+
+ plugins: ["dagger2-compiler"],
}
android_robolectric_test {
@@ -337,6 +387,13 @@
srcs: [
"tests/robolectric/src/**/*.kt",
"tests/robolectric/src/**/*.java",
+ ":SystemUI-tests-utils",
+ ":SystemUI-tests-robolectric-pilots",
+ ],
+ static_libs: [
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.test.ext.junit",
+ "inline-mockito-robolectric-prebuilt",
],
libs: [
"android.test.runner",
@@ -344,7 +401,9 @@
"android.test.mock",
"truth-prebuilt",
],
- kotlincflags: ["-Xjvm-default=enable"],
+
+ upstream: true,
+
instrumentation_for: "SystemUIRobo-stub",
java_resource_dirs: ["tests/robolectric/config"],
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index e96aead5..71a82bf 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -68,6 +68,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
@@ -899,7 +900,7 @@
android:showWhenLocked="true"
android:showForAllUsers="true"
android:finishOnTaskLaunch="true"
- android:launchMode="singleInstance"
+ android:lockTaskMode="if_whitelisted"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
android:visibleToInstantApps="true">
</activity>
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 8acc2f8..978ab5d 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -35,7 +35,6 @@
],
static_libs: [
- "PluginCoreLib",
"androidx.core_core-animation-nodeps",
"androidx.core_core-ktx",
"androidx.annotation_annotation",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index b8d78fb..5aa7769 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -304,10 +304,16 @@
) {
val view =
openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground
- ?: throw IllegalStateException(
- "The animateFrom dialog was not animated using " +
- "DialogLaunchAnimator.showFrom(View|Dialog)"
- )
+ if (view == null) {
+ Log.w(
+ TAG,
+ "Showing dialog $dialog normally as the dialog it is shown from was not shown " +
+ "using DialogLaunchAnimator"
+ )
+ dialog.show()
+ return
+ }
+
showFromView(
dialog,
view,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index 7897934..442c6fa 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
@@ -66,11 +66,28 @@
fun isPlaying(): Boolean = animator.isRunning
private fun applyConfigToShader() {
- rippleShader.setCenter(config.centerX, config.centerY)
- rippleShader.setMaxSize(config.maxWidth, config.maxHeight)
- rippleShader.rippleFill = config.shouldFillRipple
- rippleShader.pixelDensity = config.pixelDensity
- rippleShader.color = ColorUtils.setAlphaComponent(config.color, config.opacity)
- rippleShader.sparkleStrength = config.sparkleStrength
+ with(rippleShader) {
+ setCenter(config.centerX, config.centerY)
+ setMaxSize(config.maxWidth, config.maxHeight)
+ pixelDensity = config.pixelDensity
+ color = ColorUtils.setAlphaComponent(config.color, config.opacity)
+ sparkleStrength = config.sparkleStrength
+
+ assignFadeParams(baseRingFadeParams, config.baseRingFadeParams)
+ assignFadeParams(sparkleRingFadeParams, config.sparkleRingFadeParams)
+ assignFadeParams(centerFillFadeParams, config.centerFillFadeParams)
+ }
+ }
+
+ private fun assignFadeParams(
+ destFadeParams: RippleShader.FadeParams,
+ srcFadeParams: RippleShader.FadeParams?
+ ) {
+ srcFadeParams?.let {
+ destFadeParams.fadeInStart = it.fadeInStart
+ destFadeParams.fadeInEnd = it.fadeInEnd
+ destFadeParams.fadeOutStart = it.fadeOutStart
+ destFadeParams.fadeOutEnd = it.fadeOutEnd
+ }
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
index 773ac55..1786d13 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
@@ -20,8 +20,11 @@
val pixelDensity: Float = 1f,
var color: Int = Color.WHITE,
val opacity: Int = RIPPLE_DEFAULT_ALPHA,
- val shouldFillRipple: Boolean = false,
val sparkleStrength: Float = RIPPLE_SPARKLE_STRENGTH,
+ // Null means it uses default fade parameter values.
+ val baseRingFadeParams: RippleShader.FadeParams? = null,
+ val sparkleRingFadeParams: RippleShader.FadeParams? = null,
+ val centerFillFadeParams: RippleShader.FadeParams? = null,
val shouldDistort: Boolean = true
) {
companion object {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index c764d53..61ca90a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -82,12 +82,12 @@
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
float radius = in_size.x * 0.5;
float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
- float inside = soften(sdCircle(p_distorted-in_center, radius * 1.2), in_blur);
+ float inside = soften(sdCircle(p_distorted-in_center, radius * 1.25), in_blur);
float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
* (1.-sparkleRing) * in_fadeSparkle;
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
- float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing * in_sparkle_strength;
+ float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
@@ -270,38 +270,6 @@
var currentHeight: Float = 0f
private set
- /**
- * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
- * False for a ring effect.
- *
- * <p>You must reset fade params after changing this.
- *
- * TODO(b/265326983): Remove this and only expose fade params.
- */
- var rippleFill: Boolean = false
- set(value) {
- if (value) {
- baseRingFadeParams.fadeOutStart = 1f
- baseRingFadeParams.fadeOutEnd = 1f
-
- centerFillFadeParams.fadeInStart = 0f
- centerFillFadeParams.fadeInEnd = 0f
- centerFillFadeParams.fadeOutStart = 1f
- centerFillFadeParams.fadeOutEnd = 1f
- } else {
- // Set back to the original fade parameters.
- // Ideally this should be set by the client as they know the initial value.
- baseRingFadeParams.fadeOutStart = DEFAULT_BASE_RING_FADE_OUT_START
- baseRingFadeParams.fadeOutEnd = DEFAULT_FADE_OUT_END
-
- centerFillFadeParams.fadeInStart = DEFAULT_FADE_IN_START
- centerFillFadeParams.fadeInEnd = DEFAULT_CENTER_FILL_FADE_IN_END
- centerFillFadeParams.fadeOutStart = DEFAULT_CENTER_FILL_FADE_OUT_START
- centerFillFadeParams.fadeOutEnd = DEFAULT_CENTER_FILL_FADE_OUT_END
- }
- field = value
- }
-
/** Parameters that are used to fade in/ out of the sparkle ring. */
val sparkleRingFadeParams =
FadeParams(
@@ -324,12 +292,7 @@
DEFAULT_FADE_OUT_END
)
- /**
- * Parameters that are used to fade in/ out of the center fill.
- *
- * <p>Note that if [rippleFill] is set to true, those will be ignored and the center fill will
- * be always full alpha.
- */
+ /** Parameters that are used to fade in/ out of the center fill. */
val centerFillFadeParams =
FadeParams(
DEFAULT_FADE_IN_START,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index bc4796a..3c9328c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -117,15 +117,6 @@
rippleShader.color = ColorUtils.setAlphaComponent(color, alpha)
}
- /**
- * Set whether the ripple should remain filled as the ripple expands.
- *
- * See [RippleShader.rippleFill].
- */
- fun setRippleFill(rippleFill: Boolean) {
- rippleShader.rippleFill = rippleFill
- }
-
/** Set the intensity of the sparkles. */
fun setSparkleStrength(strength: Float) {
rippleShader.sparkleStrength = strength
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index a523cf1..ed6e619 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -13,11 +13,13 @@
*/
package com.android.systemui.shared.clocks
+import android.app.ActivityManager
+import android.app.UserSwitchObserver
import android.content.Context
import android.database.ContentObserver
import android.graphics.drawable.Drawable
import android.net.Uri
-import android.os.Handler
+import android.os.UserHandle
import android.provider.Settings
import android.util.Log
import androidx.annotation.OpenForTesting
@@ -29,17 +31,23 @@
import com.android.systemui.plugins.ClockSettings
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
+import com.android.systemui.util.Assert
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
-private val TAG = ClockRegistry::class.simpleName
+private val TAG = ClockRegistry::class.simpleName!!
private const val DEBUG = true
/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
val context: Context,
val pluginManager: PluginManager,
- val handler: Handler,
+ val scope: CoroutineScope,
+ val mainDispatcher: CoroutineDispatcher,
+ val bgDispatcher: CoroutineDispatcher,
val isEnabled: Boolean,
- userHandle: Int,
+ val handleAllUsers: Boolean,
defaultClockProvider: ClockProvider,
val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
) {
@@ -50,66 +58,132 @@
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
- private val settingObserver = object : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
- clockChangeListeners.forEach { it.onClockChanged() }
- }
+ private val settingObserver =
+ object : ContentObserver(null) {
+ override fun onChange(
+ selfChange: Boolean,
+ uris: Collection<Uri>,
+ flags: Int,
+ userId: Int
+ ) {
+ scope.launch(bgDispatcher) { querySettings() }
+ }
+ }
- private val pluginListener = object : PluginListener<ClockProviderPlugin> {
- override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
- connectClocks(plugin)
+ private val pluginListener =
+ object : PluginListener<ClockProviderPlugin> {
+ override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
+ connectClocks(plugin)
- override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
- disconnectClocks(plugin)
- }
+ override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
+ disconnectClocks(plugin)
+ }
- open var settings: ClockSettings?
- get() {
+ private val userSwitchObserver =
+ object : UserSwitchObserver() {
+ override fun onUserSwitchComplete(newUserId: Int) {
+ scope.launch(bgDispatcher) { querySettings() }
+ }
+ }
+
+ // TODO(b/267372164): Migrate to flows
+ var settings: ClockSettings? = null
+ get() = field
+ protected set(value) {
+ if (field != value) {
+ field = value
+ scope.launch(mainDispatcher) { onClockChanged() }
+ }
+ }
+
+ var isRegistered: Boolean = false
+ private set
+
+ @OpenForTesting
+ open fun querySettings() {
+ assertNotMainThread()
+ val result =
try {
- val json = Settings.Secure.getString(
- context.contentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
- )
- if (json == null || json.isEmpty()) {
- return null
- }
- return ClockSettings.deserialize(json)
+ val json =
+ if (handleAllUsers) {
+ Settings.Secure.getStringForUser(
+ context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ ActivityManager.getCurrentUser()
+ )
+ } else {
+ Settings.Secure.getString(
+ context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
+ )
+ }
+
+ ClockSettings.deserialize(json)
} catch (ex: Exception) {
Log.e(TAG, "Failed to parse clock settings", ex)
- return null
+ null
}
- }
- protected set(value) {
- try {
- val json = if (value != null) {
- value._applied_timestamp = System.currentTimeMillis()
- ClockSettings.serialize(value)
- } else {
- ""
- }
+ settings = result
+ }
+ @OpenForTesting
+ open fun applySettings(value: ClockSettings?) {
+ assertNotMainThread()
+
+ try {
+ value?._applied_timestamp = System.currentTimeMillis()
+ val json = ClockSettings.serialize(value)
+
+ if (handleAllUsers) {
+ Settings.Secure.putStringForUser(
+ context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ json,
+ ActivityManager.getCurrentUser()
+ )
+ } else {
Settings.Secure.putString(
context.contentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ json
)
- } catch (ex: Exception) {
- Log.e(TAG, "Failed to set clock settings", ex)
}
+ } catch (ex: Exception) {
+ Log.e(TAG, "Failed to set clock settings", ex)
}
+ settings = value
+ }
- private fun mutateSetting(mutator: (ClockSettings) -> Unit) {
- val settings = this.settings ?: ClockSettings()
- mutator(settings)
- this.settings = settings
+ @OpenForTesting
+ protected open fun assertMainThread() {
+ Assert.isMainThread()
+ }
+
+ @OpenForTesting
+ protected open fun assertNotMainThread() {
+ Assert.isNotMainThread()
+ }
+
+ private fun onClockChanged() {
+ assertMainThread()
+ clockChangeListeners.forEach { it.onClockChanged() }
+ }
+
+ private fun mutateSetting(mutator: (ClockSettings) -> ClockSettings) {
+ scope.launch(bgDispatcher) { applySettings(mutator(settings ?: ClockSettings())) }
}
var currentClockId: ClockId
get() = settings?.clockId ?: fallbackClockId
- set(value) { mutateSetting { it.clockId = value } }
+ set(value) {
+ mutateSetting { it.copy(clockId = value) }
+ }
var seedColor: Int?
get() = settings?.seedColor
- set(value) { mutateSetting { it.seedColor = value } }
+ set(value) {
+ mutateSetting { it.copy(seedColor = value) }
+ }
init {
connectClocks(defaultClockProvider)
@@ -118,19 +192,51 @@
"$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID"
)
}
+ }
- if (isEnabled) {
- pluginManager.addPluginListener(
- pluginListener,
- ClockProviderPlugin::class.java,
- /*allowMultiple=*/ true
- )
+ fun registerListeners() {
+ if (!isEnabled || isRegistered) {
+ return
+ }
+
+ isRegistered = true
+
+ pluginManager.addPluginListener(
+ pluginListener,
+ ClockProviderPlugin::class.java,
+ /*allowMultiple=*/ true
+ )
+
+ scope.launch(bgDispatcher) { querySettings() }
+ if (handleAllUsers) {
context.contentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
/*notifyForDescendants=*/ false,
settingObserver,
- userHandle
+ UserHandle.USER_ALL
)
+
+ ActivityManager.getService().registerUserSwitchObserver(userSwitchObserver, TAG)
+ } else {
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+ /*notifyForDescendants=*/ false,
+ settingObserver
+ )
+ }
+ }
+
+ fun unregisterListeners() {
+ if (!isRegistered) {
+ return
+ }
+
+ isRegistered = false
+
+ pluginManager.removePluginListener(pluginListener)
+ context.contentResolver.unregisterContentObserver(settingObserver)
+ if (handleAllUsers) {
+ ActivityManager.getService().unregisterUserSwitchObserver(userSwitchObserver)
}
}
@@ -157,7 +263,7 @@
if (DEBUG) {
Log.i(TAG, "Current clock ($currentId) was connected")
}
- clockChangeListeners.forEach { it.onClockChanged() }
+ onClockChanged()
}
}
}
@@ -172,13 +278,12 @@
if (currentId == clock.clockId) {
Log.w(TAG, "Current clock ($currentId) was disconnected")
- clockChangeListeners.forEach { it.onClockChanged() }
+ onClockChanged()
}
}
}
- @OpenForTesting
- open fun getClocks(): List<ClockMetadata> {
+ fun getClocks(): List<ClockMetadata> {
if (!isEnabled) {
return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
}
@@ -213,16 +318,16 @@
return createClock(DEFAULT_CLOCK_ID)!!
}
- private fun createClock(clockId: ClockId): ClockController? {
- val settings = this.settings ?: ClockSettings()
- if (clockId != settings.clockId) {
- settings.clockId = clockId
+ private fun createClock(targetClockId: ClockId): ClockController? {
+ var settings = this.settings ?: ClockSettings()
+ if (targetClockId != settings.clockId) {
+ settings = settings.copy(clockId = targetClockId)
}
- return availableClocks[clockId]?.provider?.createClock(settings)
+ return availableClocks[targetClockId]?.provider?.createClock(settings)
}
private data class ClockInfo(
val metadata: ClockMetadata,
- val provider: ClockProvider
+ val provider: ClockProvider,
)
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/shared/model/ClockPreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/shared/model/ClockPreviewConstants.kt
new file mode 100644
index 0000000..6a77936
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/shared/model/ClockPreviewConstants.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.shared.clocks.shared.model
+
+object ClockPreviewConstants {
+ const val KEY_HIDE_CLOCK = "hide_clock"
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index e4e9c46..c120876 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -181,6 +181,9 @@
/** Flag denoting whether the Wallpaper preview should use the full screen UI. */
const val FLAG_NAME_WALLPAPER_FULLSCREEN_PREVIEW = "wallpaper_fullscreen_preview"
+ /** Flag denoting whether the Monochromatic Theme is enabled. */
+ const val FLAG_NAME_MONOCHROMATIC_THEME = "is_monochromatic_theme_enabled"
+
object Columns {
/** String. Unique ID for the flag. */
const val NAME = "name"
diff --git a/packages/SystemUI/src/com/android/systemui/util/Assert.java b/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/Assert.java
rename to packages/SystemUI/customization/src/com/android/systemui/util/Assert.java
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index bbfb6fb..1c2f38b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -45,7 +45,7 @@
/** Initializes and returns the target clock design */
@Deprecated("Use overload with ClockSettings")
fun createClock(id: ClockId): ClockController {
- return createClock(ClockSettings(id, null, null))
+ return createClock(ClockSettings(id, null))
}
/** Initializes and returns the target clock design */
@@ -111,6 +111,9 @@
/** Call whenever the color palette should update */
fun onColorPaletteChanged(resources: Resources) {}
+
+ /** Call whenever the weather data should update */
+ fun onWeatherDataChanged(data: Weather) {}
}
/** Methods which trigger various clock animations */
@@ -183,16 +186,21 @@
/** Structure for keeping clock-specific settings */
@Keep
data class ClockSettings(
- var clockId: ClockId? = null,
- var seedColor: Int? = null,
- var _applied_timestamp: Long? = null,
+ val clockId: ClockId? = null,
+ val seedColor: Int? = null,
) {
+ var _applied_timestamp: Long? = null
+
companion object {
private val KEY_CLOCK_ID = "clockId"
private val KEY_SEED_COLOR = "seedColor"
private val KEY_TIMESTAMP = "_applied_timestamp"
- fun serialize(setting: ClockSettings): String {
+ fun serialize(setting: ClockSettings?): String {
+ if (setting == null) {
+ return ""
+ }
+
return JSONObject()
.put(KEY_CLOCK_ID, setting.clockId)
.put(KEY_SEED_COLOR, setting.seedColor)
@@ -200,13 +208,21 @@
.toString()
}
- fun deserialize(jsonStr: String): ClockSettings {
+ fun deserialize(jsonStr: String?): ClockSettings? {
+ if (jsonStr.isNullOrEmpty()) {
+ return null
+ }
+
val json = JSONObject(jsonStr)
- return ClockSettings(
- json.getString(KEY_CLOCK_ID),
- if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null,
- if (!json.isNull(KEY_TIMESTAMP)) json.getLong(KEY_TIMESTAMP) else null
- )
+ val result =
+ ClockSettings(
+ json.getString(KEY_CLOCK_ID),
+ if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
+ )
+ if (!json.isNull(KEY_TIMESTAMP)) {
+ result._applied_timestamp = json.getLong(KEY_TIMESTAMP)
+ }
+ return result
}
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt
index 85ec42d..302f175 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.statusbar
+package com.android.systemui.plugins
import android.os.Bundle
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml
index c388f15..81f4c8c 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml
@@ -15,6 +15,7 @@
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/user_switcher_item"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index 64ece47..ca4028a 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -105,6 +105,7 @@
android:id="@+id/key1"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key2"
androidprv:digit="1"
androidprv:textView="@+id/pinEntry" />
@@ -112,6 +113,7 @@
android:id="@+id/key2"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key3"
androidprv:digit="2"
androidprv:textView="@+id/pinEntry" />
@@ -119,6 +121,7 @@
android:id="@+id/key3"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key4"
androidprv:digit="3"
androidprv:textView="@+id/pinEntry" />
@@ -126,6 +129,7 @@
android:id="@+id/key4"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key5"
androidprv:digit="4"
androidprv:textView="@+id/pinEntry" />
@@ -133,6 +137,7 @@
android:id="@+id/key5"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key6"
androidprv:digit="5"
androidprv:textView="@+id/pinEntry" />
@@ -140,6 +145,7 @@
android:id="@+id/key6"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key7"
androidprv:digit="6"
androidprv:textView="@+id/pinEntry" />
@@ -147,13 +153,16 @@
android:id="@+id/key7"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key8"
androidprv:digit="7"
androidprv:textView="@+id/pinEntry" />
+
<com.android.keyguard.NumPadKey
android:id="@+id/key8"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key9"
androidprv:digit="8"
androidprv:textView="@+id/pinEntry" />
@@ -161,34 +170,33 @@
android:id="@+id/key9"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/delete_button"
androidprv:digit="9"
androidprv:textView="@+id/pinEntry" />
-
<com.android.keyguard.NumPadButton
android:id="@+id/delete_button"
+ style="@style/NumPadKey.Delete"
android:layout_width="0dp"
android:layout_height="0dp"
- style="@style/NumPadKey.Delete"
- android:contentDescription="@string/keyboardview_keycode_delete"
- />
+ android:accessibilityTraversalBefore="@id/key0"
+ android:contentDescription="@string/keyboardview_keycode_delete" />
<com.android.keyguard.NumPadKey
android:id="@+id/key0"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key_enter"
androidprv:digit="0"
androidprv:textView="@+id/pinEntry" />
<com.android.keyguard.NumPadButton
android:id="@+id/key_enter"
+ style="@style/NumPadKey.Enter"
android:layout_width="0dp"
android:layout_height="0dp"
- style="@style/NumPadKey.Enter"
- android:contentDescription="@string/keyboardview_keycode_enter"
- />
-
- </androidx.constraintlayout.widget.ConstraintLayout>
+ android:contentDescription="@string/keyboardview_keycode_enter" />
+</androidx.constraintlayout.widget.ConstraintLayout>
<include layout="@layout/keyguard_eca"
android:id="@+id/keyguard_selector_fade_container"
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index ded6f93..aa211bf 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -20,7 +20,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:layout_marginTop="@dimen/controls_top_margin"
android:layout_marginBottom="@dimen/controls_header_bottom_margin">
<!-- make sure the header stays centered in the layout by adding a spacer -->
@@ -78,6 +77,7 @@
android:layout_weight="1"
android:orientation="vertical"
android:clipChildren="true"
+ android:paddingHorizontal="16dp"
android:scrollbars="none">
<include layout="@layout/global_actions_controls_list_view" />
@@ -88,8 +88,6 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:layout_marginLeft="@dimen/global_actions_side_margin"
- android:layout_marginRight="@dimen/global_actions_side_margin"
android:background="@drawable/controls_panel_background"
android:visibility="gone"
/>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
index aaa372a..e39f1a9 100644
--- a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
+++ b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
@@ -18,6 +18,7 @@
<!-- LinearLayout -->
<com.android.systemui.statusbar.policy.KeyguardUserDetailItemView
+ android:id="@+id/user_item"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 4483db8..02186fc 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -27,22 +27,28 @@
android:layout_height="wrap_content"
/>
- <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
- android:id="@+id/icon_glow_ripple"
+ <FrameLayout
+ android:id="@+id/icon_container_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- />
-
- <!-- Add a bottom margin to avoid the glow of the icon ripple from being cropped by screen
- bounds while animating with the icon -->
- <com.android.internal.widget.CachingIconView
- android:id="@+id/app_icon"
- android:background="@drawable/media_ttt_chip_background_receiver"
- android:layout_width="@dimen/media_ttt_icon_size_receiver"
- android:layout_height="@dimen/media_ttt_icon_size_receiver"
- android:layout_gravity="center|bottom"
android:alpha="0.0"
- android:layout_marginBottom="@dimen/media_ttt_receiver_icon_bottom_margin"
- />
+ >
+ <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
+ android:id="@+id/icon_glow_ripple"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
+ <!-- Add a bottom margin to avoid the glow of the icon ripple from being cropped by screen
+ bounds while animating with the icon -->
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/app_icon"
+ android:background="@drawable/media_ttt_chip_background_receiver"
+ android:layout_width="@dimen/media_ttt_icon_size_receiver"
+ android:layout_height="@dimen/media_ttt_icon_size_receiver"
+ android:layout_gravity="center|bottom"
+ android:layout_marginBottom="@dimen/media_ttt_receiver_icon_bottom_margin"
+ />
+ </FrameLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml
index 7c86bc7..ad129e8 100644
--- a/packages/SystemUI/res/layout/qs_user_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml
@@ -18,6 +18,7 @@
<!-- LinearLayout -->
<com.android.systemui.qs.tiles.UserDetailItemView
+ android:id="@+id/user_item"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
index 2567176..130472d 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog_audio_source.xml
@@ -23,7 +23,7 @@
<TextView
android:id="@+id/screen_recording_dialog_source_text"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="14sp"
diff --git a/packages/SystemUI/res/layout/screen_record_dialog_audio_source_selected.xml b/packages/SystemUI/res/layout/screen_record_dialog_audio_source_selected.xml
index e2b8d33..9d9f5c2 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog_audio_source_selected.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog_audio_source_selected.xml
@@ -22,7 +22,7 @@
android:layout_weight="1">
<TextView
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:text="@string/screenrecord_audio_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:fontFamily="@*android:string/config_headlineFontFamily"
@@ -30,7 +30,7 @@
<TextView
android:id="@+id/screen_recording_dialog_source_text"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"
android:textAppearance="?android:attr/textAppearanceSmall"/>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
index 3f0eea9..6cc72dd 100644
--- a/packages/SystemUI/res/layout/screen_record_options.xml
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -67,7 +67,7 @@
android:importantForAccessibility="no"/>
<TextView
android:layout_width="0dp"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_weight="1"
android:gravity="center_vertical"
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index a748e29..7e9202c 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -149,6 +149,8 @@
app:layout_constraintTop_toBottomOf="@id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintWidth_max="450dp"
+ app:layout_constraintHorizontal_bias="0"
>
<include layout="@layout/screenshot_work_profile_first_run" />
<include layout="@layout/screenshot_detection_notice" />
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index 2c08f5d..356b36f 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -39,8 +39,11 @@
<com.android.systemui.statusbar.notification.row.NotificationContentView
android:id="@+id/expanded"
- android:layout_width="match_parent"
- android:layout_height="wrap_content" />
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_content_min_height"
+ android:gravity="center_vertical"
+ />
<com.android.systemui.statusbar.notification.row.NotificationContentView
android:id="@+id/expandedPublic"
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index fb4a3cb..782187a 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ondergrens <xliff:g id="PERCENT">%1$d</xliff:g> persent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linkergrens <xliff:g id="PERCENT">%1$d</xliff:g> persent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Regtergrens <xliff:g id="PERCENT">%1$d</xliff:g> persent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Werkskermskote word in die <xliff:g id="APP">%1$s</xliff:g>-app gestoor"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Gestoor in <xliff:g id="APP">%1$s</xliff:g> in die werkprofiel"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Lêers"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> het hierdie skermskoot bespeur."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> en ander oop apps het hierdie skermskoot bespeur."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Skermopnemer"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Verwerk tans skermopname"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Deurlopende kennisgewing vir \'n skermopnamesessie"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Maak <xliff:g id="APP_LABEL">%1$s</xliff:g> oop"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> deur <xliff:g id="ARTIST_NAME">%2$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Speel <xliff:g id="SONG_NAME">%1$s</xliff:g> vanaf <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Vir jou"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Ontdoen"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Beweeg nader om op <xliff:g id="DEVICENAME">%1$s</xliff:g> te speel"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Beweeg nader aan <xliff:g id="DEVICENAME">%1$s</xliff:g> om hier te speel"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Luidsprekers en skerms"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Voorgestelde toestelle"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Vereis premiumrekening"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Hoe uitsaai werk"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Saai uit"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Mense in jou omtrek met versoenbare Bluetooth-toestelle kan na die media luister wat jy uitsaai"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Jou werkbeleid laat jou toe om slegs van die werkprofiel af foonoproepe te maak"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Skakel oor na werkprofiel"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Maak toe"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Sluitskerminstellings"</string>
</resources>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index bf00ec3..57e160e 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"የታች ወሰን <xliff:g id="PERCENT">%1$d</xliff:g> በመቶ"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"የግራ ወሰን <xliff:g id="PERCENT">%1$d</xliff:g> በመቶ"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"የቀኝ ወሰን <xliff:g id="PERCENT">%1$d</xliff:g> በመቶ"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"የሥራ ቅጽበታዊ ገጽ እይታዎች በ<xliff:g id="APP">%1$s</xliff:g> መተግበሪያ ውስጥ ይቀመጣሉ"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"<xliff:g id="APP">%1$s</xliff:g> ውስጥ የስራ መገለጫው ውስጥ ተቀምጧል"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ፋይሎች"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ይህን ቅጽበታዊ ገጽ እይታ ለይቷል።"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> እና ሌሎች ክፍት መተግበሪያዎች ይህን ቅጽበታዊ ገጽ እይታ ለይተዋል።"</string>
<string name="screenrecord_name" msgid="2596401223859996572">"የማያ መቅጃ"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"የማያ ገጽ ቀረጻን በማሰናዳት ላይ"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ለአንድ የማያ ገጽ ቀረጻ ክፍለ-ጊዜ በመካሄድ ያለ ማሳወቂያ"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"መቆጣጠሪያዎችን ለማከል መተግበሪያ ይምረጡ"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# ቁጥጥር ታክሏል።}one{# ቁጥጥር ታክሏል።}other{# ቁጥጥሮች ታክለዋል።}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"ተወግዷል"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> ይታከል?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g>ን ሲያክሉ መቆጣጠሪያዎችን እና ይዘትን ወደዚህ ፓነል ሊያክል ይችላል። በአንዳንድ መተግበሪያዎች ውስጥ የትኛዎቹ መቆጣጠሪያዎች እዚህ ላይ እንደሚታዩ መምረጥ ይችላሉ።"</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"ተወዳጅ የተደረገ"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"ተወዳጅ ተደርጓል፣ አቋም <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ተወዳጅ አልተደረገም"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ክፈት"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> በ<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ከ<xliff:g id="APP_LABEL">%3$s</xliff:g> ያጫውቱ"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ከ<xliff:g id="APP_LABEL">%2$s</xliff:g> ያጫውቱ"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"ለእርስዎ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ቀልብስ"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"በ<xliff:g id="DEVICENAME">%1$s</xliff:g> ላይ ለማጫወት ጠጋ ያድርጉ"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"እዚህ ለማጫወት ወደ <xliff:g id="DEVICENAME">%1$s</xliff:g> ጠጋ ይበሉ"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"ስህተት፣ እንደገና ይሞክሩ"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"መቆጣጠሪያዎችን አክል"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"መቆጣጠሪያዎችን ያርትዑ"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"መተግበሪያ አክል"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"ውጽዓቶችን ያክሉ"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"ቡድን"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 መሣሪያ ተመርጧል"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ድምጽ ማውጫዎች እና ማሳያዎች"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"የተጠቆሙ መሣሪያዎች"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"ፕሪሚየም መለያ ይጠይቃል"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ማሰራጨት እንዴት እንደሚሠራ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ስርጭት"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ተኳሃኝ የብሉቱዝ መሣሪያዎች ያላቸው በአቅራቢያዎ ያሉ ሰዎች እርስዎ እያሰራጩት ያሉትን ሚዲያ ማዳመጥ ይችላሉ"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"የሥራ መመሪያዎ እርስዎ ከሥራ መገለጫው ብቻ ጥሪ እንዲያደርጉ ይፈቅድልዎታል"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"ወደ የሥራ መገለጫ ቀይር"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"ዝጋ"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"የማያ ገጽ ቁልፍ ቅንብሮች"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 804e565..d638d52 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"الحد السفلى <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"الحد الأيسر <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"الحد الأيمن <xliff:g id="PERCENT">%1$d</xliff:g> في المئة"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"يتم حفظ لقطات الشاشة الخاصة بالعمل في تطبيق \"<xliff:g id="APP">%1$s</xliff:g>\"."</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"الملفات"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"مسجّل الشاشة"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"جارٍ معالجة تسجيل الشاشة"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"إشعار مستمر لجلسة تسجيل شاشة"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"اختيار تطبيق لإضافة عناصر التحكّم"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{تمت إضافة عنصر تحكّم واحد.}zero{تمت إضافة # عنصر تحكّم.}two{تمت إضافة عنصرَي تحكّم.}few{تمت إضافة # عناصر تحكّم.}many{تمت إضافة # عنصر تحكّم.}other{تمت إضافة # عنصر تحكّم.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"تمت الإزالة"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"هل تريد إضافة \"<xliff:g id="APPNAME">%s</xliff:g>\"؟"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"عند إضافة تطبيق \"<xliff:g id="APPNAME">%s</xliff:g>\"، يمكنه إضافة عناصر تحكّم ومحتوى إلى هذه اللوحة. في بعض التطبيقات، يمكنك اختيار عناصر التحكّم التي تظهر هنا."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"تمت الإضافة إلى المفضّلة"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"تمت الإضافة إلى المفضّلة، الموضع <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"تمت الإزالة من المفضّلة"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"فتح <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> للفنان <xliff:g id="ARTIST_NAME">%2$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"تشغيل <xliff:g id="SONG_NAME">%1$s</xliff:g> من تطبيق <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"اقتراحات لك"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"تراجع"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"عليك الاقتراب لتشغيل الوسائط على <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"للتشغيل هنا، عليك الاقتراب من \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\"."</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"حدث خطأ، يُرجى إعادة المحاولة."</string>
<string name="controls_menu_add" msgid="4447246119229920050">"إضافة عناصر تحكّم"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"تعديل عناصر التحكّم"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"إضافة تطبيق"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"إضافة مخرجات"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"مجموعة"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"تم اختيار جهاز واحد."</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"مكبّرات الصوت والشاشات"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"الأجهزة المقترَحة"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"يجب استخدام حساب مدفوع."</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"كيفية عمل البث"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"البث"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"يمكن للأشخاص القريبين منك الذين لديهم أجهزة متوافقة تتضمّن بلوتوث الاستماع إلى الوسائط التي تبثها."</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"تسمح لك سياسة العمل بإجراء المكالمات الهاتفية من الملف الشخصي للعمل فقط."</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"التبديل إلى الملف الشخصي للعمل"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"إغلاق"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"إعدادات شاشة القفل"</string>
</resources>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 9e08a81..01148d5 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"তলৰ সীমা <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"বাওঁফালৰ সীমা <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"সোঁফালৰ সীমা <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"<xliff:g id="APP">%1$s</xliff:g> এপ্টোত কৰ্মস্থানৰ স্ক্ৰীনশ্বটসমূহ ছেভ কৰা হয়"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"<xliff:g id="APP">%1$s</xliff:g> কৰ্মস্থানৰ প্ৰ’ফাইলত ছেভ কৰা হৈছে"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ফাইল"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g>এ এই স্ক্ৰীনশ্বটটো চিনাক্ত কৰিছে।"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> আৰু আন খোলা এপ্সমূহে এই স্ক্ৰীনশ্বটটো চিনাক্ত কৰিছে।"</string>
<string name="screenrecord_name" msgid="2596401223859996572">"স্ক্ৰীন ৰেকৰ্ডাৰ"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"স্ক্রীন ৰেকৰ্ডিঙৰ প্ৰক্ৰিয়াকৰণ হৈ আছে"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রীন ৰেকৰ্ডিং ছেশ্বন চলি থকা সময়ত পোৱা জাননী"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"নিয়ন্ত্ৰণসমূহ যোগ কৰিবলৈ এপ্ বাছনি কৰক"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# টা নিয়ন্ত্ৰণ যোগ দিয়া হৈছে।}one{# টা নিয়ন্ত্ৰণ যোগ দিয়া হৈছে।}other{# টা নিয়ন্ত্ৰণ যোগ দিয়া হৈছে।}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"আঁতৰোৱা হ’ল"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> যোগ দিবনে?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"আপুনি <xliff:g id="APPNAME">%s</xliff:g> যোগ দিলে, ই এই পেনেলত নিয়ন্ত্ৰণ আৰু সমল যোগ দিব পাৰে। কিছুমান এপত আপুনি কোনবোৰ নিয়ন্ত্ৰণ ইয়াত দেখা পোৱা যাব সেয়া বাছনি কৰিব পাৰে।"</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"প্ৰিয় হিচাপে চিহ্নিত কৰা হ’ল"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"প্ৰিয় হিচাপে চিহ্নিত কৰা হ’ল, স্থান <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"অপ্ৰিয় হিচাপে চিহ্নিত কৰা হ’ল"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> খোলক"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ত <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ৰ <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ত <xliff:g id="SONG_NAME">%1$s</xliff:g> গীতটো প্লে’ কৰক"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"আপোনাৰ বাবে"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"আনডু কৰক"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ত প্লে’ কৰিবলৈ ওচৰলৈ যাওক"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ইয়াত প্লে’ কৰিবলৈ, <xliff:g id="DEVICENAME">%1$s</xliff:g>ৰ ওচৰলৈ যাওক"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"আসোঁৱাহ হৈছে, আকৌ চেষ্টা কৰক"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"নিয়ন্ত্ৰণসমূহ যোগ দিয়ক"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"নিয়ন্ত্ৰণসমূহ সম্পাদনা কৰক"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"এপ্ যোগ দিয়ক"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"আউটপুটসমূহ যোগ দিয়ক"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"গোট"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"১ টা ডিভাইচ বাছনি কৰা হৈছে"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"স্পীকাৰ আৰু ডিছপ্লে’"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"পৰামৰ্শ হিচাপে পোৱা ডিভাইচ"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Premium একাউণ্টৰ আৱশ্যক"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"সম্প্ৰচাৰ কৰাটোৱে কেনেকৈ কাম কৰে"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"সম্প্ৰচাৰ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"সমিল ব্লুটুথ ডিভাইচৰ সৈতে আপোনাৰ নিকটৱৰ্তী স্থানত থকা লোকসকলে আপুনি সম্প্ৰচাৰ কৰা মিডিয়াটো শুনিব পাৰে"</string>
@@ -1017,11 +1018,11 @@
<string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• অতি কমেও এটা ডিভাইচ উপলব্ধ"</string>
<string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"শ্বৰ্টকাটটোত স্পৰ্শ কৰি ধৰি ৰাখক"</string>
<string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"বাতিল কৰক"</string>
- <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"এতিয়াই লুটিয়াই দিয়ক"</string>
+ <string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"এতিয়াই ফ্লিপ কৰক"</string>
<string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"উন্নত ছেল্ফিৰ বাবে ফ’নটো আনফ’ল্ড কৰক"</string>
- <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"উন্নত ছেল্ফিৰ বাবে সন্মুখৰ ডিছপ্লে’ লুটিয়াই দিবনে?"</string>
+ <string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"উন্নত ছেল্ফিৰ বাবে সন্মুখৰ ডিছপ্লে’ ফ্লিপ কৰিবনে?"</string>
<string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"অধিক ৰিজ’লিউশ্বনৰ বহল ফট’ৰ বাবে পিছফালে থকা কেমেৰাটো ব্যৱহাৰ কৰক।"</string>
- <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ ই স্ক্ৰীনখন অফ হ’ব"</b></string>
+ <string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ এই স্ক্ৰীনখন অফ হ’ব"</b></string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"জপাব পৰা ডিভাইচৰ জাপ খুলি থকা হৈছে"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"জপাব পৰা ডিভাইচৰ ওলোটাই থকা হৈছে"</string>
<string name="stylus_battery_low_percentage" msgid="1620068112350141558">"<xliff:g id="PERCENTAGE">%s</xliff:g> বেটাৰী বাকী আছে"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"আপোনাৰ কৰ্মস্থানৰ নীতিয়ে আপোনাক কেৱল কৰ্মস্থানৰ প্ৰ’ফাইলৰ পৰা ফ’ন কল কৰিবলৈ দিয়ে"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"কৰ্মস্থানৰ প্ৰ’ফাইললৈ সলনি কৰক"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"বন্ধ কৰক"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"লক স্ক্ৰীনৰ ছেটিং"</string>
</resources>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 73149e8..dc8e7da 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Aşağı sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sol sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sağ sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"İş skrinşotları <xliff:g id="APP">%1$s</xliff:g> tətbiqində saxlanılır"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"İş profilində <xliff:g id="APP">%1$s</xliff:g> tətbiqində saxlanıb"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fayllar"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> bu skrinşotu aşkarladı."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> və digər açıq tətbiqlər bu skrinşotu aşkarladı."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Ekran Yazıcısı"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekran çəkilişi emal edilir"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekranın video çəkimi ərzində silinməyən bildiriş"</string>
@@ -634,7 +636,7 @@
<item msgid="2681220472659720036">"Mübadilə buferi"</item>
<item msgid="4795049793625565683">"Açar kodu"</item>
<item msgid="80697951177515644">"Çevirin, klaviatura dəyişdirici"</item>
- <item msgid="7626977989589303588">"Heç bir"</item>
+ <item msgid="7626977989589303588">"Heç biri"</item>
</string-array>
<string-array name="nav_bar_layouts">
<item msgid="9156773083127904112">"Normal"</item>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> tətbiqini açın"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> tərəfindən <xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%3$s</xliff:g> tətbiqindən oxudun"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> mahnısını <xliff:g id="APP_LABEL">%2$s</xliff:g> tətbiqindən oxudun"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Sizin üçün"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Geri qaytarın"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oxutmaq üçün yaxınlaşın"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Burada oxutmaq üçün <xliff:g id="DEVICENAME">%1$s</xliff:g> cihazına yaxınlaşdırın"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Dinamiklər & Displeylər"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Təklif olunan Cihazlar"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Premium hesab tələb edilir"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Yayım necə işləyir"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Yayım"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Uyğun Bluetooth cihazları olan yaxınlığınızdakı insanlar yayımladığınız medianı dinləyə bilər"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"İş siyasətiniz yalnız iş profilindən telefon zəngləri etməyə imkan verir"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"İş profilinə keçin"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Bağlayın"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Kilid ekranı ayarları"</string>
</resources>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 89d72a1..5b11dbc 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Donja ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Leva ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desna ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Snimci ekrana za posao se čuvaju u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Sačuvano je u aplikaciji <xliff:g id="APP">%1$s</xliff:g> na poslovnom profilu"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fajlovi"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> je otkrila ovaj snimak ekrana."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> i druge otvorene aplikacije su otkrile ovaj snimak ekrana."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Snimač ekrana"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obrađujemo video snimka ekrana"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Obaveštenje o sesiji snimanja ekrana je aktivno"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvorite <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Za vas"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Opozovi"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite da biste puštali muziku na: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Da biste puštali sadržaj ovde, približite uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Zvučnici i ekrani"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Predloženi uređaji"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Zahteva premijum nalog"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kako funkcioniše emitovanje"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Emitovanje"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Ljudi u blizini sa kompatibilnim Bluetooth uređajima mogu da slušaju medijski sadržaj koji emitujete"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Smernice za posao vam omogućavaju da telefonirate samo sa poslovnog profila"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Pređi na poslovni profil"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Zatvori"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Podešavanja zaključanog ekrana"</string>
</resources>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 5bbe0011..22b1c24 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ніжняя граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Левая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Правая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Працоўныя здымкі экрана захаваны ў праграме \"<xliff:g id="APP">%1$s</xliff:g>\""</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлы"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Запіс экрана"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Апрацоўваецца запіс экрана"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Бягучае апавяшчэнне для сеанса запісу экрана"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Выберыце праграму для дадавання элементаў кіравання"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Дададзены # элемент кіравання.}one{Дададзена # элемента кіравання.}few{Дададзена # элементы кіравання.}many{Дададзена # элементаў кіравання.}other{Дададзена # элемента кіравання.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Выдалена"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Дадаць праграму \"<xliff:g id="APPNAME">%s</xliff:g>\"?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Калі вы дадасце праграму \"<xliff:g id="APPNAME">%s</xliff:g>\", яна зможа дадаваць на гэту панэль налады і змесціва. Для некаторых праграм вы зможаце выбраць, якія налады будуць тут паказвацца."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Дададзена ў абранае"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Дададзена ў абранае, пазіцыя <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Выдалена з абранага"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Адкрыйце праграму \"<xliff:g id="APP_LABEL">%1$s</xliff:g>\""</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (выканаўца – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) з дапамогай праграмы \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Прайграйце кампазіцыю \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" з дапамогай праграмы \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Для вас"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Адрабіць"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Каб прайграць мультымедыя на прыладзе \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", наблізьцеся да яе"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Каб прайграць тут, падыдзіце бліжэй да прылады \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Памылка, паўтарыце спробу"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Дадаць элементы кіравання"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Змяніць элементы кіравання"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Дадаць праграму"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Дадайце прылады вываду"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Выбрана 1 прылада"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Дынамікі і дысплэі"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Прылады, якія падтрымліваюцца"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Патрабуецца платны ўліковы запіс"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Як адбываецца трансляцыя"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Трансляцыя"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Людзі паблізу, у якіх ёсць прылады з Bluetooth, змогуць праслухваць мультымедыйнае змесціва, якое вы трансліруеце"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Згодна з палітыкай вашай арганізацыі, рабіць тэлефонныя выклікі дазволена толькі з працоўнага профілю"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Пераключыцца на працоўны профіль"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Закрыць"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Налады экрана блакіроўкі"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 72c6b9d..1adb80b 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Долна граница: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Лява граница: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Дясна граница: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Екранните снимки, направени в служебния потребителски профил, се запазват в приложението „<xliff:g id="APP">%1$s</xliff:g>“"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Запис на екрана"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Записът на екрана се обработва"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущо известие за сесия за записване на екрана"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Изберете приложение, за да добавите контроли"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Добавена е # контрола.}other{Добавени са # контроли.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Премахнато"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Да се добави ли <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Когато добавите <xliff:g id="APPNAME">%s</xliff:g>, приложението може да добави контроли и съдържание към този панел. Някои приложения ви дават възможност да избирате кои контроли да се показват тук."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Означено като любимо"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Означено като любимо – позиция <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Не е означено като любимо"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отваряне на <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="ARTIST_NAME">%2$s</xliff:g> от <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пускане на <xliff:g id="SONG_NAME">%1$s</xliff:g> от <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"За вас"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Отмяна"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Преместете се по-близо, за да се възпроизведе на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"За възпроизвеждане тук се приближете до <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Грешка. Опитайте отново"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Добавяне на контроли"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Редактиране на контролите"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Добавяне на приложение"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Добавяне на изходящи устройства"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 избрано устройство"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Високоговорители и екрани"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Предложени устройства"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Необходим е платен профил"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Как работи предаването"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Предаване"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Хората в близост със съвместими устройства с Bluetooth могат да слушат мултимедията, която предавате"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Служебните правила ви дават възможност да извършвате телефонни обаждания само от служебния потребителски профил"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Превключване към служебния потребителски профил"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Затваряне"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Настройки за заключения екран"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 598db2c..bdda1a3 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"নিচের প্রান্ত থেকে <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"বাঁ প্রান্ত থেকে <xliff:g id="PERCENT">%1$d</xliff:g> শতাংশ"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ডান প্রান্ত থেকে <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"অফিসের স্ক্রিনশট <xliff:g id="APP">%1$s</xliff:g> অ্যাপে সেভ করা হয়"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"অফিস প্রোফাইলের মধ্যে <xliff:g id="APP">%1$s</xliff:g>-এ সেভ করা হয়েছে"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ফাইল"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g>, এই স্ক্রিনশট শনাক্ত করেছে।"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> এবং খোলা থাকা অন্য অ্যাপ এই স্ক্রিনশট শনাক্ত করেছে।"</string>
<string name="screenrecord_name" msgid="2596401223859996572">"স্ক্রিন রেকর্ডার"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"স্ক্রিন রেকর্ডিং প্রসেস হচ্ছে"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"স্ক্রিন রেকর্ডিং সেশন চলার বিজ্ঞপ্তি"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"কন্ট্রোল যোগ করতে অ্যাপ বেছে নিন"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{#টি কন্ট্রোল যোগ করা হয়েছে।}one{#টি কন্ট্রোল যোগ করা হয়েছে।}other{#টি কন্ট্রোল যোগ করা হয়েছে।}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"সরানো হয়েছে"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> যোগ করবেন?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"আপনি <xliff:g id="APPNAME">%s</xliff:g> যোগ করলে, এই প্যানেলে এটি কন্ট্রোল ও কন্টেন্ট যোগ করতে পারবে। কিছু অ্যাপের ক্ষেত্রে, এখানে কোন কোন কন্ট্রোল দেখা যাবে আপনি তা নিয়ন্ত্রণ করতে পারবেন।"</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"পছন্দসই হিসেবে চিহ্নিত করেছেন"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"পছন্দসই হিসেবে চিহ্নিত করেছেন, অবস্থান <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"পছন্দসই থেকে সরিয়ে দিয়েছেন"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> অ্যাপ খুলুন"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-এর <xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%3$s</xliff:g> অ্যাপে চালান"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> গানটি <xliff:g id="APP_LABEL">%2$s</xliff:g> অ্যাপে চালান"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"আপনার জন্য"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"আগের অবস্থায় ফিরুন"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>-এ চালাতে আরও কাছে আনুন"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"এখানে চালানোর জন্য আপনার ডিভাইস <xliff:g id="DEVICENAME">%1$s</xliff:g>-এর কাছে নিয়ে যান"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"সমস্যা হয়েছে, আবার চেষ্টা করুন"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"কন্ট্রোল যোগ করুন"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"কন্ট্রোল এডিট করুন"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"অ্যাপ যোগ করুন"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"আউটপুট যোগ করুন"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"গ্রুপ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"১টি ডিভাইস বেছে নেওয়া হয়েছে"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"স্পিকার & ডিসপ্লে"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"সাজেস্ট করা ডিভাইস"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"প্রিমিয়াম অ্যাকাউন্ট প্রয়োজন"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ব্রডকাস্ট কীভাবে কাজ করে"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"সম্প্রচার করুন"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"আশপাশে লোকজন যাদের মানানসই ব্লুটুথ ডিভাইস আছে, তারা আপনার ব্রডকাস্ট করা মিডিয়া শুনতে পারবেন"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"কাজ সংক্রান্ত নীতি, আপনাকে শুধুমাত্র অফিস প্রোফাইল থেকে কল করার অনুমতি দেয়"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"অফিস প্রোফাইলে পাল্টে নিন"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"বন্ধ করুন"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"লক স্ক্রিন সেটিংস"</string>
</resources>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 88ce4db..fe947df 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Donja granica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Lijeva granica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desna granica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Poslovni snimci ekrana se pohranjuju u aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Sačuvano je u aplikaciji <xliff:g id="APP">%1$s</xliff:g> na radnom profilu"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fajlovi"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> je otkrila ovaj snimak ekrana."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> i druge otvorene aplikacije su otkrile ovaj snimak ekrana."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Snimač ekrana"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obrađivanje snimka ekrana"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Obavještenje za sesiju snimanja ekrana je u toku"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvorite aplikaciju <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> izvođača <xliff:g id="ARTIST_NAME">%2$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducirajte pjesmu <xliff:g id="SONG_NAME">%1$s</xliff:g> pomoću aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Za vas"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite da reproducirate na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Da reproducirate ovdje, približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Zvučnici i ekrani"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Predloženi uređaji"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Zahtijeva premijum račun"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kako funkcionira emitiranje"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Emitirajte"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osobe u vašoj blizini s kompatibilnim Bluetooth uređajima mogu slušati medijske sadržaje koje emitirate"</string>
@@ -1017,7 +1021,7 @@
<string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Obrni sada"</string>
<string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Raširite telefon za bolji selfi"</string>
<string name="rear_display_unfold_bottom_sheet_title" msgid="2137403802960396357">"Obrnuti na prednji ekran radi boljeg selfija?"</string>
- <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Koristite stražnju kameru za širu fotografiju veće rezolucije."</string>
+ <string name="rear_display_bottom_sheet_description" msgid="1852662982816810352">"Koristite zadnju kameru za širu fotografiju veće rezolucije."</string>
<string name="rear_display_bottom_sheet_warning" msgid="800995919558238930"><b>"✱ Ekran će se isključiti"</b></string>
<string name="rear_display_accessibility_folded_animation" msgid="1538121649587978179">"Sklopivi uređaj se rasklapa"</string>
<string name="rear_display_accessibility_unfolded_animation" msgid="1946153682258289040">"Sklopivi uređaj se obrće"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Radna pravila vam dozvoljavaju upućivanje telefonskih poziva samo s radnog profila"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Pređite na radni profil"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Zatvori"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Postavke zaključavanja ekrana"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 953cc35..b1e0ddc 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Marge inferior <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Marge esquerre <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Marge dret <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Les captures de pantalla de treball es desen a l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fitxers"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Gravació de pantalla"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processant gravació de pantalla"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificació en curs d\'una sessió de gravació de la pantalla"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Selecciona l\'aplicació per afegir controls"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{S\'ha afegit # control.}many{# controls added.}other{S\'han afegit # controls.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Suprimit"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Vols afegir <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"En afegir <xliff:g id="APPNAME">%s</xliff:g>, podrà afegir controls i contingut en aquest tauler. En algunes aplicacions, pots triar quins controls es mostren aquí."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Afegit als preferits"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Afegit als preferits, posició <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Suprimit dels preferits"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Obre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) des de l\'aplicació <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reprodueix <xliff:g id="SONG_NAME">%1$s</xliff:g> des de l\'aplicació <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Per a tu"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Desfés"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Mou més a prop per reproduir a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Per reproduir contingut aquí, apropa\'l a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Error; torna-ho a provar"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Afegeix controls"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edita els controls"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Afegeix una aplicació"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Afegeix sortides"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositiu seleccionat"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altaveus i pantalles"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositius suggerits"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Requereix un compte prèmium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Com funciona l\'emissió"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Emet"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les persones properes amb dispositius Bluetooth compatibles poden escoltar el contingut multimèdia que emets"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"La teva política de treball et permet fer trucades només des del perfil de treball"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Canvia al perfil de treball"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Tanca"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Configuració pantalla de bloqueig"</string>
</resources>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 7c70cd86..e0da110 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Dolní okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Levý okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Pravý okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Pracovní snímky obrazovky se ukládají do aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Soubory"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Rekordér obrazovky"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Záznam obrazovky se zpracovává"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Trvalé oznámení o relaci nahrávání"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Vyberte aplikaci, pro kterou chcete přidat ovládací prvky"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Byl přidán # ovládací prvek.}few{Byly přidány # ovládací prvky.}many{Bylo přidáno # ovládacího prvku.}other{Bylo přidáno # ovládacích prvků.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Odstraněno"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Přidat aplikaci <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Když přidáte aplikaci <xliff:g id="APPNAME">%s</xliff:g>, může do tohoto panelu přidat ovládací prvky a obsah. V některých aplikacích si můžete vybrat, které ovládací prvky se zde zobrazí."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Přidáno do oblíbených"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Přidáno do oblíbených na pozici <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Odebráno z oblíbených"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otevřít aplikaci <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Přehrát skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikace <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Pro vás"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Vrátit zpět"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Pokud chcete přehrávat na zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>, přibližte se k němu"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pokud obsah chcete přehrát na tomto zařízení, přesuňte ho blíže k zařízení <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Chyba, zkuste to znovu"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Přidat ovládací prvky"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Upravit ovládací prvky"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Přidat aplikaci"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Přidání výstupů"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Je vybráno 1 zařízení"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Reproduktory a displeje"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Navrhovaná zařízení"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Vyžaduje prémiový účet"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jak vysílání funguje"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Vysílání"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Lidé ve vašem okolí s kompatibilními zařízeními Bluetooth mohou poslouchat média, která vysíláte"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Vaše pracovní zásady vám umožňují telefonovat pouze z pracovního profilu"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Přepnout na pracovní profil"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Zavřít"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Nastavení obrazovky uzamčení"</string>
</resources>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 7c4e92c..fa9b96a 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Nederste kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Venstre kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Højre kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Screenshots, der tages via arbejdsprofilen, gemmer i appen <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Gemt i <xliff:g id="APP">%1$s</xliff:g> på arbejdsprofilen"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> har registreret dette screenshot."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> og andre åbne apps har registreret dette screenshot."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Skærmoptagelse"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Behandler skærmoptagelse"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Konstant notifikation om skærmoptagelse"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Vælg en app for at tilføje styring"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# styringselement er tilføjet.}one{# styringselement er tilføjet.}other{# styringselementer er tilføjet.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Fjernet"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Vil du tilføje <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Når du tilføjer <xliff:g id="APPNAME">%s</xliff:g>, kan den føje styringselementer og indhold til dette panel. I nogle apps kan du vælge, hvilke styringselementer der vises her."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Angivet som favorit"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Angivet som favorit. Position <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Fjernet fra favoritter"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Åbn <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> af <xliff:g id="ARTIST_NAME">%2$s</xliff:g> via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Afspil <xliff:g id="SONG_NAME">%1$s</xliff:g> via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Til dig"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Fortryd"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ryk tættere på for at afspille på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"For at afspille her skal enheden tættere på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Der opstod en fejl. Prøv igen"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Tilføj styring"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Rediger styring"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Tilføj app"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tilføj medieudgange"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Der er valgt 1 enhed"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Højttalere og skærme"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Foreslåede enheder"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Kræver Premium-konto"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Sådan fungerer udsendelser"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Udsendelse"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personer i nærheden, som har kompatible Bluetooth-enheder, kan lytte til det medie, du udsender"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Din arbejdspolitik tillader kun, at du kan foretage telefonopkald fra arbejdsprofilen"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Skift til arbejdsprofil"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Luk"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Indstillinger for låseskærm"</string>
</resources>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 2b975bc..9521e71 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Unterer Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linker Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Rechter Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Mit einem Arbeitsprofil aufgenommene Screenshots werden in der App „<xliff:g id="APP">%1$s</xliff:g>“ gespeichert"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Dateien"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Bildschirmaufzeichnung"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Bildschirmaufzeichnung…"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Fortlaufende Benachrichtigung für eine Bildschirmaufzeichnung"</string>
@@ -183,9 +188,9 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Flugmodus"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN an."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Akku bei <xliff:g id="NUMBER">%d</xliff:g> Prozent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="6548654589315074529">"Akku bei <xliff:g id="PERCENTAGE">%1$d</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <string name="accessibility_battery_level_with_estimate" msgid="6548654589315074529">"Akku bei <xliff:g id="PERCENTAGE">%1$d</xliff:g> Prozent, <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Akku wird aufgeladen, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> Prozent."</string>
- <string name="accessibility_battery_level_charging_paused" msgid="3560711496775146763">"Akku bei <xliff:g id="PERCENTAGE">%d</xliff:g>. Zum Schutz des Akkus wurde das Laden pausiert."</string>
+ <string name="accessibility_battery_level_charging_paused" msgid="3560711496775146763">"Akku bei <xliff:g id="PERCENTAGE">%d</xliff:g> Prozent. Zum Schutz des Akkus wurde das Laden pausiert."</string>
<string name="accessibility_battery_level_charging_paused_with_estimate" msgid="2223541217743647858">"Akku bei <xliff:g id="PERCENTAGE">%1$d</xliff:g>. <xliff:g id="TIME">%2$s</xliff:g>. Zum Schutz des Akkus wurde das Laden pausiert."</string>
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Alle Benachrichtigungen ansehen"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Schreibtelefonie aktiviert"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"App zum Hinzufügen von Steuerelementen auswählen"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# Steuerelement hinzugefügt.}other{# Steuerelemente hinzugefügt.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Entfernt"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> hinzufügen?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Wenn du <xliff:g id="APPNAME">%s</xliff:g> hinzufügst, kann diese App Einstellungen und Inhalte zu diesem Bereich hinzufügen. In einigen Apps kannst du festlegen, welche Einstellungen hier angezeigt werden sollen."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Zu Favoriten hinzugefügt"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Zu Favoriten hinzugefügt, Position <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Aus Favoriten entfernt"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> öffnen"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> von <xliff:g id="ARTIST_NAME">%2$s</xliff:g> über <xliff:g id="APP_LABEL">%3$s</xliff:g> wiedergeben"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> über <xliff:g id="APP_LABEL">%2$s</xliff:g> wiedergeben"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Für mich"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Rückgängig machen"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Gehe für die Wiedergabe näher an „<xliff:g id="DEVICENAME">%1$s</xliff:g>“ heran"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Für eine Wiedergabe auf diesem Gerät muss es näher bei <xliff:g id="DEVICENAME">%1$s</xliff:g> sein"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Fehler – versuch es noch mal"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Steuerelemente hinzufügen"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Steuerelemente bearbeiten"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"App hinzufügen"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ausgabegeräte hinzufügen"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ein Gerät ausgewählt"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Lautsprecher & Displays"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Vorgeschlagene Geräte"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Premium-Konto erforderlich"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Funktionsweise von Nachrichten an alle"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Nachricht an alle"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personen, die in der Nähe sind und kompatible Bluetooth-Geräten haben, können sich die Medien anhören, die du per Nachricht an alle sendest"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Gemäß den Arbeitsrichtlinien darfst du nur über dein Arbeitsprofil telefonieren"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Zum Arbeitsprofil wechseln"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Schließen"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Sperrbildschirm-Einstellungen"</string>
</resources>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 47bcfcb..9f6a1c3 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Κάτω όριο <xliff:g id="PERCENT">%1$d</xliff:g> τοις εκατό"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Αριστερό όριο <xliff:g id="PERCENT">%1$d</xliff:g> τοις εκατό"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Δεξί όριο <xliff:g id="PERCENT">%1$d</xliff:g> τοις εκατό"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Τα στιγμιότυπα οθόνης εργασίας αποθηκεύονται στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Αποθηκεύτηκε στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> στο προφίλ εργασίας"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Αρχεία"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"Η εφαρμογή <xliff:g id="APPNAME">%1$s</xliff:g> εντόπισε αυτό το στιγμιότυπο οθόνης."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Η εφαρμογή <xliff:g id="APPNAME">%1$s</xliff:g> και άλλες ανοικτές εφαρμογές εντόπισαν το στιγμιότυπο οθόνης."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Εγγραφή οθόνης"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Επεξεργασία εγγραφής οθόνης"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ειδοποίηση σε εξέλιξη για μια περίοδο λειτουργίας εγγραφής οθόνης"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Άνοιγμα της εφαρμογής <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> από <xliff:g id="ARTIST_NAME">%2$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Αναπαραγωγή του <xliff:g id="SONG_NAME">%1$s</xliff:g> στην εφαρμογή <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Για εσάς"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Αναίρεση"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Πλησιάστε για αναπαραγωγή στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Για να γίνει αναπαραγωγή εδώ, μετακινηθείτε πιο κοντά στη συσκευή <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Ηχεία και οθόνες"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Προτεινόμενες συσκευές"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Απαιτεί λογαριασμό premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Πώς λειτουργεί η μετάδοση"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Μετάδοση"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Οι άνθρωποι με συμβατές συσκευές Bluetooth που βρίσκονται κοντά σας μπορούν να ακούσουν το μέσο που μεταδίδετε."</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Η πολιτική εργασίας σάς επιτρέπει να πραγματοποιείτε τηλεφωνικές κλήσεις μόνο από το προφίλ εργασίας σας."</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Εναλλαγή σε προφίλ εργασίας"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Κλείσιμο"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Ρυθμίσεις κλειδώματος οθόνης"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 2b5e8f9..c11b6c4 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Work screenshots are saved in the <xliff:g id="APP">%1$s</xliff:g> app"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Screen Recorder"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"For you"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers & displays"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Suggested devices"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Requires premium account"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"How broadcasting works"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media that you\'re broadcasting"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 77452b5..9b60f22 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Work screenshots are saved in the <xliff:g id="APP">%1$s</xliff:g> app"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Screen Recorder"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"For You"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers & Displays"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Suggested Devices"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Requires premium account"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"How broadcasting works"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media you\'re broadcasting"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 2b5e8f9..c11b6c4 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Work screenshots are saved in the <xliff:g id="APP">%1$s</xliff:g> app"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Screen Recorder"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"For you"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers & displays"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Suggested devices"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Requires premium account"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"How broadcasting works"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media that you\'re broadcasting"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 2b5e8f9..c11b6c4 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Work screenshots are saved in the <xliff:g id="APP">%1$s</xliff:g> app"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Screen Recorder"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"For you"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers & displays"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Suggested devices"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Requires premium account"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"How broadcasting works"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media that you\'re broadcasting"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 173b905..cb95bb0 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Work screenshots are saved in the <xliff:g id="APP">%1$s</xliff:g> app"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Screen Recorder"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Open <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> by <xliff:g id="ARTIST_NAME">%2$s</xliff:g> from <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Play <xliff:g id="SONG_NAME">%1$s</xliff:g> from <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"For You"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Undo"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Move closer to play on <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"To play here, move closer to <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers & Displays"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Suggested Devices"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Requires premium account"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"How broadcasting works"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media you\'re broadcasting"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index e5e20ba..7fd8a6f 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Límite inferior: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Límite izquierdo: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Límite derecho: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Las capturas de pantalla de trabajo se guardan en la app de <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Se guardó en <xliff:g id="APP">%1$s</xliff:g> en el perfil de trabajo"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detectó que tomaste una captura de pantalla."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> y otras apps en ejecución detectaron que tomaste una captura de pantalla."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Grabadora de pantalla"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Procesando grabación pantalla"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación constante para una sesión de grabación de pantalla"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproducir <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Para ti"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Acércate para reproducir en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproducir aquí, acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Bocinas y pantallas"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Requiere una cuenta premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cómo funciona la transmisión"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transmisión"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Las personas cercanas con dispositivos Bluetooth compatibles pueden escuchar el contenido multimedia que transmites"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Tu política del trabajo te permite hacer llamadas telefónicas solo desde el perfil de trabajo"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Cambiar al perfil de trabajo"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Cerrar"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Config. de pantalla de bloqueo"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 41d9c9e..48e3ba9 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"<xliff:g id="PERCENT">%1$d</xliff:g> por ciento del límite inferior"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> por ciento del límite izquierdo"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> por ciento del límite derecho"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Las capturas de pantalla de trabajo se guardan en la aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Archivos"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Grabación de pantalla"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Procesando grabación de pantalla"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación continua de una sesión de grabación de la pantalla"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Elige una aplicación para añadir controles"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control añadido.}many{# controles añadidos.}other{# controles añadidos.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Quitado"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"¿Añadir <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Si añades <xliff:g id="APPNAME">%s</xliff:g>, podrá añadir controles y contenido a este panel. En algunas aplicaciones, puedes elegir qué controles aparecen aquí."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Añadido a favoritos"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Añadido a favoritos (posición <xliff:g id="NUMBER">%d</xliff:g>)"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Quitado de favoritos"</string>
@@ -854,9 +857,10 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Poner <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Para ti"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Deshacer"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Acércate a <xliff:g id="DEVICENAME">%1$s</xliff:g> para reproducir contenido ahí"</string>
- <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproducirlo, acércate al dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>)"</string>
+ <string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproducirlo aquí, acércate al dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>)"</string>
<string name="media_transfer_playing_different_device" msgid="7186806382609785610">"Reproduciendo en <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_transfer_failed" msgid="7955354964610603723">"Se ha producido un error. Inténtalo de nuevo."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Cargando"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Error: Vuelve a intentarlo"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Añadir controles"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Editar controles"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Añadir aplicación"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Añadir dispositivos de salida"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo seleccionado"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altavoces y pantallas"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Sugerencias de dispositivos"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Requiere una cuenta premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cómo funciona la emisión"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Emisión"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Las personas cercanas con dispositivos Bluetooth compatibles pueden escuchar el contenido multimedia que emites"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Tu política del trabajo solo te permite hacer llamadas telefónicas desde el perfil de trabajo"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Cambiar al perfil de trabajo"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Cerrar"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Ajustes de pantalla de bloqueo"</string>
</resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 225812c..6bc736f 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alapiir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vasak piir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Parem piir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Töö ekraanipildid salvestatakse rakendusse <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Ekraanisalvesti"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekraanisalvestuse töötlemine"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Pooleli märguanne ekraanikuva salvestamise seansi puhul"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Valige juhtelementide lisamiseks rakendus"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Lisati # juhtnupp.}other{Lisati # juhtnuppu.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Eemaldatud"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Kas lisada <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Kui lisate rakenduse <xliff:g id="APPNAME">%s</xliff:g>, saab see sellele paneelile lisada juhtelemendid ja sisu. Mõnes rakenduses saate valida, millised juhtelemendid siin kuvatakse."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Lisatud lemmikuks"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Lisatud lemmikuks, positsioon <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Eemaldatud lemmikute hulgast"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Rakenduse <xliff:g id="APP_LABEL">%1$s</xliff:g> avamine"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> esitajalt <xliff:g id="ARTIST_NAME">%2$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Esita lugu <xliff:g id="SONG_NAME">%1$s</xliff:g> rakenduses <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Teile"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Võta tagasi"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Liikuge lähemale, et seadmes <xliff:g id="DEVICENAME">%1$s</xliff:g> esitada"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Siin esitamiseks minge seadmele <xliff:g id="DEVICENAME">%1$s</xliff:g> lähemale"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Ilmnes viga, proovige uuesti"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Lisa juhtelemente"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Muuda juhtelemente"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Rakenduse lisamine"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Väljundite lisamine"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 seade on valitud"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Kõlarid ja ekraanid"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Soovitatud seadmed"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Vajalik on tasuline konto"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kuidas ülekandmine toimib?"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Ülekanne"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Teie läheduses olevad inimesed, kellel on ühilduvad Bluetooth-seadmed, saavad kuulata teie ülekantavat meediat"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Teie töökoha eeskirjad lubavad teil helistada ainult tööprofiililt"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Lülitu tööprofiilile"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Sule"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Lukustuskuva seaded"</string>
</resources>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 37a66b0..4b1931b 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Beheko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Ezkerreko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Eskuineko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Laneko pantaila-argazkiak <xliff:g id="APP">%1$s</xliff:g> aplikazioan gordetzen dira"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fitxategiak"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Pantaila-grabagailua"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Pantaila-grabaketa prozesatzen"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Pantailaren grabaketa-saioaren jakinarazpen jarraitua"</string>
@@ -852,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ireki <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> bidez"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Erreproduzitu <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> bidez"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Zuretzat"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Desegin"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Gertura ezazu <xliff:g id="DEVICENAME">%1$s</xliff:g> gailuan erreproduzitzeko"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Hemen erreproduzitzeko, hurbildu <xliff:g id="DEVICENAME">%1$s</xliff:g> gailura"</string>
@@ -884,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%% <xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Bozgorailuak eta pantailak"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Iradokitako gailuak"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Premium kontu bat behar da"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Nola funtzionatzen dute iragarpenek?"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Iragarri"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Bluetooth bidezko gailu bateragarriak dituzten inguruko pertsonek iragartzen ari zaren multimedia-edukia entzun dezakete"</string>
@@ -1029,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Deiak laneko profiletik soilik egiteko baimena ematen dizute laneko gidalerroek"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Aldatu laneko profilera"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Itxi"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Pantaila blokeatuaren ezarpenak"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index a057ad9..df98a7e 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"مرز پایین <xliff:g id="PERCENT">%1$d</xliff:g> درصد"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"مرز سمت چپ <xliff:g id="PERCENT">%1$d</xliff:g> درصد"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"مرز سمت راست <xliff:g id="PERCENT">%1$d</xliff:g> درصد"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"نماگرفتهای نمایه کاری در برنامه <xliff:g id="APP">%1$s</xliff:g> ذخیره میشوند"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"ضبطکننده صفحهنمایش"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"درحال پردازش ضبط صفحهنمایش"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"اعلان درحال انجام برای جلسه ضبط صفحهنمایش"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"انتخاب برنامه برای افزودن کنترلها"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# کنترل اضافه شد.}one{# کنترل اضافه شد.}other{# کنترل اضافه شد.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"حذف شد"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> افزوده شود؟"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"وقتی <xliff:g id="APPNAME">%s</xliff:g> را اضافه میکنید، میتواند کنترلها و محتوا را به این پانل اضافه کند. در برخیاز برنامهها میتوانید انتخاب کنید چه کنترلهایی در اینجا نشان داده شود."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"به موارد دلخواه اضافه شد"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"اضافهشده به موارد دلخواه، جایگاه <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"حذفشده از موارد دلخواه"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"باز کردن <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> از <xliff:g id="ARTIST_NAME">%2$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%3$s</xliff:g> پخش کنید"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> را ازطریق <xliff:g id="APP_LABEL">%2$s</xliff:g> پخش کنید"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"برای شما"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"واگرد"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"برای پخش در <xliff:g id="DEVICENAME">%1$s</xliff:g> به دستگاه نزدیکتر شوید"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"برای پخش در اینجا، به <xliff:g id="DEVICENAME">%1$s</xliff:g> نزدیکتر شوید"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"خطا، دوباره امتحان کنید"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"افزودن کنترلها"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"ویرایش کنترلها"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"افزودن برنامه"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"افزودن خروجی"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"گروه"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"۱ دستگاه انتخاب شد"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"بلندگوها و نمایشگرها"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"دستگاههای پیشنهادی"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"حساب ممتاز لازم است"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"همهفرتستی چطور کار میکند"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"همهفرستی"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"افرادی که در اطرافتان دستگاههای Bluetooth سازگار دارند میتوانند به رسانهای که همهفرستی میکنید گوش کنند"</string>
@@ -951,7 +955,7 @@
<string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"موقتاً متصل است"</string>
<string name="mobile_data_poor_connection" msgid="819617772268371434">"اتصال ضعیف"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"داده تلفن همراه بهطور خودکار متصل نخواهد شد"</string>
- <string name="mobile_data_no_connection" msgid="1713872434869947377">"اتصال برقرار نیست"</string>
+ <string name="mobile_data_no_connection" msgid="1713872434869947377">"اتصال اینترنت موجود نیست"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"شبکه دیگری وجود ندارد"</string>
<string name="all_network_unavailable" msgid="4112774339909373349">"شبکهای در دسترس نیست"</string>
<string name="turn_on_wifi" msgid="1308379840799281023">"Wi-Fi"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"خطمشی کاری شما فقط به برقراری تماس ازطریق نمایه کاری اجازه میدهد"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"رفتن به نمایه کاری"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"بستن"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"تنظیمات صفحه قفل"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index c4f5571..c93c3fa 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alareuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vasen reuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Oikea reuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Työprofiilin kuvakaappaukset tallennetaan sovellukseen: <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Näytön tallentaja"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Näytön tallennusta käsitellään"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Pysyvä ilmoitus näytön tallentamisesta"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Valitse sovellus lisätäksesi säätimiä"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# säädin lisätty.}other{# säädintä lisätty.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Poistettu"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Lisätäänkö <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Kun <xliff:g id="APPNAME">%s</xliff:g> lisätään, se voi lisätä asetuksia ja sisältöä tähän paneeliin. Joissakin sovelluksissa voit valita, mitä asetukset näkyvät täällä."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Lisätty suosikkeihin"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Lisätty suosikkeihin sijalle <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Poistettu suosikeista"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Avaa <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) sovelluksessa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Soita <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="APP_LABEL">%2$s</xliff:g>)"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Sinulle"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Kumoa"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Siirry lähemmäs, jotta <xliff:g id="DEVICENAME">%1$s</xliff:g> voi toistaa tämän"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Siirry lähemmäs laitetta (<xliff:g id="DEVICENAME">%1$s</xliff:g>) toistaaksesi täällä"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Virhe, yritä uudelleen"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Lisää säätimiä"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Muokkaa säätimiä"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Lisää sovellus"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Lisää toistotapoja"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Ryhmä"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 laite valittu"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Kaiuttimet ja näytöt"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ehdotetut laitteet"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Edellyttää premium-tiliä"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Miten lähetys toimii"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Lähetys"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Lähistöllä olevat ihmiset, joilla on yhteensopiva Bluetooth-laite, voivat kuunnella lähettämääsi mediaa"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Työkäytäntö sallii sinun soittaa puheluita vain työprofiilista"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Vaihda työprofiiliin"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Sulje"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Lukitusnäytön asetukset"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index a65d2f3..f1e5fbc 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Limite inférieure : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite gauche : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite droite : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Les captures d\'écran du profil professionnel sont enregistrées dans l\'application <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fichiers"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Enregistreur d\'écran"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Trait. de l\'enregist. d\'écran…"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notification en cours pour une session d\'enregistrement d\'écran"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'application pour laquelle ajouter des commandes"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# commande ajoutée.}one{# commande ajoutée.}many{# de commandes ajoutées.}other{# commandes ajoutées.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Supprimé"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Ajouter <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Lorsque vous ajoutez <xliff:g id="APPNAME">%s</xliff:g>, elle peut ajouter des commandes et du contenu à ce panneau. Dans certaines applications, vous pouvez choisir les commandes qui s\'affichent ici."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Ajouté aux favoris"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Ajouté aux favoris, en position <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Supprimé des favoris"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ouvrez <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Lecture de <xliff:g id="SONG_NAME">%1$s</xliff:g> à partir de <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Pour vous"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Rapprochez-vous pour faire jouer le contenu sur <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pour faire jouer le contenu ici, rapprochez-vous de <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Erreur. Veuillez réessayer."</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Ajouter des commandes"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Modifier des commandes"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Ajouter une application"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ajouter des sorties"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Un appareil sélectionné"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Haut-parleurs et écrans"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Appareils suggérés"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Nécessite un compte payant"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Fonctionnement de la diffusion"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Diffusion"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les personnes à proximité disposant d\'appareils Bluetooth compatibles peuvent écouter le contenu multimédia que vous diffusez"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Votre politique de l\'entreprise vous autorise à passer des appels téléphoniques uniquement à partir de votre profil professionnel"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Passer au profil professionnel"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Fermer"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Paramètres écran de verrouillage"</string>
</resources>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index c779b63..a667728 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Limite inférieure : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite gauche : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite droite : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Les captures d\'écran du profil professionnel sont enregistrées dans l\'appli <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fichiers"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Enregistreur d\'écran"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Enregistrement de l\'écran…"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notification en cours pour une session d\'enregistrement de l\'écran"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'appli pour laquelle ajouter des commandes"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# commande ajoutée.}one{# commande ajoutée.}many{# commandes ajoutées.}other{# commandes ajoutées.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Supprimé"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Ajouter <xliff:g id="APPNAME">%s</xliff:g> ?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Lorsque vous ajoutez l\'appli <xliff:g id="APPNAME">%s</xliff:g>, elle peut ajouter des commandes et contenus dans ce panneau. Dans certaines applis, vous pouvez choisir les commandes à afficher ici."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Ajouté aux favoris"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Ajouté aux favoris, en position <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Supprimé des favoris"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Ouvre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> par <xliff:g id="ARTIST_NAME">%2$s</xliff:g> depuis <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mets <xliff:g id="SONG_NAME">%1$s</xliff:g> depuis <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Pour vous"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Annuler"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Rapprochez-vous de votre <xliff:g id="DEVICENAME">%1$s</xliff:g> pour y lire le contenu"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pour lancer la lecture ici, rapprochez-vous de <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Erreur. Veuillez réessayer."</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Ajouter des commandes"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Modifier des commandes"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Ajouter une appli"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ajouter des sorties"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Groupe"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 appareil sélectionné"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Enceintes et écrans"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Appareils suggérés"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Nécessite un compte premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Fonctionnement des annonces"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Annonce"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Les personnes à proximité équipées d\'appareils Bluetooth compatibles peuvent écouter le contenu multimédia que vous diffusez"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Votre règle professionnelle ne vous permet de passer des appels que depuis le profil professionnel"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Passer au profil professionnel"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Fermer"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Paramètres écran de verrouillage"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 9c270a9..260be9b 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bordo inferior: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Bordo esquerdo: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Bordo dereito: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"As capturas de pantalla do perfil de traballo gárdanse na aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ficheiros"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Gravadora da pantalla"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Procesando gravación pantalla"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificación en curso sobre unha sesión de gravación de pantalla"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Escolle unha aplicación para engadir controis"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Engadiuse # control.}other{Engadíronse # controis.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Quitouse"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Queres engadir <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Cando engadas a aplicación <xliff:g id="APPNAME">%s</xliff:g>, poderá incluír controis e contido neste panel Nalgunhas aplicacións, podes escoller os controis que se mostrarán aquí."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Está entre os controis favoritos"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Está entre os controis favoritos (posición: <xliff:g id="NUMBER">%d</xliff:g>)"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Non está entre os controis favoritos"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abre <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g>, de <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, en <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduce <xliff:g id="SONG_NAME">%1$s</xliff:g> en <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Para ti"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Desfacer"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Achega o dispositivo para reproducir o contido en: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproducir o contido aquí, achégate ao dispositivo (<xliff:g id="DEVICENAME">%1$s</xliff:g>)"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Erro. Téntao de novo"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Engadir controis"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Editar controis"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Engadir aplicación"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Engadir saídas"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Seleccionouse 1 dispositivo"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altofalantes e pantallas"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos suxeridos"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Require unha conta premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funcionan as difusións?"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Difusión"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"As persoas que estean preto de ti e que dispoñan de dispositivos Bluetooth compatibles poden escoitar o contido multimedia que difundas"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"A política do teu traballo só che permite facer chamadas de teléfono desde o perfil de traballo"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Cambiar ao perfil de traballo"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Pechar"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Configuración pantalla bloqueo"</string>
</resources>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 3457430..86b4bc5 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"નીચેની સીમા <xliff:g id="PERCENT">%1$d</xliff:g> ટકા"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ડાબી બાજુની સીમા <xliff:g id="PERCENT">%1$d</xliff:g> ટકા"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"જમણી બાજુની સીમા <xliff:g id="PERCENT">%1$d</xliff:g> ટકા"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ઑફિસના સ્ક્રીનશૉટ <xliff:g id="APP">%1$s</xliff:g> ઍપમાં સાચવવામાં આવે છે"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ફાઇલો"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"સ્ક્રીન રેકોર્ડર"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"સ્ક્રીન રેકૉર્ડિંગ ચાલુ છે"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"સ્ક્રીન રેકોર્ડિંગ સત્ર માટે ચાલુ નોટિફિકેશન"</string>
@@ -852,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ખોલો"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> પર <xliff:g id="ARTIST_NAME">%2$s</xliff:g>નું <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> પર <xliff:g id="SONG_NAME">%1$s</xliff:g> ગીત ચલાવો"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"તમારા માટે"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"છેલ્લો ફેરફાર રદ કરો"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> પર ચલાવવા માટે વધુ નજીક ખસેડો"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"અહીં ચલાવવા માટે, <xliff:g id="DEVICENAME">%1$s</xliff:g>ની નજીક લાવો"</string>
@@ -884,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"સ્પીકર અને ડિસ્પ્લે"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"સૂચવેલા ડિવાઇસ"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Premium એકાઉન્ટ જરૂરી છે"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"બ્રોડકાસ્ટ પ્રક્રિયાની કામ કરવાની રીત"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"બ્રોડકાસ્ટ કરો"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"સુસંગત બ્લૂટૂથ ડિવાઇસ ધરાવતા નજીકના લોકો તમે જે મીડિયા બ્રોડકાસ્ટ કરી રહ્યાં છો તે સાંભળી શકે છે"</string>
@@ -1029,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"તમારી ઑફિસની પૉલિસી તમને માત્ર ઑફિસની પ્રોફાઇલ પરથી જ ફોન કૉલ કરવાની મંજૂરી આપે છે"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"ઑફિસની પ્રોફાઇલ પર સ્વિચ કરો"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"બંધ કરો"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"લૉક સ્ક્રીનના સેટિંગ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index d96bc52..649c783 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"निचले किनारे से <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"बाएं किनारे से <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"दाएं किनारे से <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"वर्क प्रोफ़ाइल से लिए गए स्क्रीनशॉट, <xliff:g id="APP">%1$s</xliff:g> ऐप्लिकेशन में सेव किए गए हैं"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"स्क्रीन रिकॉर्डर"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"स्क्रीन रिकॉर्डिंग को प्रोसेस किया जा रहा है"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रिकॉर्ड सेशन के लिए जारी सूचना"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"कंट्रोल जोड़ने के लिए ऐप्लिकेशन चुनें"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# कंट्रोल जोड़ा गया.}one{# कंट्रोल जोड़ा गया.}other{# कंट्रोल जोड़े गए.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"हटाया गया"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> को जोड़ना है?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> को जोड़ने पर, वह इस पैनल पर कुछ कंट्रोल और कॉन्टेंट दिखा सकता है. कुछ ऐप्लिकेशन के लिए यह चुना जा सकता है कि वे इस पैनल पर कौनसे कंट्रोल दिखाएं."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"पसंदीदा बनाया गया"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"पसंदीदा बनाया गया, क्रम संख्या <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"पसंदीदा से हटाया गया"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> खोलें"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> पर, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> का <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> पर, <xliff:g id="SONG_NAME">%1$s</xliff:g> चलाएं"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"आपके लिए सुझाव"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"पहले जैसा करें"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> पर मीडिया चलाने के लिए, अपने डिवाइस को उसके पास ले जाएं"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"मीडिया ट्रांसफ़र करने के लिए, <xliff:g id="DEVICENAME">%1$s</xliff:g> के करीब जाएं"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"गड़बड़ी हुई, फिर से कोशिश करें"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"कंट्राेल जोड़ें"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"कंट्रोल में बदलाव करें"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ऐप्लिकेशन जोड़ें"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"आउटपुट जोड़ें"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"ग्रुप"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"एक डिवाइस चुना गया"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"स्पीकर और डिसप्ले"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"सुझाए गए डिवाइस"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"प्रीमियम खाता होना ज़रूरी है"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ब्रॉडकास्ट करने की सुविधा कैसे काम करती है"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ब्रॉडकास्ट करें"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"आपके आस-पास मौजूद लोग, ब्रॉडकास्ट किए जा रहे मीडिया को सुन सकते हैं. हालांकि, इसके लिए उनके पास ऐसे ब्लूटूथ डिवाइस होने चाहिए जिन पर मीडिया चलाया जा सके"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"ऑफ़िस की नीति के तहत, वर्क प्रोफ़ाइल होने पर ही फ़ोन कॉल किए जा सकते हैं"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"वर्क प्रोफ़ाइल पर स्विच करें"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"बंद करें"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"लॉक स्क्रीन की सेटिंग"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index cb83de9..53ad4b8 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Donji rub <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Lijevi rub <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desni rub <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Snimke zaslona s poslovnog profila spremaju se u aplikaciju <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Spremljeno u aplikaciju <xliff:g id="APP">%1$s</xliff:g> u poslovnom profilu"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Datoteke"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> otkrila je ovu snimku zaslona."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> i druge otvorene aplikacije otkrile su ovu snimku zaslona."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Snimač zaslona"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obrada snimanja zaslona"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Tekuća obavijest za sesiju snimanja zaslona"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvori <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Pustite <xliff:g id="SONG_NAME">%1$s</xliff:g> putem aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Za vas"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Poništi"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Približite se radi reprodukcije na uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Da biste reproducirali ovdje, približite se uređaju <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Zvučnici i zasloni"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Predloženi uređaji"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Zahtijeva račun s naplatom"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kako emitiranje funkcionira"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Emitiranje"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osobe u blizini s kompatibilnim Bluetooth uređajima mogu slušati medije koje emitirate"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Vaša pravila za poslovne uređaje omogućuju vam upućivanje poziva samo s poslovnog profila"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Prijeđite na poslovni profil"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Zatvori"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Postavke zaključanog zaslona"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index cabd898..69656ed 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alsó rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Bal oldali rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Jobb oldali rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"A munkahelyi képernyőképeket a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásba menti a rendszer"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Mentve a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazás munkaprofiljába"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fájlok"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"A(z) <xliff:g id="APPNAME">%1$s</xliff:g> észlelte ezt a képernyőképet."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"A(z) <xliff:g id="APPNAME">%1$s</xliff:g> és más nyitott alkalmazások észlelték ezt a képernyőképet."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Képernyőrögzítő"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Képernyőrögzítés feldolgozása"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Folyamatban lévő értesítés képernyőrögzítési munkamenethez"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> megnyitása"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> <xliff:g id="SONG_NAME">%1$s</xliff:g> című számának lejátszása innen: <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> lejátszása innen: <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Neked"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Visszavonás"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Menjen közelebb, ha itt szeretné lejátszani: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ha szeretné itt lejátszani, helyezkedjen közelebb a következőhöz: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hangfalak és kijelzők"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Javasolt eszközök"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Prémiumfiók szükséges"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"A közvetítés működése"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Közvetítés"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"A közelben tartózkodó, kompatibilis Bluetooth-eszközzel rendelkező személyek meghallgathatják az Ön közvetített médiatartalmait"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"A munkahelyi házirend csak munkaprofilból kezdeményezett telefonhívásokat engedélyez"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Váltás munkaprofilra"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Bezárás"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Lezárási képernyő beállításai"</string>
</resources>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 5b3cefb..d4c1399 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ներքևի սահմանագիծը՝ <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Ձախ կողմի սահմանագիծը՝ <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Աջ կողմի սահմանագիծը՝ <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Աշխատանքային սքրինշոթները պահվում են «<xliff:g id="APP">%1$s</xliff:g>» հավելվածում"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ֆայլեր"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Էկրանի տեսագրիչ"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Էկրանի տեսագրության մշակում"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Էկրանի տեսագրման աշխատաշրջանի ընթացիկ ծանուցում"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Ընտրեք հավելված` կառավարման տարրեր ավելացնելու համար"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Ավելացվեց կառավարման # տարր։}one{Ավելացվեց կառավարման # տարր։}other{Ավելացվեց կառավարման # տարր։}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Հեռացված է"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Ավելացնե՞լ <xliff:g id="APPNAME">%s</xliff:g> հավելվածը"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Եթե ավելացնեք <xliff:g id="APPNAME">%s</xliff:g> հավելվածը, այն կարող է կարգավորումներ և բովանդակություն ավելացնել այս վահանակում։ Որոշ հավելվածներում դուք կարող եք ընտրել, թե որ կարգավորումները ցուցադրվեն այստեղ։"</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Ավելացված է ընտրանիում"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Ավելացված է ընտրանիում, դիրքը՝ <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Հեռացված է ընտրանուց"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Բացեք <xliff:g id="APP_LABEL">%1$s</xliff:g> հավելվածը"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="ARTIST_NAME">%2$s</xliff:g>-ի կատարմամբ <xliff:g id="APP_LABEL">%3$s</xliff:g> հավելվածից"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Նվագարկել <xliff:g id="SONG_NAME">%1$s</xliff:g> երգը <xliff:g id="APP_LABEL">%2$s</xliff:g> հավելվածից"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Ձեզ համար"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Հետարկել"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ավելի մոտ եկեք՝ <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքում նվագարկելու համար"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Այստեղ նվագարկելու համար մոտեցեք <xliff:g id="DEVICENAME">%1$s</xliff:g> սարքին"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Սխալ առաջացավ։ Նորից փորձեք։"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Ավելացնել կառավարման տարրեր"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Փոփոխել կառավարման տարրերը"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Ավելացնել հավելված"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Ավելացրեք մուտքագրման սարքեր"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Խումբ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Ընտրված է 1 սարք"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Բարձրախոսներ և էկրաններ"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Առաջարկվող սարքեր"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Պահանջվում է պրեմիում հաշիվ"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Ինչպես է աշխատում հեռարձակումը"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Հեռարձակում"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Ձեր մոտակայքում գտնվող՝ համատեղելի Bluetooth սարքերով մարդիկ կարող են լսել մեդիա ֆայլերը, որոնք դուք հեռարձակում եք։"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Ձեր աշխատանքային կանոնների համաձայն՝ դուք կարող եք զանգեր կատարել աշխատանքային պրոֆիլից"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Անցնել աշխատանքային պրոֆիլ"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Փակել"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Կողպէկրանի կարգավորումներ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index a00839f..dca9565 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Batas bawah <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Batas kiri <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Batas kanan <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Screenshot dengan profil kerja disimpan di aplikasi <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Disimpan di <xliff:g id="APP">%1$s</xliff:g> di profil kerja"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"File"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> mendeteksi screenshot ini."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> dan aplikasi terbuka lainnya mendeteksi screenshot ini."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Perekam Layar"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Memproses perekaman layar"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifikasi yang sedang berjalan untuk sesi rekaman layar"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Pilih aplikasi untuk menambahkan kontrol"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrol ditambahkan.}other{# kontrol ditambahkan.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Dihapus"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Tambahkan <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Jika Anda menambahkannya, <xliff:g id="APPNAME">%s</xliff:g> dapat menambahkan kontrol dan konten ke panel ini. Di beberapa aplikasi, Anda dapat memilih kontrol yang akan muncul di sini."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Difavoritkan"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Difavoritkan, posisi <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Batal difavoritkan"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buka <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> dari <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Putar <xliff:g id="SONG_NAME">%1$s</xliff:g> dari <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Untuk Anda"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Urungkan"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Dekatkan untuk memutar di <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Untuk memutar di sini, dekatkan ke <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Error, coba lagi"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Tambahkan kontrol"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edit kontrol"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Tambahkan aplikasi"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tambahkan output"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 perangkat dipilih"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speaker & Layar"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Perangkat yang Disarankan"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Memerlukan akun premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cara kerja siaran"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Siaran"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Orang di dekat Anda dengan perangkat Bluetooth yang kompatibel dapat mendengarkan media yang sedang Anda siarkan"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Kebijakan kantor mengizinkan Anda melakukan panggilan telepon hanya dari profil kerja"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Beralih ke profil kerja"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Tutup"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Setelan layar kunci"</string>
</resources>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 319988c..ad94cdc 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Neðri mörk <xliff:g id="PERCENT">%1$d</xliff:g> prósent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vinstri mörk <xliff:g id="PERCENT">%1$d</xliff:g> prósent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Hægri mörk <xliff:g id="PERCENT">%1$d</xliff:g> prósent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Vinnuskjámyndir eru vistaðar í forritinu <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Skrár"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Skjáupptaka"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Vinnur úr skjáupptöku"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Áframhaldandi tilkynning fyrir skjáupptökulotu"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Veldu forrit til að bæta við stýringum"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# stýringu bætt við.}one{# stýringu bætt við.}other{# stýringum bætt við.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Fjarlægt"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Viltu bæta <xliff:g id="APPNAME">%s</xliff:g> við?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Þegar þú bætir <xliff:g id="APPNAME">%s</xliff:g> við getur það bætt stýringum og efni við þetta svæði. Í sumum forritum geturðu valið hvaða stýringar birtast hér."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Eftirlæti"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Eftirlæti, staða <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Fjarlægt úr eftirlæti"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Opna <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> með <xliff:g id="ARTIST_NAME">%2$s</xliff:g> í <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spila <xliff:g id="SONG_NAME">%1$s</xliff:g> í <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Fyrir þig"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Afturkalla"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Færðu nær til að spila í <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Farðu nær <xliff:g id="DEVICENAME">%1$s</xliff:g> til að spila hér"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Villa, reyndu aftur"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Bæta við stýringum"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Breyta stýringum"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Bæta við forriti"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Bæta við úttaki"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Hópur"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 tæki valið"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hátalarar og skjáir"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Tillögur að tækjum"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Krefst úrvalsreiknings"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Svona virkar útsending"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Útsending"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Fólk nálægt þér með samhæf Bluetooth-tæki getur hlustað á efnið sem þú sendir út"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Vinnureglur gera þér aðeins kleift að hringja símtöl úr vinnusniði"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Skipta yfir í vinnusnið"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Loka"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Stillingar fyrir lásskjá"</string>
</resources>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index fffb8d6..389522f 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Limite inferiore, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite sinistro, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite destro, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Gli screenshot acquisiti nel profilo di lavoro sono salvati nell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salvato nell\'app <xliff:g id="APP">%1$s</xliff:g> nel profilo di lavoro"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"File"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ha rilevato questo screenshot."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e altre app aperte hanno rilevato questo screenshot."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Registrazione dello schermo"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Elaboraz. registraz. schermo"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notifica costante per una sessione di registrazione dello schermo"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Apri <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> di <xliff:g id="ARTIST_NAME">%2$s</xliff:g> da <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Riproduci <xliff:g id="SONG_NAME">%1$s</xliff:g> da <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Per te"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Annulla"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Avvicinati per riprodurre su <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Per riprodurre qui i contenuti, avvicinati a <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speaker e display"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivi consigliati"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Occorre un account premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Come funziona la trasmissione"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Annuncio"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Le persone vicine a te che hanno dispositivi Bluetooth compatibili possono ascoltare i contenuti multimediali che stai trasmettendo"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Le norme di lavoro ti consentono di fare telefonate soltanto dal profilo di lavoro"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Passa a profilo di lavoro"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Chiudi"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Impostazioni schermata di blocco"</string>
</resources>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index dfe8a64..95871fc 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"<xliff:g id="PERCENT">%1$d</xliff:g> אחוז מהשוליים התחתונים"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> אחוז מהשוליים השמאליים"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> אחוז מהשוליים הימניים"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"צילומי מסך בפרופיל העבודה נשמרים באפליקציה <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"קבצים"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"מקליט המסך"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"מתבצע עיבוד של הקלטת מסך"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"התראה מתמשכת לסשן הקלטת מסך"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"יש לבחור אפליקציה כדי להוסיף פקדים"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{נוסף אמצעי בקרה אחד (#).}one{נוספו # אמצעי בקרה.}two{נוספו # אמצעי בקרה.}other{נוספו # אמצעי בקרה.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"הוסר"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"להוסיף את <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"כשמוסיפים את האפליקציה <xliff:g id="APPNAME">%s</xliff:g>, היא תוכל להוסיף אמצעי בקרה ותוכן לחלונית הזו. חלק מהאפליקציות מאפשרות לבחור אילו אמצעי בקרה יוצגו כאן."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"סומן כמועדף"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"סומן כמועדף, במיקום <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"הוסר מהמועדפים"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"פתיחה של <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> של <xliff:g id="ARTIST_NAME">%2$s</xliff:g> מ-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"הפעלת <xliff:g id="SONG_NAME">%1$s</xliff:g> מ-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"בשבילך"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ביטול"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"צריך להתקרב כדי להפעיל מדיה במכשיר <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"כדי להפעיל במכשיר הזה, יש להתקרב אל <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"שגיאה, יש לנסות שוב"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"הוספת פקדים"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"עריכת פקדים"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"הוספת אפליקציה"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"הוספת מכשירי פלט"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"קבוצה"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"נבחר מכשיר אחד"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"רמקולים ומסכים"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"הצעות למכשירים"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"נדרש חשבון פרימיום"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"הסבר על שידורים"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"שידור"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"אנשים בקרבת מקום עם מכשירי Bluetooth תואמים יכולים להאזין למדיה שמשודרת על ידך"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"המדיניות של מקום העבודה מאפשרת לך לבצע שיחות טלפון רק מפרופיל העבודה"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"מעבר לפרופיל עבודה"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"סגירה"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"הגדרות מסך הנעילה"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 7db687a..92e2c78 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"下部の境界線 <xliff:g id="PERCENT">%1$d</xliff:g> パーセント"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左の境界線 <xliff:g id="PERCENT">%1$d</xliff:g> パーセント"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右の境界線 <xliff:g id="PERCENT">%1$d</xliff:g> パーセント"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"仕事用のスクリーンショットは <xliff:g id="APP">%1$s</xliff:g> アプリに保存されます"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"仕事用プロファイルで <xliff:g id="APP">%1$s</xliff:g> に保存しました"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ファイル"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> がこのスクリーンショットを検出しました。"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> とその他の開いているアプリがこのスクリーンショットを検出しました。"</string>
<string name="screenrecord_name" msgid="2596401223859996572">"スクリーン レコーダー"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"画面の録画を処理しています"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"画面の録画セッション中の通知"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> を開く"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g>(アーティスト名: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>)を <xliff:g id="APP_LABEL">%3$s</xliff:g> で再生"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> を <xliff:g id="APP_LABEL">%2$s</xliff:g> で再生"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"おすすめ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"元に戻す"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>で再生するにはもっと近づけてください"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"このデバイスで再生するには、<xliff:g id="DEVICENAME">%1$s</xliff:g> にもっと近づけてください"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"スピーカーとディスプレイ"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"デバイスの候補"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"プレミアム アカウントが必要です"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ブロードキャストの仕組み"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ブロードキャスト"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Bluetooth 対応デバイスを持っている付近のユーザーは、あなたがブロードキャストしているメディアを聴けます"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"仕事用ポリシーでは、通話の発信を仕事用プロファイルからのみに制限できます"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"仕事用プロファイルに切り替える"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"閉じる"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"ロック画面の設定"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index f4c934c..3d8acb3 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ქვედა ზღვარი: <xliff:g id="PERCENT">%1$d</xliff:g> პროცენტი"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"მარცხენა ზღვარი: <xliff:g id="PERCENT">%1$d</xliff:g> პროცენტი"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"მარჯვენა ზღვარი: <xliff:g id="PERCENT">%1$d</xliff:g> პროცენტი"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"სამუშაო ეკრანის ანაბეჭდები ინახება <xliff:g id="APP">%1$s</xliff:g> აპში"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"შენახულია <xliff:g id="APP">%1$s</xliff:g>-ში სამსახურის პროფილში"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ფაილები"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g>-მა აღმოაჩინა ეკრანის ეს ანაბეჭდი"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g>-მა და სხვა გახსნილმა აპებმა აღმოაჩინეს ეკრანის ეს ანაბეჭდი."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"ეკრანის ჩამწერი"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ეკრანის ჩანაწერი მუშავდება"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"უწყვეტი შეტყობინება ეკრანის ჩაწერის სესიისთვის"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"გახსენით <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g>, <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="APP_LABEL">%3$s</xliff:g>-დან"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"დაუკარით <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g>-დან"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"თქვენთვის"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"მოქმედ.გაუქმება"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"მიიტანეთ უფრო ახლოს, რომ დაუკრათ <xliff:g id="DEVICENAME">%1$s</xliff:g>-ზე"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"აქ სათამაშოდ, მიუახლოვდით <xliff:g id="DEVICENAME">%1$s</xliff:g>-ს"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"დინამიკები და დისპლეები"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"შემოთავაზებული მოწყობილობები"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"საჭიროა პრემიუმ-ანგარიში"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ტრანსლირების მუშაობის პრინციპი"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ტრანსლაცია"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"თქვენთან ახლოს მყოფ ხალხს თავსებადი Bluetooth მოწყობილობით შეუძლიათ თქვენ მიერ ტრანსლირებული მედიის მოსმენა"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"თქვენი სამსახურის წესები საშუალებას გაძლევთ, სატელეფონო ზარები განახორციელოთ მხოლოდ სამსახურის პროფილიდან"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"სამსახურის პროფილზე გადართვა"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"დახურვა"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"ჩაკეტილი ეკრანის პარამეტრები"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index a3b330c..0f9dc95 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Төменгі шектік сызық: <xliff:g id="PERCENT">%1$d</xliff:g> пайыз"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Сол жақ шектік сызық: <xliff:g id="PERCENT">%1$d</xliff:g> пайыз"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Оң жақ шектік сызық: <xliff:g id="PERCENT">%1$d</xliff:g> пайыз"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Жұмыс профилінен алынған скриншоттар <xliff:g id="APP">%1$s</xliff:g> қолданбасында сақталады."</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Жұмыс профиліндегі <xliff:g id="APP">%1$s</xliff:g> қолданбасында сақталған."</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> қолданбасы осы скриншотты анықтады."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> және басқа да ашық қолданбалар осы скриншотты анықтады."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Экран жазғыш"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Экран жазғыш бейнесін өңдеу"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Экранды бейнеге жазудың ағымдағы хабарландыруы"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Басқару элементтері қосылатын қолданбаны таңдаңыз"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# басқару элементі қосылды.}other{# басқару элементі қосылды.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Өшірілді"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> қолданбасын қосу керек пе?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> қолданбасын қосқан кезде, басқару элементтері мен контент осы панельге енгізіледі. Кейбір қолданбада басқару элементтерінің қайсысы осы жерде көрсетілетінін таңдай аласыз."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Таңдаулыларға қосылды"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Таңдаулыларға қосылды, <xliff:g id="NUMBER">%d</xliff:g>-позиция"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Таңдаулылардан алып тасталды"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> қолданбасын ашу"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> қолданбасында <xliff:g id="ARTIST_NAME">%2$s</xliff:g> орындайтын \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> қолданбасында \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" әнін ойнату"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Сіз үшін"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Қайтару"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысында музыка ойнату үшін оған жақындаңыз."</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Осы жерде ойнату үшін <xliff:g id="DEVICENAME">%1$s</xliff:g> құрылғысына жақындаңыз."</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Қате шықты. Қайталап көріңіз."</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Басқару элементтерін қосу"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Басқару элементтерін өзгерту"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Қолданба қосу"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Шығыс сигналдарды қосу"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Топ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 құрылғы таңдалды."</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Динамиктер мен дисплейлер"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ұсынылған құрылғылар"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Премиум аккаунт қажет"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Тарату қалай жүзеге асады"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Тарату"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Үйлесімді Bluetooth құрылғылары бар маңайдағы адамдар сіз таратып жатқан медиамазмұнды тыңдай алады."</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Жұмыс саясатыңызға сәйкес тек жұмыс профилінен қоңырау шалуға болады."</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Жұмыс профиліне ауысу"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Жабу"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Экран құлпының параметрлері"</string>
</resources>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 05d0429..9f80e33 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"បន្ទាត់បែងចែកខាងក្រោម <xliff:g id="PERCENT">%1$d</xliff:g> ភាគរយ"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"បន្ទាត់បែងចែកខាងឆ្វេង <xliff:g id="PERCENT">%1$d</xliff:g> ភាគរយ"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"បន្ទាត់បែងចែកខាងស្ដាំ <xliff:g id="PERCENT">%1$d</xliff:g> ភាគរយ"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"រូបថតអេក្រង់ក្នុងកម្រងព័ត៌មានការងារត្រូវបានរក្សាទុកនៅក្នុងកម្មវិធី <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ឯកសារ"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"មុខងារថតវីដេអូអេក្រង់"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"កំពុងដំណើរការការថតអេក្រង់"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ការជូនដំណឹងដែលកំពុងដំណើរការសម្រាប់រយៈពេលប្រើការថតសកម្មភាពអេក្រង់"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"ជ្រើសរើសកម្មវិធីដែលត្រូវបញ្ចូលផ្ទាំងគ្រប់គ្រង"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{បានបញ្ចូលការគ្រប់គ្រង #។}other{បានបញ្ចូលការគ្រប់គ្រង #។}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"បានដកចេញ"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"បញ្ចូល <xliff:g id="APPNAME">%s</xliff:g> ឬ?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"នៅពេលអ្នកបញ្ចូល <xliff:g id="APPNAME">%s</xliff:g> កម្មវិធីនេះអាចបញ្ចូលការគ្រប់គ្រង និងខ្លឹមសារទៅផ្ទាំងនេះបាន។ ក្នុងកម្មវិធីមួយចំនួន អ្នកអាចជ្រើសរើសឱ្យការគ្រប់គ្រងណាខ្លះបង្ហាញនៅទីនេះបាន។"</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"បានដាក់ជាសំណព្វ"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"បានដាក់ជាសំណព្វ ទីតាំងទី <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"បានដកចេញពីសំណព្វ"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"បើក <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ច្រៀងដោយ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ពី <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ចាក់ <xliff:g id="SONG_NAME">%1$s</xliff:g> ពី <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"សម្រាប់អ្នក"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ត្រឡប់វិញ"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"រំកិលឱ្យកាន់តែជិត ដើម្បីចាក់នៅលើ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ដើម្បីចាក់នៅទីនេះ សូមខិតទៅជិត <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"មានបញ្ហា សូមព្យាយាមម្តងទៀត"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"បញ្ចូលផ្ទាំងគ្រប់គ្រង"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"កែផ្ទាំងគ្រប់គ្រង"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"បញ្ចូលកម្មវិធី"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"បញ្ចូលឧបករណ៍មេឌៀ"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"ក្រុម"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"បានជ្រើសរើសឧបករណ៍ 1"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ឧបករណ៍បំពងសំឡេង និងផ្ទាំងអេក្រង់"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ឧបករណ៍ដែលបានណែនាំ"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"ត្រូវការគណនីលំដាប់ខ្ពស់"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"របៀបដែលការផ្សាយដំណើរការ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ការផ្សាយ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"មនុស្សនៅជិតអ្នកដែលមានឧបករណ៍ប៊្លូធូសត្រូវគ្នាអាចស្តាប់មេឌៀដែលអ្នកកំពុងផ្សាយបាន"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"គោលការណ៍ការងាររបស់អ្នកអនុញ្ញាតឱ្យអ្នកធ្វើការហៅទូរសព្ទបានតែពីកម្រងព័ត៌មានការងារប៉ុណ្ណោះ"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"ប្ដូរទៅកម្រងព័ត៌មានការងារ"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"បិទ"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"ការកំណត់អេក្រង់ចាក់សោ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index e543780..308707a 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ಕೆಳಗಿನ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ಎಡಭಾಗದ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ಬಲಭಾಗದ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"<xliff:g id="APP">%1$s</xliff:g> ಆ್ಯಪ್ನಲ್ಲಿ ಉಳಿಸಲಾದ ಕೆಲಸದ ಸ್ಕ್ರೀನ್ಶಾಟ್ಗಳು"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್ನಲ್ಲಿನ <xliff:g id="APP">%1$s</xliff:g> ನಲ್ಲಿ ಉಳಿಸಲಾಗಿದೆ"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ಫೈಲ್ಗಳು"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"ಈ ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು <xliff:g id="APPNAME">%1$s</xliff:g> ಪತ್ತೆಹಚ್ಚಿದೆ."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ಹಾಗೂ ತೆರೆದಿರುವ ಇತರ ಆ್ಯಪ್ಗಳು ಈ ಸ್ಕ್ರೀನ್ಶಾಟ್ ಅನ್ನು ಪತ್ತೆಹಚ್ಚಿವೆ."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡರ್"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಆಗುತ್ತಿದೆ"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡಿಂಗ್ ಸೆಶನ್ಗಾಗಿ ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಅಧಿಸೂಚನೆ"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ಅನ್ನು ತೆರೆಯಿರಿ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ಅವರ <xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%3$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ಹಾಡನ್ನು <xliff:g id="APP_LABEL">%2$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಿ"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"ನಿಮಗಾಗಿ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ರದ್ದುಗೊಳಿಸಿ"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು ಅದರ ಹತ್ತಿರಕ್ಕೆ ಸರಿಯಿರಿ"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ಇಲ್ಲಿ ಪ್ಲೇ ಮಾಡಲು, <xliff:g id="DEVICENAME">%1$s</xliff:g> ಸಮೀಪಕ್ಕೆ ಸರಿಯಿರಿ"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ಸ್ಪೀಕರ್ಗಳು ಮತ್ತು ಡಿಸ್ಪ್ಲೇಗಳು"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ಸೂಚಿಸಿದ ಸಾಧನಗಳು"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"ಪ್ರೀಮಿಯಂ ಖಾತೆಯ ಅಗತ್ಯವಿದೆ"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ಪ್ರಸಾರವು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ಪ್ರಸಾರ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ಹೊಂದಾಣಿಕೆಯಾಗುವ ಬ್ಲೂಟೂತ್ ಸಾಧನಗಳನ್ನು ಹೊಂದಿರುವ ಸಮೀಪದಲ್ಲಿರುವ ಜನರು ನೀವು ಪ್ರಸಾರ ಮಾಡುತ್ತಿರುವ ಮಾಧ್ಯಮವನ್ನು ಆಲಿಸಬಹುದು"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"ನಿಮ್ಮ ಕೆಲಸದ ನೀತಿಯು ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ನಿಂದ ಮಾತ್ರ ಫೋನ್ ಕರೆಗಳನ್ನು ಮಾಡಲು ನಿಮಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ಗೆ ಬದಲಿಸಿ"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"ಮುಚ್ಚಿರಿ"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"ಲಾಕ್ ಸ್ಕ್ರೀನ್ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index ad5b523..2e4aca7 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"하단 가장자리 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"왼쪽 가장자리 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"오른쪽 가장자리 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"직장 프로필의 스크린샷은 <xliff:g id="APP">%1$s</xliff:g> 앱에 저장됩니다."</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"직장 프로필의 <xliff:g id="APP">%1$s</xliff:g>에 저장되었습니다."</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"파일"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g>에서 이 스크린샷을 감지했습니다."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> 및 기타 공개 앱에서 이 스크린샷을 감지했습니다."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"화면 녹화"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"화면 녹화 처리 중"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"화면 녹화 세션에 관한 지속적인 알림"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"컨트롤을 추가할 앱을 선택하세요"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{설정이 #개 추가되었습니다.}other{설정이 #개 추가되었습니다.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"삭제됨"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g>을(를) 추가할까요?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> 앱을 추가하면 이 패널에 컨트롤과 콘텐츠가 추가됩니다. 일부 앱에서는 여기 표시되는 컨트롤을 선택할 수 있습니다."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"즐겨찾기에 추가됨"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"즐겨찾기에 추가됨, 위치 <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"즐겨찾기에서 삭제됨"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> 열기"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>에서 <xliff:g id="ARTIST_NAME">%2$s</xliff:g>의 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>에서 <xliff:g id="SONG_NAME">%1$s</xliff:g> 재생"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"추천"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"실행취소"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>에서 재생하려면 기기를 더 가까이로 옮기세요."</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"여기에서 재생하려면 <xliff:g id="DEVICENAME">%1$s</xliff:g>에 더 가까이 이동하세요."</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"오류. 다시 시도하세요."</string>
<string name="controls_menu_add" msgid="4447246119229920050">"컨트롤 추가"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"컨트롤 수정"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"앱 추가"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"출력 추가"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"그룹"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"기기 1대 선택됨"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"스피커 및 디스플레이"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"추천 기기"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"프리미엄 계정 필요"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"브로드캐스팅 작동 원리"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"브로드캐스트"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"호환되는 블루투스 기기를 가진 근처의 사용자가 내가 브로드캐스트 중인 미디어를 수신 대기할 수 있습니다."</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"직장 정책이 직장 프로필에서만 전화를 걸도록 허용합니다."</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"직장 프로필로 전환"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"닫기"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"잠금 화면 설정"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 5c6166a..e4662ed 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ылдый жагы <xliff:g id="PERCENT">%1$d</xliff:g> пайызга"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Сол жагы <xliff:g id="PERCENT">%1$d</xliff:g> пайызга"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Оң жагы <xliff:g id="PERCENT">%1$d</xliff:g> пайызга"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Жумуш скриншоттору <xliff:g id="APP">%1$s</xliff:g> колдонмосунда сакталат"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Жумуш профилиндеги <xliff:g id="APP">%1$s</xliff:g> колдонмосуна сакталды"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ушул скриншотту аныктады."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> жана ачылып турган башка колдонмолор ушул скриншотту аныктады."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"экрандан видео жаздырып алуу"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Экрандан жаздырылып алынган видео иштетилүүдө"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Экранды жаздыруу сеансы боюнча учурдагы билдирме"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> колдонмосун ачуу"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын (аткаруучу: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) <xliff:g id="APP_LABEL">%3$s</xliff:g> колдонмосунан ойнотуу"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ырын <xliff:g id="APP_LABEL">%2$s</xliff:g> колдонмосунан ойнотуу"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Сизге"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Кайтаруу"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүндө ойнотуу үчүн жакындатыңыз"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ушул жерде ойнотуу үчүн <xliff:g id="DEVICENAME">%1$s</xliff:g> түзмөгүнө жакындаңыз"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Динамиктер жана дисплейлер"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Сунушталган түзмөктөр"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Премиум аккаунт талап кылынат"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Кабарлоо кантип иштейт"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Кабарлоо"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Шайкеш Bluetooth түзмөктөрү болгон жакын жердеги кишилер кабарлап жаткан медиаңызды уга алышат"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Жумуш саясатыңызга ылайык, жумуш профилинен гана чалууларды аткара аласыз"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Жумуш профилине которулуу"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Жабуу"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Кулпуланган экран параметрлери"</string>
</resources>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index fff2544..ac81dcc 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -64,4 +64,6 @@
<dimen name="qs_panel_padding_top">@dimen/qqs_layout_margin_top</dimen>
<dimen name="qs_panel_padding_top_combined_headers">@dimen/qs_panel_padding_top</dimen>
+
+ <dimen name="controls_padding_horizontal">16dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index c07470a..8b99ac3 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ຂອບເຂດທາງລຸ່ມ <xliff:g id="PERCENT">%1$d</xliff:g> ເປີເຊັນ"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ຂອບເຂດທາງຊ້າຍ <xliff:g id="PERCENT">%1$d</xliff:g> ເປີເຊັນ"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ຂອບເຂດທາງຂວາ <xliff:g id="PERCENT">%1$d</xliff:g> ເປີເຊັນ"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ຮູບໜ້າຈໍວຽກຖືກບັນທຶກຢູ່ໃນແອັບ <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ບັນທຶກໃນ <xliff:g id="APP">%1$s</xliff:g> ໃນໂປຣໄຟລ໌ບ່ອນເຮັດວຽກແລ້ວ"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ໄຟລ໌"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ກວດພົບຮູບໜ້າຈໍນີ້."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ແລະ ແອັບອື່ນໆທີ່ເປີດຢູ່ກວດພົບຮູບໜ້າຈໍນີ້."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"ໂປຣແກຣມບັນທຶກໜ້າຈໍ"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ກຳລັງປະມວນຜົນການບັນທຶກໜ້າຈໍ"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ການແຈ້ງເຕືອນສຳລັບເຊດຊັນການບັນທຶກໜ້າຈໍໃດໜຶ່ງ"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"ເປີດ <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ໂດຍ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"ຫຼິ້ນ <xliff:g id="SONG_NAME">%1$s</xliff:g> ຈາກ <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"ສຳລັບທ່ານ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ຍົກເລີກ"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"ຍ້າຍໄປໃກ້ຂຶ້ນເພື່ອຫຼິ້ນຢູ່ <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ເພື່ອຫຼິ້ນຢູ່ບ່ອນນີ້, ໃຫ້ເຂົ້າໃກ້ <xliff:g id="DEVICENAME">%1$s</xliff:g> ຫຼາຍຂຶ້ນ"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ລຳໂພງ ແລະ ຈໍສະແດງຜົນ"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ອຸປະກອນທີ່ແນະນຳ"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"ຕ້ອງໃຊ້ບັນຊີພຣີມຽມ"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ການອອກອາກາດເຮັດວຽກແນວໃດ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ອອກອາກາດ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ຄົນທີ່ຢູ່ໃກ້ທ່ານທີ່ມີອຸປະກອນ Bluetooth ທີ່ເຂົ້າກັນໄດ້ຈະສາມາດຟັງມີເດຍທີ່ທ່ານກຳລັງອອກອາກາດຢູ່ໄດ້"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"ນະໂຍບາຍບ່ອນເຮັດວຽກຂອງທ່ານອະນຸຍາດໃຫ້ທ່ານໂທລະສັບໄດ້ຈາກໂປຣໄຟລ໌ບ່ອນເຮັດວຽກເທົ່ານັ້ນ"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"ສະຫຼັບໄປໃຊ້ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"ປິດ"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"ການຕັ້ງຄ່າໜ້າຈໍລັອກ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 737e652..2c4d55b 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Apatinė riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kairioji riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Dešinioji riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Ekrano kopijos, užfiksuotos naudojantis darbo profiliu, išsaugomos programoje „<xliff:g id="APP">%1$s</xliff:g>“"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Failai"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Ekrano vaizdo įrašytuvas"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Apdorojam. ekrano vaizdo įraš."</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Šiuo metu rodomas ekrano įrašymo sesijos pranešimas"</string>
@@ -852,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Atidaryti „<xliff:g id="APP_LABEL">%1$s</xliff:g>“"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Leisti <xliff:g id="ARTIST_NAME">%2$s</xliff:g> – „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%3$s</xliff:g>“"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Leisti „<xliff:g id="SONG_NAME">%1$s</xliff:g>“ iš „<xliff:g id="APP_LABEL">%2$s</xliff:g>“"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Jums"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Anuliuoti"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Prieikite arčiau, kad galėtumėte leisti įrenginyje „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Jei norite leisti čia, eikite arčiau įrenginio „<xliff:g id="DEVICENAME">%1$s</xliff:g>“"</string>
@@ -884,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Garsiakalbiai ir ekranai"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Siūlomi įrenginiai"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Reikalinga mokama paskyra"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kaip veikia transliacija"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transliacija"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Netoliese esantys žmonės, turintys suderinamus „Bluetooth“ įrenginius, gali klausyti jūsų transliuojamos medijos"</string>
@@ -1029,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Pagal jūsų darbo politiką galite skambinti telefonu tik iš darbo profilio"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Perjungti į darbo profilį"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Uždaryti"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Užrakinimo ekrano nustatymai"</string>
</resources>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 9200573..412bfe8 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Apakšmala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kreisā mala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Labā mala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Darba profila ekrānuzņēmumi tiek saglabāti lietotnē <xliff:g id="APP">%1$s</xliff:g>."</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Faili"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Ekrāna ierakstītājs"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekrāna ieraksta apstrāde"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Aktīvs paziņojums par ekrāna ierakstīšanas sesiju"</string>
@@ -852,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Atveriet lietotni <xliff:g id="APP_LABEL">%1$s</xliff:g>."</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” (izpildītājs: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) no lietotnes <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Atskaņojiet failu “<xliff:g id="SONG_NAME">%1$s</xliff:g>” no lietotnes <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Tieši jums"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Atsaukt"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Pārvietojiet savu ierīci tuvāk, lai atskaņotu mūziku ierīcē “<xliff:g id="DEVICENAME">%1$s</xliff:g>”."</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Lai atskaņotu šeit, jums jāatrodas tuvāk šai ierīcei: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Skaļruņi un displeji"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ieteiktās ierīces"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Nepieciešams maksas konts"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kā darbojas apraide"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Apraide"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Tuvumā esošās personas ar saderīgām Bluetooth ierīcēm var klausīties jūsu apraidīto multivides saturu."</string>
@@ -1029,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Saskaņā ar jūsu darba politiku tālruņa zvanus drīkst veikt tikai no darba profila"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Pārslēgties uz darba profilu"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Aizvērt"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Bloķēšanas ekrāna iestatījumi"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 5052c2c..613dff3 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Долна граница <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Лева граница <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Десна граница <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Сликите од екранот во работниот профил се зачувуваат во апликацијата <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Зачувано во <xliff:g id="APP">%1$s</xliff:g> во работниот профил"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Датотеки"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ја откри оваа слика од екранот."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> и други отворени апликации ја открија оваа слика од екранот."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Снимач на екран"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Се обработува снимка од екран"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Тековно известување за сесија за снимање на екранот"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отворете <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> од <xliff:g id="ARTIST_NAME">%2$s</xliff:g> на <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пуштете <xliff:g id="SONG_NAME">%1$s</xliff:g> на <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"За вас"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Врати"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Приближете се за да пуштите на <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"За да пуштате овде, приближете се до <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Звучници и екрани"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Предложени уреди"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Потребна е премиум сметка"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Како функционира емитувањето"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Емитување"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Луѓето во ваша близина со компатибилни уреди со Bluetooth може да ги слушаат аудиозаписите што ги емитувате"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Вашето работно правило ви дозволува да упатувате повици само од работниот профил"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Префрли се на работен профил"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Затвори"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Поставки за заклучен екран"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index f4e461d..f3848d53 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"താഴെയുള്ള അതിർത്തി <xliff:g id="PERCENT">%1$d</xliff:g> ശതമാനം"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ഇടത് വശത്തെ അതിർത്തി <xliff:g id="PERCENT">%1$d</xliff:g> ശതമാനം"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"വലത് വശത്തെ അതിർത്തി <xliff:g id="PERCENT">%1$d</xliff:g> ശതമാനം"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ഔദ്യോഗിക പ്രൊഫൈലിന്റെ സ്ക്രീന്ഷോട്ടുകൾ <xliff:g id="APP">%1$s</xliff:g> ആപ്പിൽ സംരക്ഷിച്ചു"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ഔദ്യോഗിക പ്രൊഫൈലിൽ <xliff:g id="APP">%1$s</xliff:g> ആപ്പിൽ സംരക്ഷിച്ചു"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ഫയലുകൾ"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ഈ സ്ക്രീൻഷോട്ട് തിരിച്ചറിഞ്ഞു."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> എന്ന ആപ്പും തുറന്നിരിക്കുന്ന മറ്റ് ആപ്പും ഈ സ്ക്രീൻഷോട്ട് തിരിച്ചറിഞ്ഞു."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"സ്ക്രീൻ റെക്കോർഡർ"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"സ്ക്രീൻ റെക്കോർഡിംഗ് പ്രോസസുചെയ്യുന്നു"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ഒരു സ്ക്രീൻ റെക്കോർഡിംഗ് സെഷനായി നിലവിലുള്ള അറിയിപ്പ്"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> തുറക്കുക"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> എന്ന ആർട്ടിസ്റ്റിന്റെ <xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%3$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> എന്ന ഗാനം <xliff:g id="APP_LABEL">%2$s</xliff:g> ആപ്പിൽ പ്ലേ ചെയ്യുക"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"നിങ്ങൾക്കുള്ളവ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"പഴയപടിയാക്കുക"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യാൻ അടുത്തേക്ക് നീക്കുക"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ഇവിടെ പ്ലേ ചെയ്യാൻ <xliff:g id="DEVICENAME">%1$s</xliff:g> എന്നതിനടുത്തേക്ക് നീക്കുക"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"സ്പീക്കറുകളും ഡിസ്പ്ലേകളും"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"നിർദ്ദേശിച്ച ഉപകരണങ്ങൾ"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"പ്രീമിയം അക്കൗണ്ട് ആവശ്യമാണ്"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ബ്രോഡ്കാസ്റ്റ് എങ്ങനെയാണ് പ്രവർത്തിക്കുന്നത്"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ബ്രോഡ്കാസ്റ്റ്"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"അനുയോജ്യമായ Bluetooth ഉപകരണങ്ങളോടെ സമീപമുള്ള ആളുകൾക്ക് നിങ്ങൾ ബ്രോഡ്കാസ്റ്റ് ചെയ്യുന്ന മീഡിയ കേൾക്കാനാകും"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"ഔദ്യോഗിക പ്രൊഫൈലിൽ നിന്ന് മാത്രം ഫോൺ കോളുകൾ ചെയ്യാനാണ് നിങ്ങളുടെ ഔദ്യോഗിക നയം അനുവദിക്കുന്നത്"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"ഔദ്യോഗിക പ്രൊഫൈലിലേക്ക് മാറുക"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"അടയ്ക്കുക"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"ലോക്ക് സ്ക്രീൻ ക്രമീകരണം"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index f4ace18..0b108a2 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Доод талын хязгаар <xliff:g id="PERCENT">%1$d</xliff:g> хувь"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Зүүн талын хязгаар <xliff:g id="PERCENT">%1$d</xliff:g> хувь"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Баруун талын хязгаар <xliff:g id="PERCENT">%1$d</xliff:g> хувь"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Ажлын дэлгэцийн агшнуудыг <xliff:g id="APP">%1$s</xliff:g> апп дээр хадгалсан"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Ажлын профайл дахь <xliff:g id="APP">%1$s</xliff:g>-д хадгалсан"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлс"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> энэ дэлгэцийн агшныг илрүүлсэн."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> болон бусад нээлттэй апп энэ дэлгэцийн агшныг илрүүлсэн."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Дэлгэцийн үйлдэл бичигч"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Дэлгэц бичлэг боловсруулж байна"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Дэлгэц бичих горимын үргэлжилж буй мэдэгдэл"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Хяналтууд нэмэхийн тулд аппыг сонгоно уу"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# хяналт нэмсэн.}other{# хяналт нэмсэн.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Хассан"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g>-г нэмэх үү?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Та <xliff:g id="APPNAME">%s</xliff:g>-г нэмэх үед энэ нь уг түр зуурын самбарт тохиргоо болон контент нэмэх боломжтой. Зарим аппад та энд ямар тохиргоог харуулахыг сонгох боломжтой."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Дуртай гэж тэмдэглэсэн"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"<xliff:g id="NUMBER">%d</xliff:g>-р байршилд дуртай гэж тэмдэглэсэн"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Дургүй гэж тэмдэглэсэн"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g>-г нээх"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>-н <xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%3$s</xliff:g> дээр тоглуулах"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g>-г <xliff:g id="APP_LABEL">%2$s</xliff:g> дээр тоглуулах"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Танд зориулсан"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Болих"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> дээр тоглуулахын тулд төхөөрөмжөө ойртуулна уу"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Энд тоглуулахын тулд <xliff:g id="DEVICENAME">%1$s</xliff:g> руу ойртуулна уу"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Алдаа гарав, дахин оролдоно уу"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Хяналт нэмэх"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Хяналтыг өөрчлөх"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Апп нэмэх"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Гаралт нэмэх"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Бүлэг"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 төхөөрөмж сонгосон"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Чанга яригч ба дэлгэц"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Санал болгосон төхөөрөмжүүд"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Тусгай бүртгэл шаарддаг"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Нэвтрүүлэлт хэрхэн ажилладаг вэ?"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Нэвтрүүлэлт"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Тохиромжтой Bluetooth төхөөрөмжүүдтэй таны ойролцоох хүмүүс таны нэвтрүүлж буй медиаг сонсох боломжтой"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Таны ажлын бодлого танд зөвхөн ажлын профайлаас утасны дуудлага хийхийг зөвшөөрдөг"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Ажлын профайл руу сэлгэх"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Хаах"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Түгжигдсэн дэлгэцийн тохиргоо"</string>
</resources>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 96ecc99..214c92f 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"खालील सीमेपासून <xliff:g id="PERCENT">%1$d</xliff:g> टक्के"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"डाव्या सीमेपासून <xliff:g id="PERCENT">%1$d</xliff:g> टक्के"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"उजव्या सीमेपासून <xliff:g id="PERCENT">%1$d</xliff:g> टक्के"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ऑफिससंबंधित स्क्रीनशॉट <xliff:g id="APP">%1$s</xliff:g> अॅपमध्ये सेव्ह केले आहेत"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"<xliff:g id="APP">%1$s</xliff:g> मधील कार्य प्रोफाइलमध्ये सेव्ह केला"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"फाइल"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ने हा स्क्रीनशॉट डिटेक्ट केला."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> आणि उघडलेल्या इतर अॅप्सनी हा स्क्रीनशॉट डिटेक्ट केला."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"स्क्रीन रेकॉर्डर"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"स्क्रीन रेकॉर्डिंग प्रोसेस सुरू"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"स्क्रीन रेकॉर्ड सत्रासाठी सुरू असलेली सूचना"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> उघडा"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> मध्ये <xliff:g id="ARTIST_NAME">%2$s</xliff:g> चे <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> मध्ये <xliff:g id="SONG_NAME">%1$s</xliff:g> प्ले करा"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"तुमच्यासाठी"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"पहिल्यासारखे करा"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> वर प्ले करण्यासाठी जवळ जा"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"येथे प्ले करण्यासाठी, <xliff:g id="DEVICENAME">%1$s</xliff:g> च्या जवळ जा"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"स्पीकर आणि डिस्प्ले"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"सुचवलेली डिव्हाइस"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"प्रीमियम खाते आवश्यक आहे"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ब्रॉडकास्टिंग कसे काम करते"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ब्रॉडकास्ट करा"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"कंपॅटिबल ब्लूटूथ डिव्हाइस असलेले तुमच्या जवळपासचे लोक हे तुम्ही ब्रॉडकास्ट करत असलेला मीडिया ऐकू शकतात"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"तुमचे कामाशी संबंधित धोरण तुम्हाला फक्त कार्य प्रोफाइलवरून फोन कॉल करन्याची अनुमती देते"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"कार्य प्रोफाइलवर स्विच करा"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"बंद करा"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"लॉक स्क्रीन सेटिंग्ज"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 2782925..ff9897d 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Sempadan bawah <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sempadan kiri <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sempadan kanan <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Tangkapan skrin tugasan disimpan dalam apl <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Disimpan dalam <xliff:g id="APP">%1$s</xliff:g> dalam profil kerja"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fail"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> telah mengesan tangkapan skrin ini."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> dan apl lain yang dibuka telah mengesan tangkapan skrin ini."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Perakam Skrin"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Memproses rakaman skrin"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Pemberitahuan breterusan untuk sesi rakaman skrin"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Pilih apl untuk menambahkan kawalan"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kawalan ditambah.}other{# kawalan ditambah.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Dialih keluar"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Tambahkan <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Apabila anda menambahkan <xliff:g id="APPNAME">%s</xliff:g>, apl ini boleh menambahkan kawalan dan kandungan pada panel ini. Dalam sesetengah apl, anda boleh memilih kawalan yang muncul di sini."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Digemari"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Digemari, kedudukan <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Dinyahgemari"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buka <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> oleh <xliff:g id="ARTIST_NAME">%2$s</xliff:g> daripada <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Mainkan <xliff:g id="SONG_NAME">%1$s</xliff:g> daripada <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Untuk Anda"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Buat asal"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Alihkan lebih dekat untuk bermain pada<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Untuk bermain di sini, bergerak lebih dekat kepada <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Ralat, cuba lagi"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Tambah kawalan"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edit kawalan"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Tambahkan apl"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Tambah output"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Kumpulan"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 peranti dipilih"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Pembesar Suara & Paparan"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Peranti yang Dicadangkan"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Memerlukan akaun premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cara siaran berfungsi"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Siarkan"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Orang berdekatan anda dengan peranti Bluetooth yang serasi boleh mendengar media yang sedang anda siarkan"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Dasar kerja anda membenarkan anda membuat panggilan telefon hanya daripada profil kerja"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Tukar kepada profil kerja"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Tutup"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Tetapan skrin kunci"</string>
</resources>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index f875654..8abb23c 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"အောက်ခြေအနားသတ် <xliff:g id="PERCENT">%1$d</xliff:g> ရာခိုင်နှုန်း"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ဘယ်ဘက်အနားသတ် <xliff:g id="PERCENT">%1$d</xliff:g> ရာခိုင်နှုန်း"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ညာဘက်အနားသတ် <xliff:g id="PERCENT">%1$d</xliff:g> ရာခိုင်နှုန်း"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"အလုပ်နှင့်ဆိုင်သော ဖန်သားပြင်ဓာတ်ပုံများကို <xliff:g id="APP">%1$s</xliff:g> အက်ပ်တွင် သိမ်းသည်"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"အလုပ်ပရိုဖိုင်ရှိ <xliff:g id="APP">%1$s</xliff:g> တွင် သိမ်းထားသည်"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ဖိုင်များ"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> က ဤဖန်သားပြင်ဓာတ်ပုံကို တွေ့ရှိသည်။"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> နှင့် အခြားဖွင့်ထားသော အက်ပ်များက ဤဖန်သားပြင်ဓာတ်ပုံကို တွေ့ရှိသည်။"</string>
<string name="screenrecord_name" msgid="2596401223859996572">"ဖန်သားပြင် ရိုက်ကူးမှု"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"စကရင်ရိုက်ကူးမှု အပြီးသတ်နေသည်"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ဖန်သားပြင် ရိုက်ကူးသည့် စက်ရှင်အတွက် ဆက်တိုက်လာနေသော အကြောင်းကြားချက်"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"ထိန်းချုပ်မှုများထည့်ရန် အက်ပ်ရွေးခြင်း"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{ထိန်းချုပ်ခလုတ် # ခု ထည့်ထားသည်။}other{ထိန်းချုပ်ခလုတ် # ခု ထည့်ထားသည်။}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"ဖယ်ရှားထားသည်"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> ထည့်မလား။"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> ထည့်သောအခါ ၎င်းသည် ဤအကန့်တွင် သတ်မှတ်ချက်များနှင့် အကြောင်းအရာကို ထည့်နိုင်သည်။ အက်ပ်အချို့၌ မြင်ရမည့် သတ်မှတ်ချက်များကို ဤနေရာတွင် ရွေးနိုင်သည်။"</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"အကြိုက်ဆုံးတွင် ထည့်ထားသည်"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"အကြိုက်ဆုံးတွင် ထည့်ထားသည်၊ အဆင့် <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"အကြိုက်ဆုံးမှ ဖယ်ရှားထားသည်"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ကို ဖွင့်ပါ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> ၏ <xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%3$s</xliff:g> တွင် ဖွင့်ပါ"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> ကို <xliff:g id="APP_LABEL">%2$s</xliff:g> တွင် ဖွင့်ပါ"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"သင့်အတွက်"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"နောက်ပြန်ရန်"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> တွင်ဖွင့်ရန် အနီးသို့ရွှေ့ပါ"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ဤနေရာတွင် ဖွင့်ရန် <xliff:g id="DEVICENAME">%1$s</xliff:g> အနီးသို့ ရွှေ့ပါ"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"မှားသွားသည်၊ ပြန်စမ်းကြည့်ပါ"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"ထိန်းချုပ်မှုများ ထည့်ရန်"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"ထိန်းချုပ်မှုများ ပြင်ရန်"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"အက်ပ်ထည့်ရန်"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"မီဒီယာအထွက်များ ထည့်ရန်"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"အုပ်စု"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"စက်ပစ္စည်း ၁ ခုကို ရွေးချယ်ထားသည်"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"စပီကာနှင့် ဖန်သားပြင်များ"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"အကြံပြုထားသော စက်ပစ္စည်းများ"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"ပရီမီယံအကောင့် လိုအပ်သည်"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ထုတ်လွှင့်မှုဆောင်ရွက်ပုံ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ထုတ်လွှင့်ခြင်း"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"အနီးရှိတွဲသုံးနိုင်သော ဘလူးတုသ်သုံးစက် အသုံးပြုသူများက သင်ထုတ်လွှင့်နေသော မီဒီယာကို နားဆင်နိုင်သည်"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"သင့်အလုပ်မူဝါဒသည် သင့်အား အလုပ်ပရိုဖိုင်မှသာ ဖုန်းခေါ်ဆိုခွင့် ပြုသည်"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"အလုပ်ပရိုဖိုင်သို့ ပြောင်းရန်"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"ပိတ်ရန်"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"လော့ခ်မျက်နှာပြင် ဆက်တင်များ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 5c1258b..4a129e8 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Nedre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Venstre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Høyre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Jobbrelaterte skjermdumper lagres i <xliff:g id="APP">%1$s</xliff:g>-appen"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Skjermopptaker"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Behandler skjermopptaket"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Vedvarende varsel for et skjermopptak"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Velg en app for å legge til kontroller"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontroll er lagt til.}other{# kontroller er lagt til.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Fjernet"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Vil du legge til <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Når du legger til <xliff:g id="APPNAME">%s</xliff:g>, kan den legge til kontroller og innhold i dette panelet. I noen apper kan du velge hvilke kontroller som vises her."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Favoritt"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favoritt, posisjon <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Fjernet som favoritt"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Åpne <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> av <xliff:g id="ARTIST_NAME">%2$s</xliff:g> fra <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spill av <xliff:g id="SONG_NAME">%1$s</xliff:g> fra <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"For deg"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Angre"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Flytt nærmere for å spille av på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"For å spille av her, gå nærmere <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"En feil oppsto. Prøv på nytt"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Legg til kontroller"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Endre kontroller"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Legg til app"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Legg til utenheter"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Gruppe"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet er valgt"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Høyttalere og skjermer"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Foreslåtte enheter"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Krever en premium-konto"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Slik fungerer kringkasting"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Kringkasting"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Folk i nærheten med kompatible Bluetooth-enheter kan lytte til mediene du kringkaster"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Som følge av jobbreglene dine kan du bare starte telefonanrop fra jobbprofilen."</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Bytt til jobbprofilen"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Lukk"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Innstillinger for låseskjermen"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index e2c8b4b..e6292fc 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"फेदबाट <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"बायाँ किनाराबाट <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"दायाँ किनाराबाट <xliff:g id="PERCENT">%1$d</xliff:g> प्रतिशत"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"कार्य प्रोफाइल प्रयोग गरी लिइएका स्क्रिनसटहरू <xliff:g id="APP">%1$s</xliff:g> एपमा सेभ गरिन्छन्"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"कार्य प्रोफाइलअन्तर्गत <xliff:g id="APP">%1$s</xliff:g> मा सेभ गरियो"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ले यो स्क्रिनसट भेट्टाएको छ।"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> र खुला रहेका अन्य एपहरूले यो स्क्रिनसट भेट्टाएका छन्।"</string>
<string name="screenrecord_name" msgid="2596401223859996572">"स्क्रिन रेकर्डर"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"स्क्रिन रेकर्डिङको प्रक्रिया अघि बढाइँदै"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"कुनै स्क्रिन रेकर्ड गर्ने सत्रका लागि चलिरहेको सूचना"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> खोल्नुहोस्"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> को <xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%3$s</xliff:g> मा बजाउनुहोस्"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> बोलको गीत <xliff:g id="APP_LABEL">%2$s</xliff:g> मा बजाउनुहोस्"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"तपाईंको लागि सिफारिस गरिएका"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"अन्डू गर्नुहोस्"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> मा प्ले गर्न आफ्नो डिभाइस नजिकै लैजानुहोस्"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"यो डिभाइसमा प्ले गर्न <xliff:g id="DEVICENAME">%1$s</xliff:g> को अझ नजिक जानुहोस्"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"स्पिकर तथा डिस्प्लेहरू"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"सिफारिस गरिएका डिभाइसहरू"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"प्रिमियम खाता चाहिन्छ"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"प्रसारण गर्ने सुविधाले कसरी काम गर्छ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"प्रसारण"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"कम्प्याटिबल ब्लुटुथ डिभाइस भएका नजिकैका मान्छेहरू तपाईंले प्रसारण गरिरहनुभएको मिडिया सुन्न सक्छन्"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"तपाईंको कामसम्बन्धी नीतिअनुसार कार्य प्रोफाइलबाट मात्र फोन कल गर्न सकिन्छ"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"कार्य प्रोफाइल प्रयोग गर्नुहोस्"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"बन्द गर्नुहोस्"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"लक स्क्रिनसम्बन्धी सेटिङ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 56d5303..9fcc945 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Ondergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linkergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Rechtergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Werkscreenshots worden opgeslagen in de <xliff:g id="APP">%1$s</xliff:g>-app"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Bestanden"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Schermopname"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Schermopname verwerken"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Doorlopende melding voor een schermopname-sessie"</string>
@@ -852,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> openen"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="SONG_NAME">%1$s</xliff:g> van <xliff:g id="ARTIST_NAME">%2$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> afspelen via <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Voor jou"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Ongedaan maken"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Houd dichter bij <xliff:g id="DEVICENAME">%1$s</xliff:g> om af te spelen"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ga dichter naar <xliff:g id="DEVICENAME">%1$s</xliff:g> toe om hier af te spelen"</string>
@@ -884,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speakers en schermen"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Voorgestelde apparaten"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Je hebt een premium account nodig"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Hoe uitzenden werkt"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Uitzending"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Mensen bij jou in de buurt met geschikte bluetooth-apparaten kunnen luisteren naar de media die je uitzendt"</string>
@@ -1029,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Op basis van je werkbeleid kun je alleen bellen vanuit het werkprofiel"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Overschakelen naar werkprofiel"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Sluiten"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Instellingen vergrendelscherm"</string>
</resources>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 5396ede..15a7ea7 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ନିମ୍ନ ସୀମାରେଖା <xliff:g id="PERCENT">%1$d</xliff:g> ଶତକଡ଼ା"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ବାମ ସୀମାରେଖା <xliff:g id="PERCENT">%1$d</xliff:g> ଶତକଡ଼ା"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ଡାହାଣ ସୀମାରେଖା <xliff:g id="PERCENT">%1$d</xliff:g> ଶତକଡ଼ା"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ୱାର୍କ ସ୍କ୍ରିନସଟଗୁଡ଼ିକୁ <xliff:g id="APP">%1$s</xliff:g> ଆପରେ ସେଭ କରାଯାଇଛି"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ୱାର୍କ ପ୍ରୋଫାଇଲରେ ଥିବା <xliff:g id="APP">%1$s</xliff:g>ରେ ସେଭ କରାଯାଇଛି"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ଫାଇଲଗୁଡ଼ିକ"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ଏହି ସ୍କ୍ରିନସଟକୁ ଚିହ୍ନଟ କରିଛି।"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ଏବଂ ଅନ୍ୟ ଓପନ ଆପ୍ସ ଏହି ସ୍କ୍ରିନସଟକୁ ଚିହ୍ନଟ କରିଛି।"</string>
<string name="screenrecord_name" msgid="2596401223859996572">"ସ୍କ୍ରିନ୍ ରେକର୍ଡର୍"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ସ୍କ୍ରିନ ରେକର୍ଡିଂର ପ୍ରକ୍ରିୟାକରଣ"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ଏକ ସ୍କ୍ରିନ୍ ରେକର୍ଡ୍ ସେସନ୍ ପାଇଁ ଚାଲୁଥିବା ବିଜ୍ଞପ୍ତି"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଯୋଗ କରିବାକୁ ଆପ୍ ବାଛନ୍ତୁ"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{#ଟି ନିୟନ୍ତ୍ରଣ ଯୋଗ କରାଯାଇଛି।}other{#ଟି ନିୟନ୍ତ୍ରଣ ଯୋଗ କରାଯାଇଛି।}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"କାଢ଼ି ଦିଆଯାଇଛି"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g>କୁ ଯୋଗ କରିବେ?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"ଯେତେବେଳେ ଆପଣ <xliff:g id="APPNAME">%s</xliff:g>କୁ ଯୋଗ କରନ୍ତି, ସେତେବେଳେ ଏହି ପେନେଲରେ ଏହା ନିୟନ୍ତ୍ରଣ ଏବଂ ବିଷୟବସ୍ତୁ ଯୋଗ କରିପାରିବ। କିଛି ଆପ୍ସରେ, ଏଠାରେ କେଉଁ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ଦେଖାଯିବ ତାହା ଆପଣ ବାଛିପାରିବେ।"</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"ପସନ୍ଦ କରାଯାଇଛି"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"ପସନ୍ଦ କରାଯାଇଛି, ସ୍ଥିତି <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ନାପସନ୍ଦ କରାଯାଇଛି"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ଖୋଲନ୍ତୁ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g>ରୁ <xliff:g id="ARTIST_NAME">%2$s</xliff:g>ଙ୍କ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g>ରୁ <xliff:g id="SONG_NAME">%1$s</xliff:g> ଚଲାନ୍ତୁ"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"ଆପଣଙ୍କ ପାଇଁ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ପୂର୍ବବତ୍ କରନ୍ତୁ"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>ରେ ପ୍ଲେ କରିବା ପାଇଁ ପାଖକୁ ମୁଭ କରନ୍ତୁ"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ଏଠାରେ ପ୍ଲେ କରିବା ପାଇଁ <xliff:g id="DEVICENAME">%1$s</xliff:g> ପାଖକୁ ମୁଭ କରନ୍ତୁ"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"ତ୍ରୁଟି ହୋଇଛି, ପୁଣି ଚେଷ୍ଟା କର"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ ଯୋଗ କରନ୍ତୁ"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଏଡିଟ କରନ୍ତୁ"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ଆପ ଯୋଗ କରନ୍ତୁ"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"ଆଉଟପୁଟ୍ ଯୋଗ କରନ୍ତୁ"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"ଗୋଷ୍ଠୀ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1ଟି ଡିଭାଇସ୍ ଚୟନ କରାଯାଇଛି"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ସ୍ପିକର ଏବଂ ଡିସପ୍ଲେଗୁଡ଼ିକ"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ପ୍ରସ୍ତାବିତ ଡିଭାଇସଗୁଡ଼ିକ"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"ପ୍ରିମିୟମ ଆକାଉଣ୍ଟ ଆବଶ୍ୟକ"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ବ୍ରଡକାଷ୍ଟିଂ କିପରି କାମ କରେ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ବ୍ରଡକାଷ୍ଟ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ଆପଣଙ୍କ ଆଖପାଖର କମ୍ପାଟିବଲ ବ୍ଲୁଟୁଥ ଡିଭାଇସ ଥିବା ଲୋକମାନେ ଆପଣ ବ୍ରଡକାଷ୍ଟ କରୁଥିବା ମିଡିଆ ଶୁଣିପାରିବେ"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"ଆପଣଙ୍କ ୱାର୍କ ନୀତି ଆପଣଙ୍କୁ କେବଳ ୱାର୍କ ପ୍ରୋଫାଇଲରୁ ଫୋନ କଲ କରିବାକୁ ଅନୁମତି ଦିଏ"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"ୱାର୍କ ପ୍ରୋଫାଇଲକୁ ସ୍ୱିଚ କରନ୍ତୁ"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"ବନ୍ଦ କରନ୍ତୁ"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"ଲକ ସ୍କ୍ରିନ ସେଟିଂସ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 10c89f2..03350a9 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ਹੇਠਾਂ ਦੀ ਸੀਮਾ <xliff:g id="PERCENT">%1$d</xliff:g> ਫ਼ੀਸਦ"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ਖੱਬੇ ਪਾਸੇ ਵਾਲੀ ਸੀਮਾ <xliff:g id="PERCENT">%1$d</xliff:g> ਫ਼ੀਸਦ"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ਸੱਜੇ ਪਾਸੇ ਵਾਲੀ ਸੀਮਾ <xliff:g id="PERCENT">%1$d</xliff:g> ਫ਼ੀਸਦ"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ਕਾਰਜ ਸਕ੍ਰੀਨਸ਼ਾਟਾਂ ਨੂੰ <xliff:g id="APP">%1$s</xliff:g> ਐਪ ਵਿੱਚ ਰੱਖਿਅਤ ਕੀਤਾ ਜਾਂਦਾ ਹੈ"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ਫ਼ਾਈਲਾਂ"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਰ"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡਿੰਗ ਜਾਰੀ ਹੈ"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"ਕਿਸੇ ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ ਸੈਸ਼ਨ ਲਈ ਚੱਲ ਰਹੀ ਸੂਚਨਾ"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਐਪ ਚੁਣੋ"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ।}one{# ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ।}other{# ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕੀਤੇ ਗਏ।}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"ਹਟਾਇਆ ਗਿਆ"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"ਕੀ <xliff:g id="APPNAME">%s</xliff:g> ਸ਼ਾਮਲ ਕਰਨਾ ਹੈ?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"ਜਦੋਂ ਤੁਸੀਂ <xliff:g id="APPNAME">%s</xliff:g> ਸ਼ਾਮਲ ਕਰਦੇ ਹੋ, ਤਾਂ ਇਹ ਇਸ ਪੈਨਲ ਵਿੱਚ ਕੰਟਰੋਲਾਂ ਅਤੇ ਸਮੱਗਰੀ ਨੂੰ ਸ਼ਾਮਲ ਕਰ ਸਕਦੀ ਹੈ। ਕੁਝ ਐਪਾਂ ਲਈ, ਤੁਸੀਂ ਇਹ ਚੁਣ ਸਕਦੇ ਹੋ ਕਿ ਇੱਥੇ ਕਿਹੜੇ ਕੰਟਰੋਲ ਦਿਸਣ।"</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"ਮਨਪਸੰਦ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"ਮਨਪਸੰਦ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ, ਸਥਾਨ <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ਮਨਪਸੰਦ ਵਿੱਚੋਂ ਹਟਾਇਆ ਗਿਆ"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ਖੋਲ੍ਹੋ"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ਤੋਂ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> ਦਾ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> ਤੋਂ <xliff:g id="SONG_NAME">%1$s</xliff:g> ਚਲਾਓ"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"ਤੁਹਾਡੇ ਲਈ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"ਅਣਕੀਤਾ ਕਰੋ"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> \'ਤੇ ਚਲਾਉਣ ਲਈ ਨੇੜੇ ਲਿਜਾਓ"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ਇੱਥੇ ਚਲਾਉਣ ਲਈ, <xliff:g id="DEVICENAME">%1$s</xliff:g> ਦੇ ਨੇੜੇ ਲਿਆਓ"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"ਗੜਬੜ, ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"ਕੰਟਰੋਲਾਂ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ਐਪ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"ਆਊਟਪੁੱਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"ਗਰੁੱਪ"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ਡੀਵਾਈਸ ਨੂੰ ਚੁਣਿਆ ਗਿਆ"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ਸਪੀਕਰ ਅਤੇ ਡਿਸਪਲੇਆਂ"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ਸੁਝਾਏ ਗਏ ਡੀਵਾਈਸ"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"ਪ੍ਰੀਮੀਅਮ ਖਾਤੇ ਦੀ ਲੋੜ ਹੈ"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ਪ੍ਰਸਾਰਨ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ਪ੍ਰਸਾਰਨ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ਅਨੁਰੂਪ ਬਲੂਟੁੱਥ ਡੀਵਾਈਸਾਂ ਨਾਲ ਨਜ਼ਦੀਕੀ ਲੋਕ ਤੁਹਾਡੇ ਵੱਲੋਂ ਪ੍ਰਸਾਰਨ ਕੀਤੇ ਜਾ ਰਹੇ ਮੀਡੀਆ ਨੂੰ ਸੁਣ ਸਕਦੇ ਹਨ"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"ਤੁਹਾਡੀ ਕਾਰਜ ਨੀਤੀ ਤੁਹਾਨੂੰ ਸਿਰਫ਼ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਤੋਂ ਹੀ ਫ਼ੋਨ ਕਾਲਾਂ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਜਾਓ"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"ਬੰਦ ਕਰੋ"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"ਲਾਕ ਸਕ੍ਰੀਨ ਸੈਟਿੰਗਾਂ"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 4abddab..68c540f 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Przycięcie dolnej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Przycięcie lewej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Przycięcie prawej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Służbowe zrzuty ekranu są zapisywane w aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Pliki"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Nagrywanie ekranu"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Przetwarzam nagrywanie ekranu"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Stałe powiadomienie o sesji rejestrowania zawartości ekranu"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Wybierz aplikację, do której chcesz dodać elementy sterujące"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Dodano # element sterujący.}few{Dodano # elementy sterujące.}many{Dodano # elementów sterujących.}other{Dodano # elementu sterującego.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Usunięto"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Dodać aplikację <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Gdy dodasz aplikację <xliff:g id="APPNAME">%s</xliff:g>, będzie ona mogła dodawać elementy sterujące i treści do tego panelu. W niektórych aplikacjach można wybrać elementy sterujące, które się tu pojawią."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Dodano do ulubionych"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Dodano do ulubionych, pozycja <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Usunięto z ulubionych"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otwórz aplikację <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> (<xliff:g id="ARTIST_NAME">%2$s</xliff:g>) w aplikacji <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Odtwórz utwór <xliff:g id="SONG_NAME">%1$s</xliff:g> w aplikacji <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Dla Ciebie"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Cofnij"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Przysuń się bliżej, aby odtwarzać na urządzeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Aby zagrać tutaj, przysuń bliżej urządzenie <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Błąd, spróbuj ponownie"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Dodaj elementy sterujące"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Edytuj elementy sterujące"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Dodaj aplikację"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodaj urządzenia wyjściowe"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupa"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Wybrano 1 urządzenie"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Głośniki i wyświetlacze"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Proponowane urządzenia"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Wymagane konto premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jak działa transmitowanie"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transmisja"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osoby w pobliżu ze zgodnymi urządzeniami Bluetooth mogą słuchać transmitowanych przez Ciebie multimediów"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Zasady obowiązujące w firmie zezwalają na nawiązywanie połączeń telefonicznych tylko w profilu służbowym"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Przełącz na profil służbowy"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Zamknij"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Ustawienia ekranu blokady"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index f1adbfd..accb35c 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Borda inferior em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Borda esquerda em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Borda direita em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Capturas de tela do trabalho são salvas no app <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salva no app <xliff:g id="APP">%1$s</xliff:g> no perfil de trabalho"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"O app <xliff:g id="APPNAME">%1$s</xliff:g> detectou essa captura de tela."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e outros apps abertos detectaram essa captura de tela."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Gravador de tela"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processando gravação de tela"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação contínua para uma sessão de gravação de tela"</string>
@@ -489,7 +491,7 @@
<string name="volume_dialog_ringer_guidance_ring" msgid="9143194270463146858">"Chamadas e notificações farão o smartphone tocar (<xliff:g id="VOLUME_LEVEL">%1$s</xliff:g>)"</string>
<string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador System UI"</string>
<string name="status_bar" msgid="4357390266055077437">"Barra de status"</string>
- <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da IU do sistema"</string>
+ <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da interface do sistema"</string>
<string name="enable_demo_mode" msgid="3180345364745966431">"Ativar modo de demonstração"</string>
<string name="show_demo_mode" msgid="3677956462273059726">"Mostrar modo de demonstração"</string>
<string name="status_bar_ethernet" msgid="5690979758988647484">"Ethernet"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Para você"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime os dispositivos para tocar a mídia neste: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para abrir aqui, aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Alto-falantes e telas"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Requer uma conta premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funciona a transmissão"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transmitir"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"As pessoas próximas a você com dispositivos Bluetooth compatíveis podem ouvir a mídia que você está transmitindo"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Sua política de trabalho só permite fazer ligações pelo perfil de trabalho"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Alternar para o perfil de trabalho"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Fechar"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Configurações de tela de bloqueio"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 4aa89ea..9ee8afc 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Limite inferior de <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite esquerdo de <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite direito de <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"As capturas de ecrã do trabalho são guardadas na app <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Guardada na app <xliff:g id="APP">%1$s</xliff:g> no perfil de trabalho"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ficheiros"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"A app <xliff:g id="APPNAME">%1$s</xliff:g> detetou esta captura de ecrã."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"A app <xliff:g id="APPNAME">%1$s</xliff:g> e outras apps abertas detetaram esta captura de ecrã."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Gravador de ecrã"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"A processar a gravação de ecrã"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação persistente de uma sessão de gravação de ecrã"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Escolha uma app para adicionar controlos"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# controlo adicionado.}many{# controlos adicionados.}other{# controlos adicionados.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Removido"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Adicionar <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Quando adicionar a app <xliff:g id="APPNAME">%s</xliff:g>, esta pode adicionar controlos e conteúdos a este painel. Em algumas apps, pode escolher que controlos são apresentados aqui."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Adicionado aos favoritos"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Adicionados aos favoritos, posição <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Removido dos favoritos"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Reproduzir <xliff:g id="SONG_NAME">%1$s</xliff:g> a partir da app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Para si"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Anular"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime-se para reproduzir no dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para reproduzir aqui, aproxime-se do <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Erro. Tente novamente."</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Adicionar controlos"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Editar controlos"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Adicionar app"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Adicione saídas"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupo"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 dispositivo selecionado"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altifalantes e ecrãs"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Requer uma conta premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funciona a transmissão"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transmissão"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"As pessoas próximas de si com dispositivos Bluetooth compatíveis podem ouvir o conteúdo multimédia que está a transmitir"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"A sua Política de Trabalho só lhe permite fazer chamadas telefónicas a partir do perfil de trabalho"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Mudar para perfil de trabalho"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Fechar"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Definições do ecrã de bloqueio"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index f1adbfd..accb35c 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Borda inferior em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Borda esquerda em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Borda direita em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Capturas de tela do trabalho são salvas no app <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salva no app <xliff:g id="APP">%1$s</xliff:g> no perfil de trabalho"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"O app <xliff:g id="APPNAME">%1$s</xliff:g> detectou essa captura de tela."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e outros apps abertos detectaram essa captura de tela."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Gravador de tela"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processando gravação de tela"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificação contínua para uma sessão de gravação de tela"</string>
@@ -489,7 +491,7 @@
<string name="volume_dialog_ringer_guidance_ring" msgid="9143194270463146858">"Chamadas e notificações farão o smartphone tocar (<xliff:g id="VOLUME_LEVEL">%1$s</xliff:g>)"</string>
<string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador System UI"</string>
<string name="status_bar" msgid="4357390266055077437">"Barra de status"</string>
- <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da IU do sistema"</string>
+ <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da interface do sistema"</string>
<string name="enable_demo_mode" msgid="3180345364745966431">"Ativar modo de demonstração"</string>
<string name="show_demo_mode" msgid="3677956462273059726">"Mostrar modo de demonstração"</string>
<string name="status_bar_ethernet" msgid="5690979758988647484">"Ethernet"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Abrir <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> de <xliff:g id="ARTIST_NAME">%2$s</xliff:g> no app <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Tocar <xliff:g id="SONG_NAME">%1$s</xliff:g> no app <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Para você"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Desfazer"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Aproxime os dispositivos para tocar a mídia neste: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para abrir aqui, aproxime-se do dispositivo <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Alto-falantes e telas"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Requer uma conta premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funciona a transmissão"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transmitir"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"As pessoas próximas a você com dispositivos Bluetooth compatíveis podem ouvir a mídia que você está transmitindo"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Sua política de trabalho só permite fazer ligações pelo perfil de trabalho"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Alternar para o perfil de trabalho"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Fechar"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Configurações de tela de bloqueio"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 894e22e..a144755 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Marginea de jos la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Marginea stângă la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Marginea dreaptă la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Capturile de ecran pentru serviciu sunt salvate în aplicația <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salvată în <xliff:g id="APP">%1$s</xliff:g> în profilul de serviciu"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fișiere"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> a detectat această captură de ecran."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> și alte aplicații deschise au detectat această captură de ecran."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Recorder pentru ecran"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Se procesează înregistrarea"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Notificare în curs pentru o sesiune de înregistrare a ecranului"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Deschide <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Redă <xliff:g id="SONG_NAME">%1$s</xliff:g> de la <xliff:g id="ARTIST_NAME">%2$s</xliff:g> în <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Redă <xliff:g id="SONG_NAME">%1$s</xliff:g> în <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Pentru tine"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Anulează"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Apropie-te pentru a reda pe <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Pentru a reda conținutul aici, apropie-te de <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Difuzoare și afișaje"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispozitive sugerate"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Necesită un cont premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cum funcționează transmisia"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transmite"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Persoanele din apropiere cu dispozitive Bluetooth compatibile pot asculta conținutul pe care îl transmiți"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Politica privind activitatea îți permite să efectuezi apeluri telefonice numai din profilul de serviciu"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Comută la profilul de serviciu"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Închide"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Setările ecranului de blocare"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index e55572c..8ac0b14 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Граница снизу: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Граница слева: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Граница справа: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Скриншоты сохраняются в приложении \"<xliff:g id="APP">%1$s</xliff:g>\" в рабочем профиле"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Скриншот сохранен в рабочем профиле в приложении <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлы"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"Приложение \"<xliff:g id="APPNAME">%1$s</xliff:g>\" обнаружило создание скриншота."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Приложение \"<xliff:g id="APPNAME">%1$s</xliff:g>\" и другие запущенные продукты обнаружили создание скриншота."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Запись видео с экрана"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обработка записи с экрана…"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Текущее уведомление для записи видео с экрана"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Открыть приложение \"<xliff:g id="APP_LABEL">%1$s</xliff:g>\""</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" (исполнитель: <xliff:g id="ARTIST_NAME">%2$s</xliff:g>) из приложения \"<xliff:g id="APP_LABEL">%3$s</xliff:g>\""</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Воспроизвести медиафайл \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" из приложения \"<xliff:g id="APP_LABEL">%2$s</xliff:g>\""</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Для вас"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Отменить"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Чтобы начать трансляцию на устройстве \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\", подойдите к нему ближе."</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Чтобы начать воспроизведение здесь, подойдите ближе к устройству (<xliff:g id="DEVICENAME">%1$s</xliff:g>)."</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Колонки и дисплеи"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Рекомендуемые устройства"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Требуется премиум-аккаунт"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Как работают трансляции"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Трансляция"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Находящиеся рядом с вами люди с совместимыми устройствами Bluetooth могут слушать медиафайлы, которые вы транслируете."</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Согласно правилам вашей организации вы можете совершать телефонные звонки только из рабочего профиля."</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Перейти в рабочий профиль"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Закрыть"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Настройки блокировки экрана"</string>
</resources>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 8ea1695..5e78e73 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"පහළ සීමාව සියයට <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"වම් සීමාව සියයට <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"දකුණු සීමාව සියයට <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"වැඩ තිර රූ <xliff:g id="APP">%1$s</xliff:g> යෙදුම තුළ සුරැකෙයි"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ගොනු"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"තිර රෙකෝඩරය"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"තිර පටිගත කිරීම සකසමින්"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"තිර පටිගත කිරීමේ සැසියක් සඳහා කෙරෙන දැනුම් දීම"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"පාලන එක් කිරීමට යෙදුම තෝරා ගන්න"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# පාලනයක් එක් කර ඇත.}one{පාලන #ක් එක් කර ඇත.}other{පාලන #ක් එක් කර ඇත.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"ඉවත් කළා"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> එක් කරන්න ද?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"ඔබ <xliff:g id="APPNAME">%s</xliff:g> එක් කළ විට, එයට මෙම පැනලයට පාලන සහ අන්තර්ගතය එක් කළ හැක. සමහර යෙදුම්වල, ඔබට මෙහි පෙන්වන්නේ කුමන පාලන ද යන්න තෝරා ගැනීමට හැක."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"ප්රියතම කළා"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"ප්රියතම කළා, තත්ත්ව <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ප්රියතම වෙතින් ඉවත් කළා"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> විවෘත කරන්න"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g>ගේ <xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%3$s</xliff:g> වෙතින් වාදනය කරන්න"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> <xliff:g id="APP_LABEL">%2$s</xliff:g> වෙතින් වාදනය කරන්න"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"ඔබට"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"පසුගමනය කරන්න"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> හි වාදනය කිරීමට වඩාත් ළං වන්න"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"මෙහි වාදනය කිරීම සඳහා, <xliff:g id="DEVICENAME">%1$s</xliff:g> වෙත සමීප වන්න"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"දෝෂයකි, නැවත උත්සාහ කරන්න"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"පාලන එක් කරන්න"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"පාලන සංස්කරණය කරන්න"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"යෙදුම එක් කරන්න"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"ප්රතිදාන එක් කරන්න"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"සමූහය"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"උපාංග 1ක් තෝරන ලදී"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ස්පීකර් සහ සංදර්ශක"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"යෝජිත උපාංග"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"පාරිතෝෂික ගිණුමක් අවශ්යයි"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"විකාශනය ක්රියා කරන ආකාරය"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"විකාශනය"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ගැළපෙන බ්ලූටූත් උපාංග සහිත ඔබ අවට සිටින පුද්ගලයින්ට ඔබ විකාශනය කරන මාධ්යයට සවන් දිය හැකිය"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"ඔබේ වැඩ ප්රතිපත්තිය ඔබට කාර්යාල පැතිකඩෙන් පමණක් දුරකථන ඇමතුම් ලබා ගැනීමට ඉඩ සලසයි"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"කාර්යාල පැතිකඩ වෙත මාරු වන්න"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"වසන්න"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"අගුළු තිර සැකසීම්"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index e321fcb..884848b 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"<xliff:g id="PERCENT">%1$d</xliff:g> %% dolnej hranice"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> %% ľavej hranice"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> %% pravej hranice"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Pracovné snímky obrazovky sú uložené v aplikácii <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Rekordér obrazovky"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Spracúva sa záznam obrazovky"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Zobrazuje sa upozornenie týkajúce sa relácie záznamu obrazovky"</string>
@@ -852,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Otvoriť <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> od interpreta <xliff:g id="ARTIST_NAME">%2$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Prehrať skladbu <xliff:g id="SONG_NAME">%1$s</xliff:g> z aplikácie <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Pre vás"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Späť"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Ak chcete prehrávať v zariadení <xliff:g id="DEVICENAME">%1$s</xliff:g>, priblížte sa k nemu"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ak tu chcete prehrávať obsah, priblížte zariadenie k zariadeniu <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Reproduktory a obrazovky"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Navrhované zariadenia"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Vyžaduje prémiový účet"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Ako vysielanie funguje"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Vysielanie"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Ľudia v okolí s kompatibilnými zariadeniami s rozhraním Bluetooth si môžu vypočuť médiá, ktoré vysielate"</string>
@@ -1029,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Pracovné pravidlá vám umožňujú telefonovať iba v pracovnom profile"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Prepnúť na pracovný profil"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Zavrieť"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Nastavenia uzamknutej obrazovky"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index a5989bd..6b04eab 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Meja spodaj <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Meja levo <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Meja desno <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Posnetki zaslona v delovnem profilu so shranjeni v aplikaciji <xliff:g id="APP">%1$s</xliff:g>."</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Shranjeno v aplikaciji <xliff:g id="APP">%1$s</xliff:g> v delovnem profilu"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Datoteke"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> je zaznala ta posnetek zaslona."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> in druge odprte aplikacije so zaznale ta posnetek zaslona."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Snemalnik zaslona"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obdelava videoposnetka zaslona"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Nenehno obveščanje o seji snemanja zaslona"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Izberite aplikacijo za dodajanje kontrolnikov"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrolnik je dodan.}one{# kontrolnik je dodan.}two{# kontrolnika sta dodana.}few{# kontrolniki so dodani.}other{# kontrolnikov je dodanih.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Odstranjeno"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Želite dodati aplikacijo <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Ko dodate aplikacijo <xliff:g id="APPNAME">%s</xliff:g>, lahko ta doda kontrolnike in vsebino v to podokno. V nekaterih aplikacijah lahko izberete, kateri kontrolniki so prikazani tukaj."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Dodano med priljubljene"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Dodano med priljubljene, položaj <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Odstranjeno iz priljubljenih"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Odpri aplikacijo <xliff:g id="APP_LABEL">%1$s</xliff:g>."</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> izvajalca <xliff:g id="ARTIST_NAME">%2$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%3$s</xliff:g>."</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Predvajaj skladbo <xliff:g id="SONG_NAME">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_LABEL">%2$s</xliff:g>."</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Za vas"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Razveljavi"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Za predvajanje v napravi <xliff:g id="DEVICENAME">%1$s</xliff:g> bolj približajte telefon"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Če želite predvajati tukaj, se približajte napravi <xliff:g id="DEVICENAME">%1$s</xliff:g>."</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Napaka, poskusite znova"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Dodajte kontrolnike"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Uredite kontrolnike"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Dodaj aplikacijo"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Dodajanje izhodov"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Skupina"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Izbrana je ena naprava"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Zvočniki in zasloni"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Predlagane naprave"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Obvezen je plačljivi račun"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kako deluje oddajanje"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Oddajanje"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Osebe v bližini z združljivo napravo Bluetooth lahko poslušajo predstavnost, ki jo oddajate."</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Službeni pravilnik dovoljuje opravljanje telefonskih klicev le iz delovnega profila."</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Preklopi na delovni profil"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Zapri"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Nastavitve zaklepanja zaslona"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index a6db996..b0fdbe2 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Kufiri i poshtëm <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kufiri i majtë <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Kufiri i djathtë <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Pamjet e ekranit të punës janë ruajtur në aplikacionin \"<xliff:g id="APP">%1$s</xliff:g>\""</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Ruajtur në <xliff:g id="APP">%1$s</xliff:g> në profilin e punës"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Skedarë"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> zbuloi këtë pamje ekrani."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> dhe aplikacionet e tjera të hapura zbuluan këtë pamje ekrani."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Regjistruesi i ekranit"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Regjistrimi i ekranit po përpunohet"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Njoftim i vazhdueshëm për një seancë regjistrimi të ekranit"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Hap <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="ARTIST_NAME">%2$s</xliff:g> nga <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Luaj <xliff:g id="SONG_NAME">%1$s</xliff:g> nga <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Për ty"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Zhbëj"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Afrohu për të luajtur në <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Për ta luajtur këtu, afrohu më shumë te <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altoparlantët dhe ekranet"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Pajisjet e sugjeruara"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Kërkon llogari premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Si funksionon transmetimi"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Transmetimi"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personat në afërsi me ty me pajisje të përputhshme me Bluetooth mund të dëgjojnë median që ti po transmeton"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Politika jote e punës të lejon të bësh telefonata vetëm nga profili i punës"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Kalo te profili i punës"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Mbyll"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Cilësimet e ekranit të kyçjes"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index a3ed992..35de274 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Доња ивица <xliff:g id="PERCENT">%1$d</xliff:g> посто"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Лева ивица <xliff:g id="PERCENT">%1$d</xliff:g> посто"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Десна ивица <xliff:g id="PERCENT">%1$d</xliff:g> посто"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Снимци екрана за посао се чувају у апликацији <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Сачувано је у апликацији <xliff:g id="APP">%1$s</xliff:g> на пословном профилу"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Фајлови"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"Апликација <xliff:g id="APPNAME">%1$s</xliff:g> је открила овај снимак екрана."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> и друге отворене апликације су откриле овај снимак екрана."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Снимач екрана"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обрађујемо видео снимка екрана"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Обавештење о сесији снимања екрана је активно"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Отворите <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> извођача <xliff:g id="ARTIST_NAME">%2$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Пустите <xliff:g id="SONG_NAME">%1$s</xliff:g> из апликације <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"За вас"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Опозови"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Приближите да бисте пуштали музику на: <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Да бисте пуштали садржај овде, приближите уређају <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Звучници и екрани"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Предложени уређаји"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Захтева премијум налог"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Како функционише емитовање"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Емитовање"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Људи у близини са компатибилним Bluetooth уређајима могу да слушају медијски садржај који емитујете"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Смернице за посао вам омогућавају да телефонирате само са пословног профила"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Пређи на пословни профил"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Затвори"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Подешавања закључаног екрана"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 41fe502..356c674 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Nedre gräns: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vänster gräns: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Höger gräns: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Jobbskärmbilder sparas i <xliff:g id="APP">%1$s</xliff:g>-appen"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Sparad i <xliff:g id="APP">%1$s</xliff:g> i jobbprofilen"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> identifierade skärmbilden."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> och andra öppna appar identifierade skärmbilden."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Skärminspelare"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Behandlar skärminspelning"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Avisering om att skärminspelning pågår"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Välj en app om du vill lägga till snabbkontroller"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontroll har lagts till.}other{# kontroller har lagts till.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Har tagits bort"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Vill du lägga till <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"När du lägger till <xliff:g id="APPNAME">%s</xliff:g> kan den lägga till kontroller och innehåll i den här panelen. I vissa appar kan du styra vilka kontroller som visas här."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Har lagts till som favorit"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Har lagts till som favorit, plats <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Har tagits bort från favoriter"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Öppna <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> med <xliff:g id="ARTIST_NAME">%2$s</xliff:g> från <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Spela upp <xliff:g id="SONG_NAME">%1$s</xliff:g> från <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"För dig"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Ångra"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Flytta närmare för att spela upp på <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Kom närmare <xliff:g id="DEVICENAME">%1$s</xliff:g> om du vill spela upp här"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Fel, försök igen"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Lägg till snabbkontroller"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Redigera snabbkontroller"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Lägg till app"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Lägg till utgångar"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grupp"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 enhet har valts"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Högtalare och skärmar"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Förslag på enheter"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Premiumkonto krävs"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Så fungerar utsändning"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Utsändning"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Personer i närheten med kompatibla Bluetooth-enheter kan lyssna på medieinnehåll som du sänder ut"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Jobbprincipen tillåter endast att du ringer telefonsamtal från jobbprofilen"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Byt till jobbprofilen"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Stäng"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Inställningar för låsskärm"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index a126e7e..abaad50 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Mpaka wa sehemu ya chini wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Mpaka wa sehemu ya kushoto wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Mpaka wa sehemu ya kulia wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Picha ya skrini ya kazi huhifadhiwa kwenye programu ya <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Faili"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Kinasa Skrini"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Inachakata rekodi ya skrini"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Arifa inayoendelea ya kipindi cha kurekodi skrini"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Chagua programu ili uweke vidhibiti"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Umeweka kidhibiti #.}other{Umeweka vidhibiti #.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Kimeondolewa"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Ungependa kuweka <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Unapoweka <xliff:g id="APPNAME">%s</xliff:g>, inaweza kuweka vidhibiti na maudhui kwenye kidirisha hiki. Katika baadhi ya programu, unaweza kuchagua ni vidhibiti vipi vionekane hapa."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Kimewekwa kwenye vipendwa"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Kimewekwa kwenye vipendwa, nafasi ya <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Kimeondolewa kwenye vipendwa"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Fungua <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> ulioimbwa na <xliff:g id="ARTIST_NAME">%2$s</xliff:g> katika <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Cheza <xliff:g id="SONG_NAME">%1$s</xliff:g> katika <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Kwa Ajili Yako"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Tendua"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Sogeza karibu ili ucheze kwenye <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ili ucheze maudhui kwenye kifaa hiki, sogeza karibu na <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Hitilafu, jaribu tena"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Weka vidhibiti"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Badilisha vidhibiti"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Weka programu"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Weka vifaa vya kutoa sauti"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Kikundi"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Umechagua kifaa 1"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Spika na Skrini"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Vifaa Vilivyopendekezwa"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Inahitaji akaunti ya kulipia"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jinsi utangazaji unavyofanya kazi"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Tangaza"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Watu walio karibu nawe wenye vifaa oanifu vya Bluetooth wanaweza kusikiliza maudhui unayoyatangaza"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Sera ya mahali pako pa kazi inakuruhusu upige simu kutoka kwenye wasifu wa kazini pekee"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Tumia wasifu wa kazini"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Funga"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Mipangilio ya skrini iliyofungwa"</string>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 5d78e4e..2a27b47 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -45,8 +45,6 @@
<item name="controls_task_view_width_percentage" translatable="false" format="float" type="dimen">0.45</item>
<dimen name="controls_task_view_right_margin">8dp</dimen>
- <dimen name="status_bar_header_height_keyguard">42dp</dimen>
-
<dimen name="lockscreen_shade_max_over_scroll_amount">32dp</dimen>
<dimen name="status_view_margin_horizontal">8dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index db7fb48..45b137a 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -16,8 +16,9 @@
*/
-->
<resources>
- <!-- Height of the status bar header bar when on Keyguard -->
- <dimen name="status_bar_header_height_keyguard">60dp</dimen>
+ <!-- Height of the status bar header bar when on Keyguard.
+ On large screens should be the same as the regular status bar. -->
+ <dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen>
<!-- Size of user icon + frame in the qs user picker (incl. frame) -->
<dimen name="qs_framed_avatar_size">60dp</dimen>
@@ -51,9 +52,6 @@
<!-- Text size for user name in user switcher -->
<dimen name="kg_user_switcher_text_size">18sp</dimen>
- <dimen name="controls_header_bottom_margin">12dp</dimen>
- <dimen name="controls_top_margin">24dp</dimen>
-
<dimen name="global_actions_grid_item_layout_height">80dp</dimen>
<dimen name="qs_brightness_margin_bottom">16dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 122806a..9ed9360 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -17,14 +17,11 @@
*/
-->
<resources>
- <dimen name="controls_padding_horizontal">205dp</dimen>
<dimen name="split_shade_notifications_scrim_margin_bottom">24dp</dimen>
<dimen name="notification_panel_margin_bottom">64dp</dimen>
<dimen name="keyguard_split_shade_top_margin">72dp</dimen>
- <dimen name="status_bar_header_height_keyguard">56dp</dimen>
-
<dimen name="status_view_margin_horizontal">24dp</dimen>
<dimen name="qs_media_session_height_expanded">184dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index 927059a..8f59df6 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -19,7 +19,7 @@
<!-- gap on either side of status bar notification icons -->
<dimen name="status_bar_icon_padding">1dp</dimen>
- <dimen name="controls_padding_horizontal">75dp</dimen>
+ <dimen name="controls_padding_horizontal">40dp</dimen>
<dimen name="large_screen_shade_header_height">56dp</dimen>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index a5528a1..e3dcccb 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"கீழ் எல்லை <xliff:g id="PERCENT">%1$d</xliff:g> சதவீதம்"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"இடது எல்லை <xliff:g id="PERCENT">%1$d</xliff:g> சதவீதம்"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"வலது எல்லை <xliff:g id="PERCENT">%1$d</xliff:g> சதவீதம்"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"பணிக் கணக்கு ஸ்கிரீன்ஷாட்டுகள் <xliff:g id="APP">%1$s</xliff:g> ஆப்ஸில் சேமிக்கப்படும்"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"ஸ்கிரீன் ரெக்கார்டர்"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"ஸ்க்ரீன் ரெக்கார்டிங் செயலாக்கப்படுகிறது"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"திரை ரெக்கார்டிங் அமர்விற்கான தொடர் அறிவிப்பு"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"கட்டுப்பாடுகளைச் சேர்க்க வேண்டிய ஆப்ஸைத் தேர்ந்தெடுங்கள்"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# கட்டுப்பாடு சேர்க்கப்பட்டது.}other{# கட்டுப்பாடுகள் சேர்க்கப்பட்டன.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"அகற்றப்பட்டது"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> ஆப்ஸைச் சேர்க்கவா?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"இந்தப் பேனலில் <xliff:g id="APPNAME">%s</xliff:g> ஆப்ஸைச் சேர்க்கும்போது கட்டுப்பாடுகளையும் உள்ளடக்கத்தையும் அது சேர்க்கலாம். இருப்பினும், சில ஆப்ஸில் எந்தெந்தக் கட்டுப்பாடுகள் இங்கே காட்டப்பட வேண்டும் என்பதை நீங்களே தேர்வுசெய்யலாம்."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"பிடித்தவற்றில் சேர்க்கப்பட்டது"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"பிடித்தவற்றில் சேர்க்கப்பட்டது, நிலை <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"பிடித்தவற்றிலிருந்து நீக்கப்பட்டது"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ஆப்ஸைத் திறங்கள்"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="ARTIST_NAME">%2$s</xliff:g> இன் <xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%3$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="SONG_NAME">%1$s</xliff:g> பாடலை <xliff:g id="APP_LABEL">%2$s</xliff:g> ஆப்ஸில் பிளேசெய்"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"உங்களுக்கானவை"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"செயல்தவிர்"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> சாதனத்தில் இயக்க உங்கள் சாதனத்தை அருகில் எடுத்துச் செல்லுங்கள்"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"இங்கு பிளே செய்ய உங்கள் சாதனத்தை <xliff:g id="DEVICENAME">%1$s</xliff:g>அருகில் நகர்த்துங்கள்"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"பிழை, மீண்டும் முயலவும்"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"கட்டுப்பாடுகளைச் சேர்த்தல்"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"கட்டுப்பாடுகளை மாற்றுதல்"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"ஆப்ஸைச் சேர்"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"அவுட்புட்களைச் சேர்த்தல்"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"குழு"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 சாதனம் தேர்ந்தெடுக்கப்பட்டுள்ளது"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ஸ்பீக்கர்கள் & டிஸ்ப்ளேக்கள்"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"பரிந்துரைக்கப்படும் சாதனங்கள்"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"பிரீமியம் கணக்கு தேவை"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"பிராட்காஸ்ட் எவ்வாறு செயல்படுகிறது?"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"பிராட்காஸ்ட்"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"நீங்கள் பிராட்காஸ்ட் செய்யும் மீடியாவை அருகிலுள்ளவர்கள் இணக்கமான புளூடூத் சாதனங்கள் மூலம் கேட்கலாம்"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"உங்கள் பணிக் கொள்கையின்படி நீங்கள் பணிக் கணக்கில் இருந்து மட்டுமே ஃபோன் அழைப்புகளைச் செய்ய முடியும்"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"பணிக் கணக்கிற்கு மாறு"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"மூடுக"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"பூட்டுத் திரை அமைப்புகள்"</string>
</resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 1b82e39..d50b1cf 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"దిగువ సరిహద్దు <xliff:g id="PERCENT">%1$d</xliff:g> శాతం"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ఎడమ వైపు సరిహద్దు <xliff:g id="PERCENT">%1$d</xliff:g> శాతం"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"కుడి వైపు సరిహద్దు <xliff:g id="PERCENT">%1$d</xliff:g> శాతం"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"వర్క్ స్క్రీన్షాట్లు <xliff:g id="APP">%1$s</xliff:g> యాప్లో సేవ్ చేయబడతాయి"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"వర్క్ ప్రొఫైల్లోని <xliff:g id="APP">%1$s</xliff:g>లో సేవ్ చేయబడింది"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ఫైల్స్"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g>, ఈ స్క్రీన్షాట్ను గుర్తించింది."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g>, ఇతర ఓపెన్ యాప్లు ఈ స్క్రీన్షాట్ను గుర్తించాయి."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"స్క్రీన్ రికార్డర్"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"స్క్రీన్ రికార్డింగ్ అవుతోంది"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"స్క్రీన్ రికార్డ్ సెషన్ కోసం ఆన్గోయింగ్ నోటిఫికేషన్"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"కంట్రోల్స్ను యాడ్ చేయడానికి యాప్ను ఎంచుకోండి"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# కంట్రోల్ జోడించబడింది.}other{# కంట్రోల్స్ జోడించబడ్డాయి.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"తీసివేయబడింది"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g>ను జోడించాలా?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"మీరు <xliff:g id="APPNAME">%s</xliff:g>ను జోడించినప్పుడు, ఇది ఈ ప్యానెల్కు కంట్రోల్స్ని, కంటెంట్ను జోడించగలదు. కొన్ని యాప్లలో, ఇక్కడ ఏయే కంట్రోల్స్ కనిపించాలో మీరు ఎంచుకోవచ్చు."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"ఇష్టమైనదిగా గుర్తు పెట్టబడింది"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"<xliff:g id="NUMBER">%d</xliff:g>వ స్థానంలో ఇష్టమైనదిగా గుర్తు పెట్టబడింది"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"ఇష్టమైనదిగా పెట్టిన గుర్తు తీసివేయబడింది"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g>ను తెరవండి"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> నుండి <xliff:g id="ARTIST_NAME">%2$s</xliff:g> పాడిన <xliff:g id="SONG_NAME">%1$s</xliff:g>ను ప్లే చేయండి"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> నుండి <xliff:g id="SONG_NAME">%1$s</xliff:g>ను ప్లే చేయండి"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"మీ కోసం"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"చర్య రద్దు"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g>లో ప్లే చేయడానికి దగ్గరగా వెళ్లండి"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ఇక్కడ ఆడటానికి, <xliff:g id="DEVICENAME">%1$s</xliff:g>కు దగ్గరగా వెళ్లండి"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"ఎర్రర్, మళ్లీ ప్రయత్నించండి"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"కంట్రోల్స్ను జోడించండి"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"కంట్రోల్స్ను ఎడిట్ చేయండి"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"యాప్ను జోడించండి"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"అవుట్పుట్లను జోడించండి"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"గ్రూప్"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 పరికరం ఎంచుకోబడింది"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"స్పీకర్లు & డిస్ప్లేలు"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"సూచించబడిన పరికరాలు"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"ప్రీమియం ఖాతా అవసరం"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ప్రసారం కావడం అనేది ఎలా పని చేస్తుంది"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ప్రసారం"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"మీకు సమీపంలో ఉన్న వ్యక్తులు అనుకూలత ఉన్న బ్లూటూత్ పరికరాలతో మీరు ప్రసారం చేస్తున్న మీడియాను వినగలరు"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"మీ వర్క్ పాలసీ, మిమ్మల్ని వర్క్ ప్రొఫైల్ నుండి మాత్రమే ఫోన్ కాల్స్ చేయడానికి అనుమతిస్తుంది"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"వర్క్ ప్రొఫైల్కు మారండి"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"మూసివేయండి"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"లాక్ స్క్రీన్ సెట్టింగ్లు"</string>
</resources>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 28bb388..930123f 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ขอบเขตด้านล่าง <xliff:g id="PERCENT">%1$d</xliff:g> เปอร์เซ็นต์"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ขอบเขตด้านซ้าย <xliff:g id="PERCENT">%1$d</xliff:g> เปอร์เซ็นต์"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ขอบเขตด้านขวา <xliff:g id="PERCENT">%1$d</xliff:g> เปอร์เซ็นต์"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"ภาพหน้าจองานจะบันทึกอยู่ในแอป <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"บันทึกไว้ที่ <xliff:g id="APP">%1$s</xliff:g> ในโปรไฟล์งาน"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ไฟล์"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ตรวจพบภาพหน้าจอนี้"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> และแอปอื่นๆ ที่เปิดอยู่ตรวจพบภาพหน้าจอนี้"</string>
<string name="screenrecord_name" msgid="2596401223859996572">"โปรแกรมบันทึกหน้าจอ"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"กำลังประมวลผลการอัดหน้าจอ"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"การแจ้งเตือนต่อเนื่องสำหรับเซสชันการบันทึกหน้าจอ"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"เปิด <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> ของ <xliff:g id="ARTIST_NAME">%2$s</xliff:g> จาก <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"เปิดเพลง <xliff:g id="SONG_NAME">%1$s</xliff:g> จาก <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"สำหรับคุณ"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"เลิกทำ"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"ขยับไปใกล้มากขึ้นเพื่อเล่นใน <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"ขยับไปใกล้ <xliff:g id="DEVICENAME">%1$s</xliff:g> มากขึ้นเพื่อเล่นที่นี่"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ลำโพงและจอแสดงผล"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"อุปกรณ์ที่แนะนำ"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"ต้องใช้บัญชี Premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"วิธีการทำงานของการออกอากาศ"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"ประกาศ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"ผู้ที่อยู่ใกล้คุณและมีอุปกรณ์บลูทูธที่รองรับสามารถรับฟังสื่อที่คุณกำลังออกอากาศได้"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"นโยบายการทำงานอนุญาตให้คุณโทรออกได้จากโปรไฟล์งานเท่านั้น"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"สลับไปใช้โปรไฟล์งาน"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"ปิด"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"การตั้งค่าหน้าจอล็อก"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 7387289..45697d9 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"<xliff:g id="PERCENT">%1$d</xliff:g> (na) porsyento sa hangganan sa ibaba"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> (na) porsyento sa hangganan sa kaliwa"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> (na) porsyento sa hangganan sa kanan"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Naka-save ang mga screenshot sa trabaho sa <xliff:g id="APP">%1$s</xliff:g> app"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Na-save sa <xliff:g id="APP">%1$s</xliff:g> sa profile sa trabaho"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Mga File"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"Na-detect ng <xliff:g id="APPNAME">%1$s</xliff:g> ang screenshot. na ito"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Na-detect ng <xliff:g id="APPNAME">%1$s</xliff:g> at ng iba pang bukas na app ang screenshot na ito."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Recorder ng Screen"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Pinoproseso screen recording"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Kasalukuyang notification para sa session ng pag-record ng screen"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Buksan ang <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> ni/ng <xliff:g id="ARTIST_NAME">%2$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"I-play ang <xliff:g id="SONG_NAME">%1$s</xliff:g> mula sa <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Para sa Iyo"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"I-undo"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Lumapit pa para mag-play sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Para mag-play dito, lumapit sa <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Mga Speaker at Display"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Mga Iminumungkahing Device"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Nangangailangan ng premium account"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Paano gumagana ang pag-broadcast"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Makakapakinig ang mga taong malapit sa iyo na may mga compatible na Bluetooth device sa media na bino-broadcast mo"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Pinapayagan ka ng iyong patakaran sa trabaho na tumawag lang mula sa profile sa trabaho"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Lumipat sa profile sa trabaho"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Isara"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Mga setting ng lock screen"</string>
</resources>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 7c5552d..a145f7b 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Alt sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sol sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sağ sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"İş profilindeki ekran görüntüleri <xliff:g id="APP">%1$s</xliff:g> uygulamasına kaydedilir"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Dosyalar"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Ekran Kaydedicisi"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekran kaydı işleniyor"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekran kaydı oturumu için devam eden bildirim"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Denetim eklemek için uygulama seçin"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# kontrol eklendi.}other{# kontrol eklendi.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Kaldırıldı"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> eklensin mi?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> uygulamasını eklerseniz bu panele kontrol ve içerik ekleyebilir. Bazı uygulamalarda, burada hangi kontrollerin görüneceğini seçebilirsiniz."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Favoriler listesine eklendi"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favorilere eklendi, konum: <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Favorilerden kaldırıldı"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> uygulamasını aç"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> uygulamasından <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> uygulamasından <xliff:g id="SONG_NAME">%1$s</xliff:g> şarkısını çal"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Sizin İçin"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Geri al"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> cihazında oynatmak için yaklaşın"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Burada oynatmak için <xliff:g id="DEVICENAME">%1$s</xliff:g> cihazına yaklaşın"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Hata, yeniden deneyin"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Denetim ekle"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Denetimleri düzenle"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Uygulama ekle"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Çıkışlar ekleyin"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 cihaz seçildi"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hoparlörler ve Ekranlar"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Önerilen Cihazlar"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Premium hesap gerekiyor"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Yayınlamanın işleyiş şekli"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Anons"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Yakınınızda ve uyumlu Bluetooth cihazları olan kişiler yayınladığınız medya içeriğini dinleyebilir"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"İşletme politikanız yalnızca iş profilinden telefon araması yapmanıza izin veriyor"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"İş profiline geç"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Kapat"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Kilit ekranı ayarları"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 0a93ec6..5cb17d1 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Знизу на <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Зліва на <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Справа на <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Робочі знімки екрана зберігаються в додатку <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файли"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Запис відео з екрана"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обробка записування екрана"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Сповіщення про сеанс запису екрана"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Виберіть, для якого додатка налаштувати елементи керування"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Додано # елемент керування.}one{Додано # елемент керування.}few{Додано # елементи керування.}many{Додано # елементів керування.}other{Додано # елемента керування.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Вилучено"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Долучити додаток <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Якщо ви долучите додаток <xliff:g id="APPNAME">%s</xliff:g>, він зможе розміщувати елементи керування й контент на цій панелі. У деяких додатках можна вибрати, які елементи керування тут відображатимуться."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Додано у вибране"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Додано у вибране, позиція <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Видалено з вибраного"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Відкрити додаток <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\", яку виконує <xliff:g id="ARTIST_NAME">%2$s</xliff:g>, у додатку <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Увімкнути пісню \"<xliff:g id="SONG_NAME">%1$s</xliff:g>\" у додатку <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Для вас"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Відмінити"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Щоб відтворити контент на пристрої <xliff:g id="DEVICENAME">%1$s</xliff:g>, наблизьтеся до нього"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Щоб відтворити на цьому пристрої, перемістіть його ближче до пристрою \"<xliff:g id="DEVICENAME">%1$s</xliff:g>\""</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Помилка. Спробуйте знову"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Додати елементи керування"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Змінити елементи керування"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Долучити додаток"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Додати пристрої виводу"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Група"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Вибрано 1 пристрій"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Колонки й екрани"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Пропоновані пристрої"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Потрібен платний обліковий запис"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Як працює трансляція"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Трансляція"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Люди поблизу, які мають сумісні пристрої з Bluetooth, можуть слухати медіаконтент, який ви транслюєте."</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Відповідно до правил організації ви можете телефонувати лише з робочого профілю"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Перейти в робочий профіль"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Закрити"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Параметри заблокованого екрана"</string>
</resources>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index ad845f0..48ef8c4 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"نیچے کا احاطہ <xliff:g id="PERCENT">%1$d</xliff:g> فیصد"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"بایاں احاطہ <xliff:g id="PERCENT">%1$d</xliff:g> فیصد"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"دایاں احاطہ <xliff:g id="PERCENT">%1$d</xliff:g> فیصد"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"دفتری اسکرین شاٹس کو <xliff:g id="APP">%1$s</xliff:g> ایپ میں محفوظ کیا جاتا ہے"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"فائلز"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"اسکرین ریکارڈر"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"سکرین ریکارڈنگ پروسیس ہورہی ہے"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"اسکرین ریکارڈ سیشن کیلئے جاری اطلاع"</string>
@@ -852,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> کھولیں"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> سے <xliff:g id="ARTIST_NAME">%2$s</xliff:g> کا <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> سے <xliff:g id="SONG_NAME">%1$s</xliff:g> چلائیں"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"آپ کیلئے"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"کالعدم کریں"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> پر چلانے کے لیے قریب کریں"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"یہاں چلانے کے لیے، <xliff:g id="DEVICENAME">%1$s</xliff:g> کے زیادہ جائیں"</string>
@@ -884,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"اسپیکرز اور ڈسپلیز"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"تجویز کردہ آلات"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"پریمئیم اکاؤنٹ درکار ہے"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"براڈکاسٹنگ کیسے کام کرتا ہے"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"براڈکاسٹ"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"موافق بلوٹوتھ آلات کے ساتھ آپ کے قریبی لوگ آپ کے نشر کردہ میڈیا کو سن سکتے ہیں"</string>
@@ -1029,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"آپ کے کام سے متعلق پالیسی آپ کو صرف دفتری پروفائل سے فون کالز کرنے کی اجازت دیتی ہے"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"دفتری پروفائل پر سوئچ کریں"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"بند کریں"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"مقفل اسکرین کی ترتیبات"</string>
</resources>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 4927ec2..573b33a 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Quyi chegara <xliff:g id="PERCENT">%1$d</xliff:g> foiz"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Chap chegara <xliff:g id="PERCENT">%1$d</xliff:g> foiz"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Oʻng chegara <xliff:g id="PERCENT">%1$d</xliff:g> foiz"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Ish skrinshotlari <xliff:g id="APP">%1$s</xliff:g> ilovasida saqlanadi"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fayllar"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"Ekrandan yozib olish"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Ekran yozib olinmoqda"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ekrandan yozib olish seansi uchun joriy bildirishnoma"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Boshqaruv elementlarini kiritish uchun ilovani tanlang"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# ta boshqaruv elementi kiritildi.}other{# ta boshqaruv elementi kiritildi.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Olib tashlandi"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"<xliff:g id="APPNAME">%s</xliff:g> qoʻshilsinmi?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"<xliff:g id="APPNAME">%s</xliff:g> bu panelga boshqaruv elementlari va kontent qoʻshishi mumkin. Ayrim ilovalarda bu yerda qaysi elementlar chiqishini tanlashingiz mumkin."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Saralanganlarga kiritilgan"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Saralanganlarga kiritilgan, <xliff:g id="NUMBER">%d</xliff:g>-joy"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Saralanganlardan olib tashlangan"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ilovasini ochish"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"<xliff:g id="APP_LABEL">%3$s</xliff:g> ilovasida ijro etish: <xliff:g id="SONG_NAME">%1$s</xliff:g> – <xliff:g id="ARTIST_NAME">%2$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"<xliff:g id="APP_LABEL">%2$s</xliff:g> ilovasida ijro etilmoqda: <xliff:g id="SONG_NAME">%1$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Siz uchun"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Qaytarish"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"<xliff:g id="DEVICENAME">%1$s</xliff:g> qurilmasida ijro qilish uchun unga yaqinlashing"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Shu yerda ijro qilish uchun <xliff:g id="DEVICENAME">%1$s</xliff:g>ga yaqinroq suring"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Xato, qayta urining"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Element kiritish"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Elementlarni tahrirlash"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Ilova kiritish"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Chiquvchi qurilmani kiritish"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Guruh"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 ta qurilma tanlandi"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Karnaylar va displeylar"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Taklif qilingan qurilmalar"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Premium hisob talab etiladi"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Translatsiya qanday ishlaydi"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Translatsiya"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Atrofingizdagi mos Bluetooth qurilmasiga ega foydalanuvchilar siz translatsiya qilayotgan mediani tinglay olishadi"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Ishga oid siyosatingiz faqat ish profilidan telefon chaqiruvlarini amalga oshirish imkonini beradi"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Ish profiliga almashish"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Yopish"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Qulflangan ekran sozlamalari"</string>
</resources>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 016c834..2d5f598 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Cạnh dưới cùng <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Cạnh trái <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Cạnh phải <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Ảnh chụp màn hình công việc được lưu trong ứng dụng <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Đã lưu vào <xliff:g id="APP">%1$s</xliff:g> trong hồ sơ công việc"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> đã phát hiện thấy ảnh chụp màn hình này."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> và các ứng dụng đang mở khác đã phát hiện thấy ảnh chụp màn hình này."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Trình ghi màn hình"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Đang xử lý video ghi màn hình"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Thông báo đang diễn ra về phiên ghi màn hình"</string>
@@ -800,10 +802,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Chọn ứng dụng để thêm các tùy chọn điều khiển"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{Đã thêm # chế độ điều khiển.}other{Đã thêm # chế độ điều khiển.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Đã xóa"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"Thêm <xliff:g id="APPNAME">%s</xliff:g>?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"Khi bạn thêm <xliff:g id="APPNAME">%s</xliff:g>, ứng dụng có thể bổ sung các chế độ điều khiển và nội dung vào bảng điều khiển này. Trong một số ứng dụng, bạn có thể chọn chế độ điều khiển nào sẽ hiển thị tại đây."</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"Được yêu thích"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"Được yêu thích, vị trí số <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Không được yêu thích"</string>
@@ -854,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Mở <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> của <xliff:g id="ARTIST_NAME">%2$s</xliff:g> trên <xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Phát <xliff:g id="SONG_NAME">%1$s</xliff:g> trên <xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Dành cho bạn"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Hủy"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Đưa thiết bị đến gần hơn để phát trên <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Để phát ở đây, vui lòng lại gần <xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -870,8 +871,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"Lỗi, hãy thử lại"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"Thêm các tùy chọn điều khiển"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"Chỉnh sửa tùy chọn điều khiển"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"Thêm ứng dụng"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Thêm thiết bị đầu ra"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Nhóm"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"Đã chọn 1 thiết bị"</string>
@@ -887,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Loa và màn hình"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Thiết bị được đề xuất"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Yêu cầu tài khoản trả phí"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cách tính năng truyền hoạt động"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Truyền"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Những người ở gần có thiết bị Bluetooth tương thích có thể nghe nội dung nghe nhìn bạn đang truyền"</string>
@@ -1032,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Chính sách của nơi làm việc chỉ cho phép bạn gọi điện thoại từ hồ sơ công việc"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Chuyển sang hồ sơ công việc"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Đóng"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Cài đặt màn hình khoá"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 15c834c..b3aef64 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -91,8 +91,13 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"底部边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左侧边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右侧边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"工作屏幕截图保存在“<xliff:g id="APP">%1$s</xliff:g>”应用中"</string>
+ <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
+ <skip />
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"文件"</string>
+ <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
+ <skip />
+ <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
+ <skip />
<string name="screenrecord_name" msgid="2596401223859996572">"屏幕录制器"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"正在处理屏幕录制视频"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"持续显示屏幕录制会话通知"</string>
@@ -800,10 +805,8 @@
<string name="controls_providers_title" msgid="6879775889857085056">"选择要添加控制器的应用"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{已添加 # 个控件。}other{已添加 # 个控件。}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"已移除"</string>
- <!-- no translation found for controls_panel_authorization_title (267429338785864842) -->
- <skip />
- <!-- no translation found for controls_panel_authorization (4540047176861801815) -->
- <skip />
+ <string name="controls_panel_authorization_title" msgid="267429338785864842">"添加“<xliff:g id="APPNAME">%s</xliff:g>”?"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"当您添加“<xliff:g id="APPNAME">%s</xliff:g>”后,它可以将控件和内容添加到此面板。在某些应用中,您可以选择在此处显示哪些控件。"</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"已收藏"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"已收藏,位置:<xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"已取消收藏"</string>
@@ -854,6 +857,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"打开<xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"通过<xliff:g id="APP_LABEL">%3$s</xliff:g>播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"通过<xliff:g id="APP_LABEL">%2$s</xliff:g>播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"为您推荐"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"撤消"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"若要在“<xliff:g id="DEVICENAME">%1$s</xliff:g>”上播放,请靠近这台设备"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"若要在此设备上播放,请再靠近<xliff:g id="DEVICENAME">%1$s</xliff:g>一点"</string>
@@ -870,8 +874,7 @@
<string name="controls_error_failed" msgid="960228639198558525">"出现错误,请重试"</string>
<string name="controls_menu_add" msgid="4447246119229920050">"添加控制器"</string>
<string name="controls_menu_edit" msgid="890623986951347062">"修改控制器"</string>
- <!-- no translation found for controls_menu_add_another_app (8661172304650786705) -->
- <skip />
+ <string name="controls_menu_add_another_app" msgid="8661172304650786705">"添加应用"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"添加输出设备"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"群组"</string>
<string name="media_output_dialog_single_device" msgid="3102758980643351058">"已选择 1 个设备"</string>
@@ -887,6 +890,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"音箱和显示屏"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"建议的设备"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"需要使用付费帐号"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"广播的运作方式"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"广播"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"附近使用兼容蓝牙设备的用户可以收听您广播的媒体内容"</string>
@@ -1032,6 +1036,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"根据您的工作政策,您只能通过工作资料拨打电话"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"切换到工作资料"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"关闭"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"锁屏设置"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 8aa29763..f13dbf05a 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"下方邊界 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左方邊界 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右方邊界 <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"工作的螢幕截圖會儲存在「<xliff:g id="APP">%1$s</xliff:g>」應用程式"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"已儲存在工作資料夾的「<xliff:g id="APP">%1$s</xliff:g>」中"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"檔案"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"「<xliff:g id="APPNAME">%1$s</xliff:g>」偵測到這張螢幕截圖。"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"「<xliff:g id="APPNAME">%1$s</xliff:g>」和其他開啟的應用程式偵測到這張螢幕截圖。"</string>
<string name="screenrecord_name" msgid="2596401223859996572">"螢幕畫面錄影工具"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"正在處理螢幕錄影內容"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"持續顯示錄影畫面工作階段通知"</string>
@@ -801,7 +803,7 @@
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{已新增 # 個控制項。}other{已新增 # 個控制項。}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"已移除"</string>
<string name="controls_panel_authorization_title" msgid="267429338785864842">"要新增「<xliff:g id="APPNAME">%s</xliff:g>」嗎?"</string>
- <string name="controls_panel_authorization" msgid="4540047176861801815">"當你新增「<xliff:g id="APPNAME">%s</xliff:g>」時,應用程式也可將控制選項及內容新增到這個面板。某些應用程式可讓你選擇要顯示在這裡的控制選項。"</string>
+ <string name="controls_panel_authorization" msgid="4540047176861801815">"如果您新增「<xliff:g id="APPNAME">%s</xliff:g>」,應用程式可將控制項和內容新增至此面板。部份應用程式可讓您選擇要在這裡顯示的控制項。"</string>
<string name="accessibility_control_favorite" msgid="8694362691985545985">"已加入收藏"</string>
<string name="accessibility_control_favorite_position" msgid="54220258048929221">"已加入至收藏位置 <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="accessibility_control_not_favorite" msgid="1291760269563092359">"已取消收藏"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"開啟 <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"在 <xliff:g id="APP_LABEL">%3$s</xliff:g> 播放 <xliff:g id="ARTIST_NAME">%2$s</xliff:g> 的《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"在 <xliff:g id="APP_LABEL">%2$s</xliff:g> 播放《<xliff:g id="SONG_NAME">%1$s</xliff:g>》"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"為您推薦"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"如要在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放,請靠近一點"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"如要在這部裝置播放,請靠近「<xliff:g id="DEVICENAME">%1$s</xliff:g>」一點"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"喇叭和螢幕"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"建議的裝置"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"需要付費帳戶"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"廣播運作方式"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"廣播"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"附近有兼容藍牙裝置的人可收聽您正在廣播的媒體內容"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"您的公司政策只允許透過工作設定檔撥打電話"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"切換至工作設定檔"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"關閉"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"上鎖畫面設定"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 321821c..0ca96fb 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"下方邊界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左側邊界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右側邊界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"工作的螢幕截圖會儲存在「<xliff:g id="APP">%1$s</xliff:g>」應用程式"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"已儲存在工作資料夾的「<xliff:g id="APP">%1$s</xliff:g>」中"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"檔案"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"「<xliff:g id="APPNAME">%1$s</xliff:g>」偵測到這張螢幕截圖。"</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"「<xliff:g id="APPNAME">%1$s</xliff:g>」和其他開啟的應用程式偵測到這張螢幕截圖。"</string>
<string name="screenrecord_name" msgid="2596401223859996572">"螢幕錄影器"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"處理螢幕錄影內容"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"持續顯示螢幕畫面錄製工作階段通知"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"開啟「<xliff:g id="APP_LABEL">%1$s</xliff:g>」"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"透過「<xliff:g id="APP_LABEL">%3$s</xliff:g>」播放<xliff:g id="ARTIST_NAME">%2$s</xliff:g>的〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"透過「<xliff:g id="APP_LABEL">%2$s</xliff:g>」播放〈<xliff:g id="SONG_NAME">%1$s</xliff:g>〉"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"為你推薦"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"復原"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"如要在「<xliff:g id="DEVICENAME">%1$s</xliff:g>」上播放,請靠近一點"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"如要在這部裝置播放,請移到更靠近「<xliff:g id="DEVICENAME">%1$s</xliff:g>」的位置"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"喇叭和螢幕"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"建議的裝置"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"必須有付費帳戶"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"廣播功能的運作方式"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"廣播"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"如果附近的人有相容的藍牙裝置,就可以聽到你正在廣播的媒體內容"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"貴公司政策僅允許透過工作資料夾撥打電話"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"切換至工作資料夾"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"關閉"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"螢幕鎖定設定"</string>
</resources>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index c094acf..29fb0e9 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -91,8 +91,10 @@
<string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Iphesenti elingu-<xliff:g id="PERCENT">%1$d</xliff:g> lomngcele ophansi"</string>
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Iphesenti elingu-<xliff:g id="PERCENT">%1$d</xliff:g> lomngcele ongakwesobunxele"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Iphesenti elingu-<xliff:g id="PERCENT">%1$d</xliff:g> lomngcele ongakwesokudla"</string>
- <string name="screenshot_work_profile_notification" msgid="2812417845875653929">"Izithombe-skrini zomsebenzi zigcinwa ku-app ye-<xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Kulondolozwe ku-<xliff:g id="APP">%1$s</xliff:g> kuphrofayela yomsebenzi"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Amafayela"</string>
+ <string name="screenshot_detected_template" msgid="7940376642921719915">"I-<xliff:g id="APPNAME">%1$s</xliff:g> ithole lesi sithombe-skrini."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"I-<xliff:g id="APPNAME">%1$s</xliff:g> namanye ama-app avuliwe athole lesi sithombe-skrini."</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Irekhoda yesikrini"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Icubungula okokuqopha iskrini"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Isaziso esiqhubekayo seseshini yokurekhoda isikrini"</string>
@@ -852,6 +854,7 @@
<string name="controls_media_smartspace_rec_description" msgid="4136242327044070732">"Vula i-<xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_description" msgid="2189271793070870883">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> ka-<xliff:g id="ARTIST_NAME">%2$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%3$s</xliff:g>"</string>
<string name="controls_media_smartspace_rec_item_no_artist_description" msgid="8703614798636591077">"Dlala i-<xliff:g id="SONG_NAME">%1$s</xliff:g> kusuka ku-<xliff:g id="APP_LABEL">%2$s</xliff:g>"</string>
+ <string name="controls_media_smartspace_rec_header" msgid="5053461390357112834">"Okwenzelwe wena"</string>
<string name="media_transfer_undo" msgid="1895606387620728736">"Hlehlisa"</string>
<string name="media_move_closer_to_start_cast" msgid="2673104707465013176">"Sondeza eduze ukudlala ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
<string name="media_move_closer_to_end_cast" msgid="7302555909119374738">"Ukuze udlale lapha, sondela ku-<xliff:g id="DEVICENAME">%1$s</xliff:g>"</string>
@@ -884,6 +887,7 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Izipikha Neziboniso"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Amadivayisi Aphakanyisiwe"</string>
+ <string name="media_output_status_require_premium" msgid="5691200962588753380">"Idinga i-akhawunti ye-premium"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Indlela ukusakaza okusebenza ngayo"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Sakaza"</string>
<string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"Abantu abaseduze nawe abanamadivayisi e-Bluetooth ahambisanayo bangalalela imidiya oyisakazayo"</string>
@@ -1029,6 +1033,5 @@
<string name="call_from_work_profile_text" msgid="3458704745640229638">"Inqubomgomo yakho yomsebenzi ikuvumela ukuthi wenze amakholi wefoni kuphela ngephrofayela yomsebenzi"</string>
<string name="call_from_work_profile_action" msgid="2937701298133010724">"Shintshela kuphrofayela yomsebenzi"</string>
<string name="call_from_work_profile_close" msgid="7927067108901068098">"Vala"</string>
- <!-- no translation found for lock_screen_settings (9197175446592718435) -->
- <skip />
+ <string name="lock_screen_settings" msgid="9197175446592718435">"Amasethingi okukhiya isikrini"</string>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2b0021b..9391714 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -43,12 +43,18 @@
<dimen name="navigation_edge_panel_height">268dp</dimen>
<!-- The threshold to drag to trigger the edge action -->
<dimen name="navigation_edge_action_drag_threshold">16dp</dimen>
+ <!-- The drag distance to consider evaluating gesture -->
+ <dimen name="navigation_edge_action_min_distance_to_start_animation">24dp</dimen>
<!-- The threshold to progress back animation for edge swipe -->
<dimen name="navigation_edge_action_progress_threshold">412dp</dimen>
<!-- The minimum display position of the arrow on the screen -->
<dimen name="navigation_edge_arrow_min_y">64dp</dimen>
<!-- The amount by which the arrow is shifted to avoid the finger-->
<dimen name="navigation_edge_finger_offset">64dp</dimen>
+ <!-- The threshold to dynamically activate the edge action -->
+ <dimen name="navigation_edge_action_reactivation_drag_threshold">32dp</dimen>
+ <!-- The threshold to dynamically deactivate the edge action -->
+ <dimen name="navigation_edge_action_deactivation_drag_threshold">32dp</dimen>
<!-- The thickness of the arrow -->
<dimen name="navigation_edge_arrow_thickness">4dp</dimen>
@@ -56,37 +62,61 @@
<dimen name="navigation_edge_minimum_x_delta_for_switch">32dp</dimen>
<!-- entry state -->
+ <item name="navigation_edge_entry_scale" format="float" type="dimen">0.98</item>
<dimen name="navigation_edge_entry_margin">4dp</dimen>
- <dimen name="navigation_edge_entry_background_width">8dp</dimen>
- <dimen name="navigation_edge_entry_background_height">60dp</dimen>
- <dimen name="navigation_edge_entry_edge_corners">30dp</dimen>
- <dimen name="navigation_edge_entry_far_corners">30dp</dimen>
- <dimen name="navigation_edge_entry_arrow_length">10dp</dimen>
- <dimen name="navigation_edge_entry_arrow_height">7dp</dimen>
+ <item name="navigation_edge_entry_background_alpha" format="float" type="dimen">1.0</item>
+ <dimen name="navigation_edge_entry_background_width">0dp</dimen>
+ <dimen name="navigation_edge_entry_background_height">48dp</dimen>
+ <dimen name="navigation_edge_entry_edge_corners">6dp</dimen>
+ <dimen name="navigation_edge_entry_far_corners">6dp</dimen>
+ <item name="navigation_edge_entry_arrow_alpha" format="float" type="dimen">0.0</item>
+ <dimen name="navigation_edge_entry_arrow_length">8.6dp</dimen>
+ <dimen name="navigation_edge_entry_arrow_height">5dp</dimen>
<!-- pre-threshold -->
<dimen name="navigation_edge_pre_threshold_margin">4dp</dimen>
- <dimen name="navigation_edge_pre_threshold_background_width">64dp</dimen>
- <dimen name="navigation_edge_pre_threshold_background_height">60dp</dimen>
- <dimen name="navigation_edge_pre_threshold_edge_corners">22dp</dimen>
- <dimen name="navigation_edge_pre_threshold_far_corners">26dp</dimen>
+ <item name="navigation_edge_pre_threshold_background_alpha" format="float" type="dimen">1.0
+ </item>
+ <item name="navigation_edge_pre_threshold_scale" format="float" type="dimen">0.98</item>
+ <dimen name="navigation_edge_pre_threshold_background_width">51dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_background_height">46dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_edge_corners">16dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_far_corners">20dp</dimen>
+ <item name="navigation_edge_pre_threshold_arrow_alpha" format="float" type="dimen">1.0</item>
+ <dimen name="navigation_edge_pre_threshold_arrow_length">8dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_arrow_height">5.6dp</dimen>
- <!-- post-threshold / active -->
+ <!-- active (post-threshold) -->
+ <item name="navigation_edge_active_scale" format="float" type="dimen">1.0</item>
<dimen name="navigation_edge_active_margin">14dp</dimen>
- <dimen name="navigation_edge_active_background_width">60dp</dimen>
- <dimen name="navigation_edge_active_background_height">60dp</dimen>
- <dimen name="navigation_edge_active_edge_corners">30dp</dimen>
- <dimen name="navigation_edge_active_far_corners">30dp</dimen>
- <dimen name="navigation_edge_active_arrow_length">8dp</dimen>
- <dimen name="navigation_edge_active_arrow_height">9dp</dimen>
+ <item name="navigation_edge_active_background_alpha" format="float" type="dimen">1.0</item>
+ <dimen name="navigation_edge_active_background_width">48dp</dimen>
+ <dimen name="navigation_edge_active_background_height">48dp</dimen>
+ <dimen name="navigation_edge_active_edge_corners">24dp</dimen>
+ <dimen name="navigation_edge_active_far_corners">24dp</dimen>
+ <item name="navigation_edge_active_arrow_alpha" format="float" type="dimen">1.0</item>
+ <dimen name="navigation_edge_active_arrow_length">6.4dp</dimen>
+ <dimen name="navigation_edge_active_arrow_height">7.2dp</dimen>
+ <!-- committed -->
+ <item name="navigation_edge_committed_scale" format="float" type="dimen">0.85</item>
+ <item name="navigation_edge_committed_alpha" format="float" type="dimen">0</item>
+
+ <!-- cancelled -->
+ <dimen name="navigation_edge_cancelled_background_width">0dp</dimen>
+
+ <item name="navigation_edge_stretch_scale" format="float" type="dimen">1.0</item>
<dimen name="navigation_edge_stretch_margin">18dp</dimen>
- <dimen name="navigation_edge_stretch_background_width">74dp</dimen>
- <dimen name="navigation_edge_stretch_background_height">60dp</dimen>
- <dimen name="navigation_edge_stretch_edge_corners">30dp</dimen>
- <dimen name="navigation_edge_stretch_far_corners">30dp</dimen>
- <dimen name="navigation_edge_stretched_arrow_length">7dp</dimen>
- <dimen name="navigation_edge_stretched_arrow_height">10dp</dimen>
+ <dimen name="navigation_edge_stretch_background_width">60dp</dimen>
+ <item name="navigation_edge_stretch_background_alpha" format="float" type="dimen">
+ @dimen/navigation_edge_entry_background_alpha
+ </item>
+ <dimen name="navigation_edge_stretch_background_height">48dp</dimen>
+ <dimen name="navigation_edge_stretch_edge_corners">24dp</dimen>
+ <dimen name="navigation_edge_stretch_far_corners">24dp</dimen>
+ <item name="navigation_edge_strech_arrow_alpha" format="float" type="dimen">1.0</item>
+ <dimen name="navigation_edge_stretched_arrow_length">5.6dp</dimen>
+ <dimen name="navigation_edge_stretched_arrow_height">8dp</dimen>
<dimen name="navigation_edge_cancelled_arrow_length">12dp</dimen>
<dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen>
@@ -154,6 +184,15 @@
<!-- Height of a small notification in the status bar-->
<dimen name="notification_min_height">@*android:dimen/notification_min_height</dimen>
+ <!-- Minimum allowed height of notifications -->
+ <dimen name="notification_validation_minimum_allowed_height">10dp</dimen>
+
+ <!-- Minimum height for displaying notification content. -->
+ <dimen name="notification_content_min_height">48dp</dimen>
+
+ <!-- Reference width used when validating notification layouts -->
+ <dimen name="notification_validation_reference_width">320dp</dimen>
+
<!-- Increased height of a small notification in the status bar -->
<dimen name="notification_min_height_increased">146dp</dimen>
@@ -1115,7 +1154,7 @@
<!-- Home Controls -->
<dimen name="controls_header_menu_size">48dp</dimen>
- <dimen name="controls_header_bottom_margin">24dp</dimen>
+ <dimen name="controls_header_bottom_margin">16dp</dimen>
<dimen name="controls_header_app_icon_size">24dp</dimen>
<dimen name="controls_top_margin">48dp</dimen>
<dimen name="controls_padding_horizontal">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 227b00e..7d5b66e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2382,6 +2382,10 @@
<string name="media_transfer_loading">Loading</string>
<!-- Default name of the device. [CHAR LIMIT=30] -->
<string name="media_ttt_default_device_type">tablet</string>
+ <!-- Description of media transfer icon of unknown app appears in receiver devices. [CHAR LIMIT=NONE]-->
+ <string name="media_transfer_receiver_content_description_unknown_app">Casting your media</string>
+ <!-- Description of media transfer icon appears in receiver devices. [CHAR LIMIT=NONE]-->
+ <string name="media_transfer_receiver_content_description_with_app_name">Casting <xliff:g id="app_label" example="Spotify">%1$s</xliff:g></string>
<!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] -->
<string name="controls_error_timeout">Inactive, check app</string>
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index d9c81af..5129fc0 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -73,11 +73,12 @@
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/qs_media_info_spacing"
android:layout_marginBottom="@dimen/qs_media_padding"
- android:layout_marginTop="0dp"
+ android:layout_marginTop="@dimen/qs_media_icon_offset"
app:layout_constraintStart_toStartOf="@id/header_title"
app:layout_constraintEnd_toStartOf="@id/header_artist"
app:layout_constraintTop_toTopOf="@id/header_artist"
- app:layout_constraintBottom_toTopOf="@id/media_action_barrier_top"
+ app:layout_constraintBottom_toBottomOf="@id/header_artist"
+ app:layout_constraintVertical_bias="0"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed" />
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index e56e5d5..00a0444 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -48,8 +48,7 @@
app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/clock"
app:layout_constraintEnd_toStartOf="@id/barrier"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBaseline_toBaselineOf="@id/clock"
app:layout_constraintHorizontal_bias="0"
/>
</Constraint>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
index 95675ce..209d5e8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
@@ -38,13 +38,19 @@
public class Monitor {
private final String mTag = getClass().getSimpleName();
private final Executor mExecutor;
+ private final Set<Condition> mPreconditions;
private final HashMap<Condition, ArraySet<Subscription.Token>> mConditions = new HashMap<>();
private final HashMap<Subscription.Token, SubscriptionState> mSubscriptions = new HashMap<>();
private static class SubscriptionState {
private final Subscription mSubscription;
+
+ // A subscription must maintain a reference to any active nested subscription so that it may
+ // be later removed when the current subscription becomes invalid.
+ private Subscription.Token mNestedSubscriptionToken;
private Boolean mAllConditionsMet;
+ private boolean mActive;
SubscriptionState(Subscription subscription) {
mSubscription = subscription;
@@ -54,7 +60,27 @@
return mSubscription.mConditions;
}
- public void update() {
+ /**
+ * Signals that the {@link Subscription} is now being monitored and will receive updates
+ * based on its conditions.
+ */
+ private void setActive(boolean active) {
+ if (mActive == active) {
+ return;
+ }
+
+ mActive = active;
+
+ final Callback callback = mSubscription.getCallback();
+
+ if (callback == null) {
+ return;
+ }
+
+ callback.onActiveChanged(active);
+ }
+
+ public void update(Monitor monitor) {
final Boolean result = Evaluator.INSTANCE.evaluate(mSubscription.mConditions,
Evaluator.OP_AND);
// Consider unknown (null) as true
@@ -65,7 +91,50 @@
}
mAllConditionsMet = newAllConditionsMet;
- mSubscription.mCallback.onConditionsChanged(mAllConditionsMet);
+
+ final Subscription nestedSubscription = mSubscription.getNestedSubscription();
+
+ if (nestedSubscription != null) {
+ if (mAllConditionsMet && mNestedSubscriptionToken == null) {
+ // When all conditions are met for a subscription with a nested subscription
+ // that is not currently being monitored, add the nested subscription for
+ // monitor.
+ mNestedSubscriptionToken =
+ monitor.addSubscription(nestedSubscription, null);
+ } else if (!mAllConditionsMet && mNestedSubscriptionToken != null) {
+ // When conditions are not met and there is an active nested condition, remove
+ // the nested condition from monitoring.
+ removeNestedSubscription(monitor);
+ }
+ return;
+ }
+
+ mSubscription.getCallback().onConditionsChanged(mAllConditionsMet);
+ }
+
+ /**
+ * Invoked when the {@link Subscription} has been added to the {@link Monitor}.
+ */
+ public void onAdded() {
+ setActive(true);
+ }
+
+ /**
+ * Invoked when the {@link Subscription} has been removed from the {@link Monitor},
+ * allowing cleanup code to run.
+ */
+ public void onRemoved(Monitor monitor) {
+ setActive(false);
+ removeNestedSubscription(monitor);
+ }
+
+ private void removeNestedSubscription(Monitor monitor) {
+ if (mNestedSubscriptionToken == null) {
+ return;
+ }
+
+ monitor.removeSubscription(mNestedSubscriptionToken);
+ mNestedSubscriptionToken = null;
}
}
@@ -77,9 +146,20 @@
}
};
+ /**
+ * Constructor for injected use-cases. By default, no preconditions are present.
+ */
@Inject
public Monitor(@Main Executor executor) {
+ this(executor, Collections.emptySet());
+ }
+
+ /**
+ * Main constructor, allowing specifying preconditions.
+ */
+ public Monitor(Executor executor, Set<Condition> preconditions) {
mExecutor = executor;
+ mPreconditions = preconditions;
}
private void updateConditionMetState(Condition condition) {
@@ -91,7 +171,7 @@
return;
}
- subscriptions.stream().forEach(token -> mSubscriptions.get(token).update());
+ subscriptions.stream().forEach(token -> mSubscriptions.get(token).update(this));
}
/**
@@ -101,15 +181,25 @@
* @return A {@link Subscription.Token} that can be used to remove the subscription.
*/
public Subscription.Token addSubscription(@NonNull Subscription subscription) {
+ return addSubscription(subscription, mPreconditions);
+ }
+
+ private Subscription.Token addSubscription(@NonNull Subscription subscription,
+ Set<Condition> preconditions) {
+ // If preconditions are set on the monitor, set up as a nested condition.
+ final Subscription normalizedCondition = preconditions != null
+ ? new Subscription.Builder(subscription).addConditions(preconditions).build()
+ : subscription;
+
final Subscription.Token token = new Subscription.Token();
- final SubscriptionState state = new SubscriptionState(subscription);
+ final SubscriptionState state = new SubscriptionState(normalizedCondition);
mExecutor.execute(() -> {
if (shouldLog()) Log.d(mTag, "adding subscription");
mSubscriptions.put(token, state);
// Add and associate conditions.
- subscription.getConditions().stream().forEach(condition -> {
+ normalizedCondition.getConditions().stream().forEach(condition -> {
if (!mConditions.containsKey(condition)) {
mConditions.put(condition, new ArraySet<>());
condition.addCallback(mConditionCallback);
@@ -118,8 +208,10 @@
mConditions.get(condition).add(token);
});
+ state.onAdded();
+
// Update subscription state.
- state.update();
+ state.update(this);
});
return token;
@@ -139,7 +231,9 @@
return;
}
- mSubscriptions.remove(token).getConditions().forEach(condition -> {
+ final SubscriptionState removedSubscription = mSubscriptions.remove(token);
+
+ removedSubscription.getConditions().forEach(condition -> {
if (!mConditions.containsKey(condition)) {
Log.e(mTag, "condition not present:" + condition);
return;
@@ -153,6 +247,8 @@
mConditions.remove(condition);
}
});
+
+ removedSubscription.onRemoved(this);
});
}
@@ -168,12 +264,19 @@
private final Set<Condition> mConditions;
private final Callback mCallback;
- /**
- *
- */
- public Subscription(Set<Condition> conditions, Callback callback) {
+ // A nested {@link Subscription} is a special callback where the specified condition's
+ // active state is dependent on the conditions of the parent {@link Subscription} being met.
+ // Once active, the nested subscription's conditions are registered as normal with the
+ // monitor and its callback (which could also be a nested condition) is triggered based on
+ // those conditions. The nested condition will be removed from monitor if the outer
+ // subscription's conditions ever become invalid.
+ private final Subscription mNestedSubscription;
+
+ private Subscription(Set<Condition> conditions, Callback callback,
+ Subscription nestedSubscription) {
this.mConditions = Collections.unmodifiableSet(conditions);
this.mCallback = callback;
+ this.mNestedSubscription = nestedSubscription;
}
public Set<Condition> getConditions() {
@@ -184,6 +287,10 @@
return mCallback;
}
+ public Subscription getNestedSubscription() {
+ return mNestedSubscription;
+ }
+
/**
* A {@link Token} is an identifier that is associated with a {@link Subscription} which is
* registered with a {@link Monitor}.
@@ -196,14 +303,26 @@
*/
public static class Builder {
private final Callback mCallback;
+ private final Subscription mNestedSubscription;
private final ArraySet<Condition> mConditions;
+ private final ArraySet<Condition> mPreconditions;
/**
* Default constructor specifying the {@link Callback} for the {@link Subscription}.
*/
public Builder(Callback callback) {
+ this(null, callback);
+ }
+
+ public Builder(Subscription nestedSubscription) {
+ this(nestedSubscription, null);
+ }
+
+ private Builder(Subscription nestedSubscription, Callback callback) {
+ mNestedSubscription = nestedSubscription;
mCallback = callback;
- mConditions = new ArraySet<>();
+ mConditions = new ArraySet();
+ mPreconditions = new ArraySet();
}
/**
@@ -217,11 +336,38 @@
}
/**
+ * Adds a set of {@link Condition} to be a precondition for {@link Subscription}.
+ *
+ * @return The updated {@link Builder}.
+ */
+ public Builder addPreconditions(Set<Condition> condition) {
+ if (condition == null) {
+ return this;
+ }
+ mPreconditions.addAll(condition);
+ return this;
+ }
+
+ /**
+ * Adds a {@link Condition} to be a precondition for {@link Subscription}.
+ *
+ * @return The updated {@link Builder}.
+ */
+ public Builder addPrecondition(Condition condition) {
+ mPreconditions.add(condition);
+ return this;
+ }
+
+ /**
* Adds a set of {@link Condition} to be associated with the {@link Subscription}.
*
* @return The updated {@link Builder}.
*/
public Builder addConditions(Set<Condition> condition) {
+ if (condition == null) {
+ return this;
+ }
+
mConditions.addAll(condition);
return this;
}
@@ -232,7 +378,11 @@
* @return The resulting {@link Subscription}.
*/
public Subscription build() {
- return new Subscription(mConditions, mCallback);
+ final Subscription subscription =
+ new Subscription(mConditions, mCallback, mNestedSubscription);
+ return !mPreconditions.isEmpty()
+ ? new Subscription(mPreconditions, null, subscription)
+ : subscription;
}
}
}
@@ -255,5 +405,13 @@
* only partial conditions have been fulfilled.
*/
void onConditionsChanged(boolean allConditionsMet);
+
+ /**
+ * Called when the active state of the {@link Subscription} changes.
+ * @param active {@code true} when changes to the conditions will affect the
+ * {@link Subscription}, {@code false} otherwise.
+ */
+ default void onActiveChanged(boolean active) {
+ }
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index ef2247f..9a581aa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -114,7 +114,27 @@
/** Dump region sampler */
fun dump(pw: PrintWriter) {
- regionSampler?.dump(pw)
+ pw.println("[RegionSampler]")
+ pw.println("regionSamplingEnabled: $regionSamplingEnabled")
+ pw.println("regionDarkness: $regionDarkness")
+ pw.println("lightForegroundColor: ${Integer.toHexString(lightForegroundColor)}")
+ pw.println("darkForegroundColor: ${Integer.toHexString(darkForegroundColor)}")
+ pw.println("passed-in sampledView: $sampledView")
+ pw.println("calculated samplingBounds: $samplingBounds")
+ pw.println(
+ "sampledView width: ${sampledView?.width}, sampledView height: ${sampledView?.height}"
+ )
+ pw.println("screen width: ${displaySize.x}, screen height: ${displaySize.y}")
+ pw.println(
+ "sampledRegionWithOffset: ${convertBounds(calculateSampledRegion(sampledView!!))}"
+ )
+ // TODO(b/265969235): mock initialSampling based on if component is on HS or LS wallpaper
+ // HS Smartspace - wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_SYSTEM)
+ // LS Smartspace, clock - wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK)
+ pw.println(
+ "initialSampling for lockscreen: " +
+ "${wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK)}"
+ )
}
fun calculateSampledRegion(sampledView: View): RectF {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
index 9b73cc3..bd20777 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
@@ -25,7 +25,7 @@
import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows
/** Extension of [TextView] which draws two shadows on the text (ambient and key shadows} */
-class DoubleShadowTextView
+open class DoubleShadowTextView
@JvmOverloads
constructor(
context: Context,
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index 38fa354..54ae84f9 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -16,18 +16,20 @@
package com.android.keyguard
-import android.annotation.IntDef
import android.content.ContentResolver
import android.database.ContentObserver
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT
import android.net.Uri
import android.os.Handler
+import android.os.PowerManager
+import android.os.PowerManager.WAKE_REASON_UNFOLD_DEVICE
import android.os.UserHandle
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE
import android.util.Log
import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser
@@ -52,23 +54,26 @@
companion object {
const val TAG = "ActiveUnlockConfig"
-
- const val BIOMETRIC_TYPE_NONE = 0
- const val BIOMETRIC_TYPE_ANY_FACE = 1
- const val BIOMETRIC_TYPE_ANY_FINGERPRINT = 2
- const val BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT = 3
}
- @Retention(AnnotationRetention.SOURCE)
- @IntDef(BIOMETRIC_TYPE_NONE, BIOMETRIC_TYPE_ANY_FACE, BIOMETRIC_TYPE_ANY_FINGERPRINT,
- BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT)
- annotation class BiometricType
-
/**
* Indicates the origin for an active unlock request.
*/
- enum class ACTIVE_UNLOCK_REQUEST_ORIGIN {
- WAKE, UNLOCK_INTENT, BIOMETRIC_FAIL, ASSISTANT
+ enum class ActiveUnlockRequestOrigin {
+ WAKE,
+ UNLOCK_INTENT,
+ BIOMETRIC_FAIL,
+ ASSISTANT,
+ }
+
+ /**
+ * Biometric type options.
+ */
+ enum class BiometricType(val intValue: Int) {
+ NONE(0),
+ ANY_FACE(1),
+ ANY_FINGERPRINT(2),
+ UNDER_DISPLAY_FINGERPRINT(3),
}
var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null
@@ -76,9 +81,10 @@
private var requestActiveUnlockOnUnlockIntent = false
private var requestActiveUnlockOnBioFail = false
- private var faceErrorsToTriggerBiometricFailOn = mutableSetOf(FACE_ERROR_TIMEOUT)
+ private var faceErrorsToTriggerBiometricFailOn = mutableSetOf<Int>()
private var faceAcquireInfoToTriggerBiometricFailOn = mutableSetOf<Int>()
- private var onUnlockIntentWhenBiometricEnrolled = mutableSetOf<Int>(BIOMETRIC_TYPE_NONE)
+ private var onUnlockIntentWhenBiometricEnrolled = mutableSetOf<Int>()
+ private var wakeupsConsideredUnlockIntents = mutableSetOf<Int>()
private val settingsObserver = object : ContentObserver(handler) {
private val wakeUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE)
@@ -89,16 +95,19 @@
secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)
private val unlockIntentWhenBiometricEnrolledUri =
secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)
+ private val wakeupsConsideredUnlockIntentsUri =
+ secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS)
fun register() {
registerUri(
listOf(
- wakeUri,
- unlockIntentUri,
- bioFailUri,
- faceErrorsUri,
- faceAcquireInfoUri,
- unlockIntentWhenBiometricEnrolledUri
+ wakeUri,
+ unlockIntentUri,
+ bioFailUri,
+ faceErrorsUri,
+ faceAcquireInfoUri,
+ unlockIntentWhenBiometricEnrolledUri,
+ wakeupsConsideredUnlockIntentsUri,
)
)
@@ -153,7 +162,7 @@
secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
getCurrentUser()),
faceAcquireInfoToTriggerBiometricFailOn,
- setOf<Int>())
+ emptySet())
}
if (selfChange || uris.contains(unlockIntentWhenBiometricEnrolledUri)) {
@@ -162,7 +171,16 @@
ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
getCurrentUser()),
onUnlockIntentWhenBiometricEnrolled,
- setOf(BIOMETRIC_TYPE_NONE))
+ setOf(BiometricType.NONE.intValue))
+ }
+
+ if (selfChange || uris.contains(wakeupsConsideredUnlockIntentsUri)) {
+ processStringArray(
+ secureSettings.getStringForUser(
+ ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ getCurrentUser()),
+ wakeupsConsideredUnlockIntents,
+ setOf(WAKE_REASON_UNFOLD_DEVICE))
}
}
@@ -181,10 +199,12 @@
out.clear()
stringSetting?.let {
for (code: String in stringSetting.split("|")) {
- try {
- out.add(code.toInt())
- } catch (e: NumberFormatException) {
- Log.e(TAG, "Passed an invalid setting=$code")
+ if (code.isNotEmpty()) {
+ try {
+ out.add(code.toInt())
+ } catch (e: NumberFormatException) {
+ Log.e(TAG, "Passed an invalid setting=$code")
+ }
}
}
} ?: out.addAll(default)
@@ -221,22 +241,30 @@
}
/**
+ * Whether the PowerManager wake reason is considered an unlock intent and should use origin
+ * [ActiveUnlockRequestOrigin.UNLOCK_INTENT] instead of [ActiveUnlockRequestOrigin.WAKE].
+ */
+ fun isWakeupConsideredUnlockIntent(pmWakeReason: Int): Boolean {
+ return wakeupsConsideredUnlockIntents.contains(pmWakeReason)
+ }
+
+ /**
* Whether to trigger active unlock based on where the request is coming from and
* the current settings.
*/
- fun shouldAllowActiveUnlockFromOrigin(requestOrigin: ACTIVE_UNLOCK_REQUEST_ORIGIN): Boolean {
+ fun shouldAllowActiveUnlockFromOrigin(requestOrigin: ActiveUnlockRequestOrigin): Boolean {
return when (requestOrigin) {
- ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE -> requestActiveUnlockOnWakeup
+ ActiveUnlockRequestOrigin.WAKE -> requestActiveUnlockOnWakeup
- ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT ->
+ ActiveUnlockRequestOrigin.UNLOCK_INTENT ->
requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup ||
(shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment())
- ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL ->
+ ActiveUnlockRequestOrigin.BIOMETRIC_FAIL ->
requestActiveUnlockOnBioFail || requestActiveUnlockOnUnlockIntent ||
requestActiveUnlockOnWakeup
- ACTIVE_UNLOCK_REQUEST_ORIGIN.ASSISTANT -> isActiveUnlockEnabled()
+ ActiveUnlockRequestOrigin.ASSISTANT -> isActiveUnlockEnabled()
}
}
@@ -252,18 +280,18 @@
val udfpsEnrolled = it.isUdfpsEnrolled
if (!anyFaceEnrolled && !anyFingerprintEnrolled) {
- return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_NONE)
+ return onUnlockIntentWhenBiometricEnrolled.contains(BiometricType.NONE.intValue)
}
if (!anyFaceEnrolled && anyFingerprintEnrolled) {
return onUnlockIntentWhenBiometricEnrolled.contains(
- BIOMETRIC_TYPE_ANY_FINGERPRINT) ||
+ BiometricType.ANY_FINGERPRINT.intValue) ||
(udfpsEnrolled && onUnlockIntentWhenBiometricEnrolled.contains(
- BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT))
+ BiometricType.UNDER_DISPLAY_FINGERPRINT.intValue))
}
if (!anyFingerprintEnrolled && anyFaceEnrolled) {
- return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_ANY_FACE)
+ return onUnlockIntentWhenBiometricEnrolled.contains(BiometricType.ANY_FACE.intValue)
}
}
@@ -275,11 +303,15 @@
pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup")
pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent")
pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail")
- pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=" +
- "$onUnlockIntentWhenBiometricEnrolled")
+ pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=${
+ onUnlockIntentWhenBiometricEnrolled.map { BiometricType.values()[it] }
+ }")
pw.println(" requestActiveUnlockOnFaceError=$faceErrorsToTriggerBiometricFailOn")
pw.println(" requestActiveUnlockOnFaceAcquireInfo=" +
"$faceAcquireInfoToTriggerBiometricFailOn")
+ pw.println(" activeUnlockWakeupsConsideredUnlockIntents=${
+ wakeupsConsideredUnlockIntents.map { PowerManager.wakeReasonToString(it) }
+ }")
pw.println("Current state:")
keyguardUpdateMonitor?.let {
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 7f95ca6..1254e1e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -24,8 +24,8 @@
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
-import android.widget.FrameLayout
import android.view.ViewTreeObserver
+import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -40,34 +40,37 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.log.dagger.KeyguardSmallClockLog
import com.android.systemui.log.dagger.KeyguardLargeClockLog
+import com.android.systemui.log.dagger.KeyguardSmallClockLog
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockTickRate
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
import com.android.systemui.shared.regionsampling.RegionSampler
+import com.android.systemui.plugins.Weather
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.Locale
+import java.util.TimeZone
+import java.util.concurrent.Executor
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
-import java.util.Locale
-import java.util.TimeZone
-import java.util.concurrent.Executor
-import javax.inject.Inject
/**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
* [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
*/
-open class ClockEventController @Inject constructor(
+open class ClockEventController
+@Inject
+constructor(
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val broadcastDispatcher: BroadcastDispatcher,
@@ -114,52 +117,59 @@
private var disposableHandle: DisposableHandle? = null
private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
- private val mLayoutChangedListener = object : View.OnLayoutChangeListener {
- private var currentSmallClockView: View? = null
- private var currentLargeClockView: View? = null
- private var currentSmallClockLocation = IntArray(2)
- private var currentLargeClockLocation = IntArray(2)
+ private val mLayoutChangedListener =
+ object : View.OnLayoutChangeListener {
+ private var currentSmallClockView: View? = null
+ private var currentLargeClockView: View? = null
+ private var currentSmallClockLocation = IntArray(2)
+ private var currentLargeClockLocation = IntArray(2)
- override fun onLayoutChange(
- view: View?,
- left: Int,
- top: Int,
- right: Int,
- bottom: Int,
- oldLeft: Int,
- oldTop: Int,
- oldRight: Int,
- oldBottom: Int
- ) {
- val parent = (view?.parent) as FrameLayout
+ override fun onLayoutChange(
+ view: View?,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ oldLeft: Int,
+ oldTop: Int,
+ oldRight: Int,
+ oldBottom: Int
+ ) {
+ val parent = (view?.parent) as FrameLayout
- // don't pass in negative bounds when clocks are in transition state
- if (view.locationOnScreen[0] < 0 || view.locationOnScreen[1] < 0) {
- return
- }
+ // don't pass in negative bounds when clocks are in transition state
+ if (view.locationOnScreen[0] < 0 || view.locationOnScreen[1] < 0) {
+ return
+ }
- // SMALL CLOCK
- if (parent.id == R.id.lockscreen_clock_view) {
- // view bounds have changed due to clock size changing (i.e. different character widths)
- // AND/OR the view has been translated when transitioning between small and large clock
- if (view != currentSmallClockView ||
- !view.locationOnScreen.contentEquals(currentSmallClockLocation)) {
- currentSmallClockView = view
- currentSmallClockLocation = view.locationOnScreen
- updateRegionSampler(view)
+ // SMALL CLOCK
+ if (parent.id == R.id.lockscreen_clock_view) {
+ // view bounds have changed due to clock size changing (i.e. different character
+ // widths)
+ // AND/OR the view has been translated when transitioning between small and
+ // large clock
+ if (
+ view != currentSmallClockView ||
+ !view.locationOnScreen.contentEquals(currentSmallClockLocation)
+ ) {
+ currentSmallClockView = view
+ currentSmallClockLocation = view.locationOnScreen
+ updateRegionSampler(view)
+ }
+ }
+ // LARGE CLOCK
+ else if (parent.id == R.id.lockscreen_clock_view_large) {
+ if (
+ view != currentLargeClockView ||
+ !view.locationOnScreen.contentEquals(currentLargeClockLocation)
+ ) {
+ currentLargeClockView = view
+ currentLargeClockLocation = view.locationOnScreen
+ updateRegionSampler(view)
+ }
+ }
}
}
- // LARGE CLOCK
- else if (parent.id == R.id.lockscreen_clock_view_large) {
- if (view != currentLargeClockView ||
- !view.locationOnScreen.contentEquals(currentLargeClockLocation)) {
- currentLargeClockView = view
- currentLargeClockLocation = view.locationOnScreen
- updateRegionSampler(view)
- }
- }
- }
- }
private fun updateColors() {
val wallpaperManager = WallpaperManager.getInstance(context)
@@ -188,30 +198,33 @@
private fun updateRegionSampler(sampledRegion: View) {
regionSampler?.stopRegionSampler()
- regionSampler = createRegionSampler(
- sampledRegion,
- mainExecutor,
- bgExecutor,
- regionSamplingEnabled,
- ::updateColors
- )?.apply { startRegionSampler() }
+ regionSampler =
+ createRegionSampler(
+ sampledRegion,
+ mainExecutor,
+ bgExecutor,
+ regionSamplingEnabled,
+ ::updateColors
+ )
+ ?.apply { startRegionSampler() }
updateColors()
}
protected open fun createRegionSampler(
- sampledView: View?,
- mainExecutor: Executor?,
- bgExecutor: Executor?,
- regionSamplingEnabled: Boolean,
- updateColors: () -> Unit
+ sampledView: View?,
+ mainExecutor: Executor?,
+ bgExecutor: Executor?,
+ regionSamplingEnabled: Boolean,
+ updateColors: () -> Unit
): RegionSampler? {
return RegionSampler(
sampledView,
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- updateColors)
+ updateColors
+ )
}
var regionSampler: RegionSampler? = null
@@ -223,58 +236,68 @@
private var smallClockIsDark = true
private var largeClockIsDark = true
- private val configListener = object : ConfigurationController.ConfigurationListener {
- override fun onThemeChanged() {
- clock?.events?.onColorPaletteChanged(resources)
- updateColors()
- }
-
- override fun onDensityOrFontScaleChanged() {
- updateFontSizes()
- }
- }
-
- private val batteryCallback = object : BatteryStateChangeCallback {
- override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- if (isKeyguardVisible && !isCharging && charging) {
- clock?.animations?.charge()
+ private val configListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ clock?.events?.onColorPaletteChanged(resources)
+ updateColors()
}
- isCharging = charging
- }
- }
- private val localeBroadcastReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- clock?.events?.onLocaleChanged(Locale.getDefault())
+ override fun onDensityOrFontScaleChanged() {
+ updateFontSizes()
+ }
}
- }
- private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onKeyguardVisibilityChanged(visible: Boolean) {
- isKeyguardVisible = visible
- if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) {
- if (!isKeyguardVisible) {
- clock?.animations?.doze(if (isDozing) 1f else 0f)
+ private val batteryCallback =
+ object : BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ if (isKeyguardVisible && !isCharging && charging) {
+ clock?.animations?.charge()
+ }
+ isCharging = charging
+ }
+ }
+
+ private val localeBroadcastReceiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ clock?.events?.onLocaleChanged(Locale.getDefault())
+ }
+ }
+
+ private val keyguardUpdateMonitorCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onKeyguardVisibilityChanged(visible: Boolean) {
+ isKeyguardVisible = visible
+ if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ if (!isKeyguardVisible) {
+ clock?.animations?.doze(if (isDozing) 1f else 0f)
+ }
+ }
+
+ smallTimeListener?.update(shouldTimeListenerRun)
+ largeTimeListener?.update(shouldTimeListenerRun)
+ }
+
+ override fun onTimeFormatChanged(timeFormat: String) {
+ clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) {
+ clock?.events?.onTimeZoneChanged(timeZone)
+ }
+
+ override fun onUserSwitchComplete(userId: Int) {
+ clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+
+ override fun onWeatherDataChanged(data: Weather?) {
+ if (data != null) {
+ clock?.events?.onWeatherDataChanged(data)
}
}
-
- smallTimeListener?.update(shouldTimeListenerRun)
- largeTimeListener?.update(shouldTimeListenerRun)
}
- override fun onTimeFormatChanged(timeFormat: String) {
- clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
- }
-
- override fun onTimeZoneChanged(timeZone: TimeZone) {
- clock?.events?.onTimeZoneChanged(timeZone)
- }
-
- override fun onUserSwitchComplete(userId: Int) {
- clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
- }
- }
-
fun registerListeners(parent: View) {
if (isRegistered) {
return
@@ -288,17 +311,18 @@
configurationController.addCallback(configListener)
batteryController.addCallback(batteryCallback)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
- disposableHandle = parent.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- listenForDozing(this)
- if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
- listenForDozeAmountTransition(this)
- listenForAnyStateToAodTransition(this)
- } else {
- listenForDozeAmount(this)
+ disposableHandle =
+ parent.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ listenForDozing(this)
+ if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ listenForDozeAmountTransition(this)
+ listenForAnyStateToAodTransition(this)
+ } else {
+ listenForDozeAmount(this)
+ }
}
}
- }
smallTimeListener?.update(shouldTimeListenerRun)
largeTimeListener?.update(shouldTimeListenerRun)
}
@@ -337,10 +361,18 @@
}
private fun updateFontSizes() {
- clock?.smallClock?.events?.onFontSettingChanged(
- resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat())
- clock?.largeClock?.events?.onFontSettingChanged(
- resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat())
+ clock
+ ?.smallClock
+ ?.events
+ ?.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
+ )
+ clock
+ ?.largeClock
+ ?.events
+ ?.onFontSettingChanged(
+ resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
+ )
}
private fun handleDoze(doze: Float) {
@@ -352,68 +384,59 @@
@VisibleForTesting
internal fun listenForDozeAmount(scope: CoroutineScope): Job {
- return scope.launch {
- keyguardInteractor.dozeAmount.collect {
- handleDoze(it)
- }
- }
+ return scope.launch { keyguardInteractor.dozeAmount.collect { handleDoze(it) } }
}
@VisibleForTesting
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.dozeAmountTransition.collect {
- handleDoze(it.value)
- }
+ keyguardTransitionInteractor.dozeAmountTransition.collect { handleDoze(it.value) }
}
}
/**
- * When keyguard is displayed again after being gone, the clock must be reset to full
- * dozing.
+ * When keyguard is displayed again after being gone, the clock must be reset to full dozing.
*/
@VisibleForTesting
internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.anyStateToAodTransition.filter {
- it.transitionState == TransitionState.FINISHED
- }.collect {
- handleDoze(1f)
- }
+ keyguardTransitionInteractor.anyStateToAodTransition
+ .filter { it.transitionState == TransitionState.FINISHED }
+ .collect { handleDoze(1f) }
}
}
@VisibleForTesting
internal fun listenForDozing(scope: CoroutineScope): Job {
return scope.launch {
- combine (
- keyguardInteractor.dozeAmount,
- keyguardInteractor.isDozing,
- ) { localDozeAmount, localIsDozing ->
- localDozeAmount > dozeAmount || localIsDozing
- }
- .collect { localIsDozing ->
- isDozing = localIsDozing
- }
+ combine(
+ keyguardInteractor.dozeAmount,
+ keyguardInteractor.isDozing,
+ ) { localDozeAmount, localIsDozing ->
+ localDozeAmount > dozeAmount || localIsDozing
+ }
+ .collect { localIsDozing -> isDozing = localIsDozing }
}
}
class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) {
- val predrawListener = ViewTreeObserver.OnPreDrawListener {
- clockFace.events.onTimeTick()
- true
- }
-
- val secondsRunnable = object : Runnable {
- override fun run() {
- if (!isRunning) {
- return
- }
-
- executor.executeDelayed(this, 990)
+ val predrawListener =
+ ViewTreeObserver.OnPreDrawListener {
clockFace.events.onTimeTick()
+ true
}
- }
+
+ val secondsRunnable =
+ object : Runnable {
+ override fun run() {
+ if (!isRunning) {
+ return
+ }
+
+ executor.executeDelayed(this, 990)
+ clockFace.events.onTimeTick()
+ }
+ }
var isRunning: Boolean = false
private set
@@ -425,7 +448,9 @@
isRunning = true
when (clockFace.events.tickRate) {
- ClockTickRate.PER_MINUTE -> {/* Handled by KeyguardClockSwitchController */}
+ ClockTickRate.PER_MINUTE -> {
+ /* Handled by KeyguardClockSwitchController */
+ }
ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable)
ClockTickRate.PER_FRAME -> {
clockFace.view.viewTreeObserver.addOnPreDrawListener(predrawListener)
@@ -435,7 +460,9 @@
}
fun stop() {
- if (!isRunning) { return }
+ if (!isRunning) {
+ return
+ }
isRunning = false
clockFace.view.viewTreeObserver.removeOnPreDrawListener(predrawListener)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 4acbb0a..0326b6d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -209,7 +209,6 @@
if (!animate) {
out.setAlpha(0f);
- out.setVisibility(INVISIBLE);
in.setAlpha(1f);
in.setVisibility(VISIBLE);
mStatusArea.setTranslationY(statusAreaYTranslation);
@@ -225,10 +224,7 @@
direction * -mClockSwitchYAmount));
mClockOutAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
- if (mClockOutAnim == animation) {
- out.setVisibility(INVISIBLE);
- mClockOutAnim = null;
- }
+ mClockOutAnim = null;
}
});
@@ -242,9 +238,7 @@
mClockInAnim.setStartDelay(CLOCK_OUT_MILLIS / 2);
mClockInAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
- if (mClockInAnim == animation) {
- mClockInAnim = null;
- }
+ mClockInAnim = null;
}
});
@@ -257,9 +251,7 @@
mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
- if (mStatusAreaAnim == animation) {
- mStatusAreaAnim = null;
- }
+ mStatusAreaAnim = null;
}
});
mStatusAreaAnim.start();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index a148aa1..baaeb2a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -106,6 +106,12 @@
updateDoubleLineClock();
}
};
+ private final ContentObserver mShowWeatherObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean change) {
+ setWeatherVisibility();
+ }
+ };
private final KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener
mKeyguardUnlockAnimationListener =
@@ -216,7 +222,15 @@
UserHandle.USER_ALL
);
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
+ false, /* notifyForDescendants */
+ mShowWeatherObserver,
+ UserHandle.USER_ALL
+ );
+
updateDoubleLineClock();
+ setWeatherVisibility();
mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
mKeyguardUnlockAnimationListener);
@@ -390,6 +404,10 @@
int clockHeight = clock.getLargeClock().getView().getHeight();
return frameHeight / 2 + clockHeight / 2 + mKeyguardLargeClockTopMargin / -2;
} else {
+ // This is only called if we've never shown the large clock as the frame is inflated
+ // with 'gone', but then the visibility is never set when it is animated away by
+ // KeyguardClockSwitch, instead it is removed from the view hierarchy.
+ // TODO(b/261755021): Cleanup Large Frame Visibility
int clockHeight = clock.getSmallClock().getView().getHeight();
return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
}
@@ -407,11 +425,15 @@
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
return clock.getLargeClock().getView().getHeight();
} else {
+ // Is not called except in certain edge cases, see comment in getClockBottom
+ // TODO(b/261755021): Cleanup Large Frame Visibility
return clock.getSmallClock().getView().getHeight();
}
}
boolean isClockTopAligned() {
+ // Returns false except certain edge cases, see comment in getClockBottom
+ // TODO(b/261755021): Cleanup Large Frame Visibility
return mLargeClockFrame.getVisibility() != View.VISIBLE;
}
@@ -449,6 +471,14 @@
}
}
+ private void setWeatherVisibility() {
+ if (mWeatherView != null) {
+ mUiExecutor.execute(
+ () -> mWeatherView.setVisibility(
+ mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE));
+ }
+ }
+
/**
* Sets the clipChildren property on relevant views, to allow the smartspace to draw out of
* bounds during the unlock transition.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index 1a06b5f..fe8b8c9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -27,6 +27,7 @@
override var userId: Int = 0,
override var listening: Boolean = false,
// keep sorted
+ var alternateBouncerShowing: Boolean = false,
var authInterruptActive: Boolean = false,
var biometricSettingEnabledForUser: Boolean = false,
var bouncerFullyShown: Boolean = false,
@@ -44,7 +45,6 @@
var secureCameraLaunched: Boolean = false,
var supportsDetect: Boolean = false,
var switchingUser: Boolean = false,
- var udfpsBouncerShowing: Boolean = false,
var udfpsFingerDown: Boolean = false,
var userNotTrustedOrDetectionIsNeeded: Boolean = false,
) : KeyguardListenModel() {
@@ -73,7 +73,7 @@
secureCameraLaunched.toString(),
supportsDetect.toString(),
switchingUser.toString(),
- udfpsBouncerShowing.toString(),
+ alternateBouncerShowing.toString(),
udfpsFingerDown.toString(),
userNotTrustedOrDetectionIsNeeded.toString(),
)
@@ -95,6 +95,7 @@
userId = model.userId
listening = model.listening
// keep sorted
+ alternateBouncerShowing = model.alternateBouncerShowing
biometricSettingEnabledForUser = model.biometricSettingEnabledForUser
bouncerFullyShown = model.bouncerFullyShown
faceAndFpNotAuthenticated = model.faceAndFpNotAuthenticated
@@ -111,7 +112,6 @@
secureCameraLaunched = model.secureCameraLaunched
supportsDetect = model.supportsDetect
switchingUser = model.switchingUser
- udfpsBouncerShowing = model.udfpsBouncerShowing
switchingUser = model.switchingUser
udfpsFingerDown = model.udfpsFingerDown
userNotTrustedOrDetectionIsNeeded = model.userNotTrustedOrDetectionIsNeeded
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 57bfe54..9fcacce 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -237,7 +237,7 @@
}
if (mUpdateMonitor.isFaceEnrolled()) {
mUpdateMonitor.requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
"swipeUpOnBouncer");
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 54886c3..bb29a7e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -94,6 +94,7 @@
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
@@ -108,7 +109,6 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -148,6 +148,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
import com.android.systemui.log.SessionTracker;
+import com.android.systemui.plugins.Weather;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -260,6 +261,7 @@
@VisibleForTesting
public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
+ public static final int BIOMETRIC_HELP_FACE_NOT_AVAILABLE = -3;
/**
* If no cancel signal has been received after this amount of time, set the biometric running
@@ -310,7 +312,7 @@
private boolean mGoingToSleep;
private boolean mPrimaryBouncerFullyShown;
private boolean mPrimaryBouncerIsOrWillBeShowing;
- private boolean mUdfpsBouncerShowing;
+ private boolean mAlternateBouncerShowing;
private boolean mAuthInterruptActive;
private boolean mNeedsSlowUnlockTransition;
private boolean mAssistantVisible;
@@ -535,7 +537,8 @@
* It's assumed that the trust was granted for the current user.
*/
private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) {
- final boolean isBouncerShowing = mPrimaryBouncerIsOrWillBeShowing || mUdfpsBouncerShowing;
+ final boolean isBouncerShowing =
+ mPrimaryBouncerIsOrWillBeShowing || mAlternateBouncerShowing;
return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested())
&& (mDeviceInteractive || flags.temporaryAndRenewable())
&& (isBouncerShowing || flags.dismissKeyguardRequested());
@@ -1311,7 +1314,8 @@
}
public boolean getUserHasTrust(int userId) {
- return !isTrustDisabled() && mUserHasTrust.get(userId);
+ return !isTrustDisabled() && mUserHasTrust.get(userId)
+ && isUnlockingWithTrustAgentAllowed();
}
/**
@@ -1319,12 +1323,19 @@
*/
public boolean getUserUnlockedWithBiometric(int userId) {
BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
- BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated
&& isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric);
- boolean faceAllowed = face != null && face.mAuthenticated
+ return fingerprintAllowed || getUserUnlockedWithFace(userId);
+ }
+
+
+ /**
+ * Returns whether the user is unlocked with face.
+ */
+ public boolean getUserUnlockedWithFace(int userId) {
+ BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
+ return face != null && face.mAuthenticated
&& isUnlockingWithBiometricAllowed(face.mIsStrongBiometric);
- return fingerprintAllowed || faceAllowed;
}
/**
@@ -1399,6 +1410,10 @@
return mUserTrustIsUsuallyManaged.get(userId);
}
+ private boolean isUnlockingWithTrustAgentAllowed() {
+ return isUnlockingWithBiometricAllowed(true);
+ }
+
public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
// StrongAuthTracker#isUnlockingWithBiometricAllowed includes
// STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
@@ -1534,7 +1549,7 @@
FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED);
if (mAssistantVisible) {
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.ASSISTANT,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT,
"assistant",
false);
}
@@ -1664,7 +1679,7 @@
@Override
public void onAuthenticationFailed() {
requestActiveUnlockDismissKeyguard(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
"fingerprintFailure");
handleFingerprintAuthFailed();
}
@@ -1729,11 +1744,11 @@
public void onAuthenticationFailed() {
String reason =
mKeyguardBypassController.canBypass() ? "bypass"
- : mUdfpsBouncerShowing ? "udfpsBouncer"
+ : mAlternateBouncerShowing ? "alternateBouncer"
: mPrimaryBouncerFullyShown ? "bouncer"
: "udfpsFpDown";
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
"faceFailure-" + reason);
handleFaceAuthFailed();
@@ -1760,7 +1775,7 @@
if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) {
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
"faceError-" + errMsgId);
}
}
@@ -1772,7 +1787,7 @@
if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
acquireInfo)) {
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
"faceAcquireInfo-" + acquireInfo);
}
}
@@ -1912,8 +1927,11 @@
FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason);
updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_STARTED_WAKING_UP);
- requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "wakingUp - "
- + PowerManager.wakeReasonToString(pmWakeReason));
+ requestActiveUnlock(
+ mActiveUnlockConfig.isWakeupConsideredUnlockIntent(pmWakeReason)
+ ? ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT
+ : ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE,
+ "wakingUp - " + PowerManager.wakeReasonToString(pmWakeReason));
} else {
mLogger.logSkipUpdateFaceListeningOnWakeup(pmWakeReason);
}
@@ -2477,7 +2495,7 @@
mAuthInterruptActive = active;
updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD);
- requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "onReach");
+ requestActiveUnlock(ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE, "onReach");
}
/**
@@ -2547,7 +2565,7 @@
* Attempts to trigger active unlock from trust agent.
*/
private void requestActiveUnlock(
- @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ @NonNull ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin,
String reason,
boolean dismissKeyguard
) {
@@ -2558,7 +2576,7 @@
final boolean allowRequest =
mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(requestOrigin);
- if (requestOrigin == ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE
+ if (requestOrigin == ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE
&& !allowRequest && mActiveUnlockConfig.isActiveUnlockEnabled()) {
// instead of requesting the active unlock, initiate the unlock
initiateActiveUnlock(reason);
@@ -2577,7 +2595,7 @@
* Only dismisses the keyguard under certain conditions.
*/
public void requestActiveUnlock(
- @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ @NonNull ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin,
String extraReason
) {
final boolean canFaceBypass = isFaceEnrolled() && mKeyguardBypassController != null
@@ -2585,7 +2603,7 @@
requestActiveUnlock(
requestOrigin,
extraReason, canFaceBypass
- || mUdfpsBouncerShowing
+ || mAlternateBouncerShowing
|| mPrimaryBouncerFullyShown
|| mAuthController.isUdfpsFingerDown());
}
@@ -2594,7 +2612,7 @@
* Attempts to trigger active unlock from trust agent with a request to dismiss the keyguard.
*/
public void requestActiveUnlockDismissKeyguard(
- @NonNull ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN requestOrigin,
+ @NonNull ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin,
String extraReason
) {
requestActiveUnlock(
@@ -2603,23 +2621,23 @@
}
/**
- * Whether the UDFPS bouncer is showing.
+ * Whether the alternate bouncer is showing.
*/
- public void setUdfpsBouncerShowing(boolean showing) {
- mUdfpsBouncerShowing = showing;
- if (mUdfpsBouncerShowing) {
+ public void setAlternateBouncerShowing(boolean showing) {
+ mAlternateBouncerShowing = showing;
+ if (mAlternateBouncerShowing) {
updateFaceListeningState(BIOMETRIC_ACTION_START,
FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN);
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
- "udfpsBouncer");
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
+ "alternateBouncer");
}
}
private boolean shouldTriggerActiveUnlock() {
// Triggers:
final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
- final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mUdfpsBouncerShowing
+ final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mAlternateBouncerShowing
|| (isKeyguardVisible() && !mGoingToSleep
&& mStatusBarState != StatusBarState.SHADE_LOCKED);
@@ -2803,7 +2821,6 @@
final boolean isPostureAllowedForFaceAuth =
mConfigFaceAuthSupportedPosture == 0 /* DEVICE_POSTURE_UNKNOWN */ ? true
: (mPostureState == mConfigFaceAuthSupportedPosture);
-
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
final boolean shouldListen =
@@ -2813,11 +2830,11 @@
|| awakeKeyguard
|| shouldListenForFaceAssistant
|| isUdfpsFingerDown
- || mUdfpsBouncerShowing)
+ || mAlternateBouncerShowing)
&& !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded
&& !mKeyguardGoingAway && biometricEnabledForUser
&& faceAuthAllowedOrDetectionIsNeeded && mIsPrimaryUser
- && (!mSecureCameraLaunched || mOccludingAppRequestingFace)
+ && (!mSecureCameraLaunched || mAlternateBouncerShowing)
&& faceAndFpNotAuthenticated
&& !mGoingToSleep
&& isPostureAllowedForFaceAuth;
@@ -2828,6 +2845,7 @@
System.currentTimeMillis(),
user,
shouldListen,
+ mAlternateBouncerShowing,
mAuthInterruptActive,
biometricEnabledForUser,
mPrimaryBouncerFullyShown,
@@ -2845,7 +2863,6 @@
mSecureCameraLaunched,
supportsDetect,
mSwitchingUser,
- mUdfpsBouncerShowing,
isUdfpsFingerDown,
userNotTrustedOrDetectionIsNeeded));
@@ -2933,9 +2950,26 @@
// This would need to be updated for multi-sensor devices
final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
&& mFaceSensorProperties.get(0).supportsFaceDetection;
- if (!isUnlockingWithBiometricAllowed(FACE) && supportsFaceDetection) {
- mLogger.v("startListeningForFace - detect");
- mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
+ if (!isUnlockingWithBiometricAllowed(FACE)) {
+ final boolean udfpsFingerprintAuthRunning = isUdfpsSupported()
+ && isFingerprintDetectionRunning();
+ if (supportsFaceDetection && !udfpsFingerprintAuthRunning) {
+ // Run face detection. (If a face is detected, show the bouncer.)
+ mLogger.v("startListeningForFace - detect");
+ mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
+ } else {
+ // Don't run face detection. Instead, inform the user
+ // face auth is unavailable and how to proceed.
+ // (ie: "Use fingerprint instead" or "Swipe up to open")
+ mLogger.v("Ignoring \"startListeningForFace - detect\". "
+ + "Informing user face isn't available.");
+ mFaceAuthenticationCallback.onAuthenticationHelp(
+ BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+ mContext.getResources().getString(
+ R.string.keyguard_face_unlock_unavailable)
+ );
+ return;
+ }
} else {
mLogger.v("startListeningForFace - authenticate");
final boolean isBypassEnabled = mKeyguardBypassController != null
@@ -2966,6 +3000,23 @@
return isUnlockWithFacePossible(userId) || isUnlockWithFingerprintPossible(userId);
}
+ /**
+ * If non-strong (i.e. weak or convenience) biometrics hardware is available, not disabled, and
+ * user has enrolled templates. This does NOT check if the device is encrypted or in lockdown.
+ *
+ * @param userId User that's trying to unlock.
+ * @return {@code true} if possible.
+ */
+ public boolean isUnlockingWithNonStrongBiometricsPossible(int userId) {
+ // This assumes that there is at most one face and at most one fingerprint sensor
+ return (mFaceManager != null && !mFaceSensorProperties.isEmpty()
+ && (mFaceSensorProperties.get(0).sensorStrength != SensorProperties.STRENGTH_STRONG)
+ && isUnlockWithFacePossible(userId))
+ || (mFpm != null && !mFingerprintSensorProperties.isEmpty()
+ && (mFingerprintSensorProperties.get(0).sensorStrength
+ != SensorProperties.STRENGTH_STRONG) && isUnlockWithFingerprintPossible(userId));
+ }
+
@SuppressLint("MissingPermission")
@VisibleForTesting
boolean isUnlockWithFingerprintPossible(int userId) {
@@ -3217,6 +3268,24 @@
}
/**
+ * @param data the weather data (temp, conditions, unit) for weather clock to use
+ */
+ public void sendWeatherData(Weather data) {
+ mHandler.post(()-> {
+ handleWeatherDataUpdate(data); });
+ }
+
+ private void handleWeatherDataUpdate(Weather data) {
+ Assert.isMainThread();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onWeatherDataChanged(data);
+ }
+ }
+ }
+
+ /**
* Handle {@link #MSG_BATTERY_UPDATE}
*/
private void handleBatteryUpdate(BatteryStatus status) {
@@ -3398,7 +3467,7 @@
if (wasPrimaryBouncerFullyShown != mPrimaryBouncerFullyShown) {
if (mPrimaryBouncerFullyShown) {
requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
"bouncerFullyShown");
}
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -3921,7 +3990,7 @@
pw.println(" mPrimaryBouncerIsOrWillBeShowing="
+ mPrimaryBouncerIsOrWillBeShowing);
pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState));
- pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
+ pw.println(" mAlternateBouncerShowing=" + mAlternateBouncerShowing);
} else if (isSfpsSupported()) {
pw.println(" sfpsEnrolled=" + isSfpsEnrolled());
pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index e6b9ac8..0da799e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -23,6 +23,7 @@
import androidx.annotation.Nullable;
import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.plugins.Weather;
import com.android.systemui.statusbar.KeyguardIndicationController;
import java.util.TimeZone;
@@ -58,6 +59,11 @@
public void onTimeFormatChanged(String timeFormat) { }
/**
+ * Called when receive new weather data.
+ */
+ public void onWeatherDataChanged(Weather data) { }
+
+ /**
* Called when the carrier PLMN or SPN changes.
*/
public void onRefreshCarrierInfo() { }
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 676979c..b1a83fb 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -18,13 +18,12 @@
import android.content.Context;
import android.content.res.Resources;
-import android.os.Handler;
-import android.os.UserHandle;
import android.view.LayoutInflater;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -34,6 +33,8 @@
import dagger.Module;
import dagger.Provides;
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.CoroutineScope;
/** Dagger Module for clocks. */
@Module
@@ -44,17 +45,23 @@
public static ClockRegistry getClockRegistry(
@Application Context context,
PluginManager pluginManager,
- @Main Handler handler,
+ @Application CoroutineScope scope,
+ @Main CoroutineDispatcher mainDispatcher,
+ @Background CoroutineDispatcher bgDispatcher,
FeatureFlags featureFlags,
@Main Resources resources,
LayoutInflater layoutInflater) {
- return new ClockRegistry(
+ ClockRegistry registry = new ClockRegistry(
context,
pluginManager,
- handler,
+ scope,
+ mainDispatcher,
+ bgDispatcher,
featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
- UserHandle.USER_ALL,
+ /* handleAllUsers= */ true,
new DefaultClockProvider(context, layoutInflater, resources),
context.getString(R.string.lockscreen_clock_id_fallback));
+ registry.registerListeners();
+ return registry;
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 2c7eceb..379c78a 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -16,9 +16,11 @@
package com.android.keyguard.logging
+import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController
import com.android.systemui.log.dagger.KeyguardLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.KeyguardIndicationController
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -76,4 +78,46 @@
{ "$str1 msgId: $str2 msg: $str3" }
)
}
+
+ fun logUpdateDeviceEntryIndication(
+ animate: Boolean,
+ visible: Boolean,
+ dozing: Boolean,
+ ) {
+ buffer.log(
+ KeyguardIndicationController.TAG,
+ LogLevel.DEBUG,
+ {
+ bool1 = animate
+ bool2 = visible
+ bool3 = dozing
+ },
+ { "updateDeviceEntryIndication animate:$bool1 visible:$bool2 dozing $bool3" }
+ )
+ }
+
+ fun logKeyguardSwitchIndication(
+ type: Int,
+ message: String?,
+ ) {
+ buffer.log(
+ KeyguardIndicationController.TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = type
+ str1 = message
+ },
+ { "keyguardSwitchIndication ${getKeyguardSwitchIndicationNonSensitiveLog(int1, str1)}" }
+ )
+ }
+
+ fun getKeyguardSwitchIndicationNonSensitiveLog(type: Int, message: String?): String {
+ // only show the battery string. other strings may contain sensitive info
+ return if (type == KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY) {
+ "type=${KeyguardIndicationRotateTextViewController.indicationTypeToString(type)}" +
+ " message=$message"
+ } else {
+ "type=${KeyguardIndicationRotateTextViewController.indicationTypeToString(type)}"
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 201a1d9..c414c08 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -372,7 +372,7 @@
}
fun logUserRequestedUnlock(
- requestOrigin: ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN,
+ requestOrigin: ActiveUnlockConfig.ActiveUnlockRequestOrigin,
reason: String?,
dismissKeyguard: Boolean
) {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index c1c7f2d..fb65588 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -124,16 +124,7 @@
};
private final ScreenDecorationsLogger mLogger;
- private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
- @Override
- public void onFaceSensorLocationChanged() {
- mLogger.onSensorLocationChanged();
- if (mExecutor != null) {
- mExecutor.execute(
- () -> updateOverlayProviderViews(new Integer[]{mFaceScanningViewId}));
- }
- }
- };
+ private final AuthController mAuthController;
private DisplayTracker mDisplayTracker;
@VisibleForTesting
@@ -340,9 +331,22 @@
mFaceScanningFactory = faceScanningFactory;
mFaceScanningViewId = com.android.systemui.R.id.face_scanning_anim;
mLogger = logger;
- authController.addCallback(mAuthControllerCallback);
+ mAuthController = authController;
}
+
+ private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+ @Override
+ public void onFaceSensorLocationChanged() {
+ mLogger.onSensorLocationChanged();
+ if (mExecutor != null) {
+ mExecutor.execute(
+ () -> updateOverlayProviderViews(
+ new Integer[]{mFaceScanningViewId}));
+ }
+ }
+ };
+
@Override
public void start() {
if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
@@ -353,6 +357,7 @@
mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler);
mExecutor.execute(this::startOnScreenDecorationsThread);
mDotViewController.setUiExecutor(mExecutor);
+ mAuthController.addCallback(mAuthControllerCallback);
}
private boolean isPrivacyDotEnabled() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 1f6f6d9..3ea3cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -18,6 +18,7 @@
import android.annotation.RawRes
import android.content.Context
+import android.content.Context.FINGERPRINT_SERVICE
import android.content.res.Configuration
import android.hardware.fingerprint.FingerprintManager
import android.view.DisplayInfo
@@ -66,16 +67,11 @@
R.dimen.biometric_dialog_fingerprint_icon_width),
context.resources.getDimensionPixelSize(
R.dimen.biometric_dialog_fingerprint_icon_height))
- var sideFps = false
- (context.getSystemService(Context.FINGERPRINT_SERVICE)
- as FingerprintManager?)?.let { fpm ->
- for (prop in fpm.sensorPropertiesInternal) {
- if (prop.isAnySidefpsType) {
- sideFps = true
- }
- }
- }
- isSideFps = sideFps
+ isSideFps =
+ (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm ->
+ fpm.sensorPropertiesInternal.any { it.isAnySidefpsType }
+ } ?: false
+ preloadAssets(context)
val displayInfo = DisplayInfo()
context.display?.getDisplayInfo(displayInfo)
if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
@@ -329,6 +325,40 @@
else -> null
}
+ private fun preloadAssets(context: Context) {
+ if (isSideFps) {
+ cacheLottieAssetsInContext(
+ context,
+ R.raw.biometricprompt_fingerprint_to_error_landscape,
+ R.raw.biometricprompt_folded_base_bottomright,
+ R.raw.biometricprompt_folded_base_default,
+ R.raw.biometricprompt_folded_base_topleft,
+ R.raw.biometricprompt_landscape_base,
+ R.raw.biometricprompt_portrait_base_bottomright,
+ R.raw.biometricprompt_portrait_base_topleft,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
+ R.raw.biometricprompt_symbol_error_to_success_landscape,
+ R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
+ R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+ )
+ } else {
+ cacheLottieAssetsInContext(
+ context,
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
+ R.raw.fingerprint_dialogue_error_to_success_lottie,
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+ )
+ }
+ }
+
override fun onFoldUpdated(isFolded: Boolean) {
isDeviceFolded = isFolded
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
rename to packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
index b3b6fa2..d6ad4da 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
@@ -24,14 +24,15 @@
import android.graphics.drawable.Drawable
import android.util.Log
import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieCompositionFactory
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
private const val TAG = "AuthIconController"
/** Controller for animating the BiometricPrompt icon/affordance. */
abstract class AuthIconController(
- protected val context: Context,
- protected val iconView: LottieAnimationView
+ protected val context: Context,
+ protected val iconView: LottieAnimationView
) : Animatable2.AnimationCallback() {
/** If this controller should ignore events and pause. */
@@ -94,4 +95,12 @@
open fun handleAnimationEnd(drawable: Drawable) {}
open fun onConfigurationChanged(newConfig: Configuration) {}
+
+ // TODO(b/251476085): Migrate this to an extension at the appropriate level?
+ /** Load the given [rawResources] immediately so they are cached for use in the [context]. */
+ protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {
+ for (res in rawResources) {
+ LottieCompositionFactory.fromRawRes(context, res)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 53ab6d6..58b230f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -88,6 +88,7 @@
rippleShader.color = 0xffffffff.toInt() // default color
rippleShader.rawProgress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
+ setupRippleFadeParams()
ripplePaint.shader = rippleShader
dwellShader.color = 0xffffffff.toInt() // default color
@@ -294,7 +295,6 @@
)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
- rippleShader.rippleFill = false
drawRipple = true
visibility = VISIBLE
}
@@ -339,6 +339,18 @@
)
}
+ private fun setupRippleFadeParams() {
+ with(rippleShader) {
+ baseRingFadeParams.fadeOutStart = RippleShader.DEFAULT_BASE_RING_FADE_OUT_START
+ baseRingFadeParams.fadeOutEnd = RippleShader.DEFAULT_FADE_OUT_END
+
+ centerFillFadeParams.fadeInStart = RippleShader.DEFAULT_FADE_IN_START
+ centerFillFadeParams.fadeInEnd = RippleShader.DEFAULT_CENTER_FILL_FADE_IN_END
+ centerFillFadeParams.fadeOutStart = RippleShader.DEFAULT_CENTER_FILL_FADE_OUT_START
+ centerFillFadeParams.fadeOutEnd = RippleShader.DEFAULT_CENTER_FILL_FADE_OUT_END
+ }
+ }
+
override fun onDraw(canvas: Canvas?) {
// To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
// the active effect area. Values here should be kept in sync with the
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index fb0c0a6..5ca36ab 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -72,7 +72,7 @@
height = WindowManager.LayoutParams.MATCH_PARENT
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
format = PixelFormat.TRANSLUCENT
- type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
+ type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
fitInsetsTypes = 0 // Ignore insets from all system bars
title = "Wired Charging Animation"
flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/CoroutineResult.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/CoroutineResult.kt
new file mode 100644
index 0000000..b973667
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/CoroutineResult.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.common.coroutine
+
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.ensureActive
+import kotlinx.coroutines.withTimeout
+
+/**
+ * Calls the specified function [block] and returns its encapsulated result if invocation was
+ * successful, catching any [Throwable] exception that was thrown from the block function execution
+ * and encapsulating it as a failure.
+ *
+ * Unlike [runCatching], [suspendRunCatching] does not break structured concurrency by rethrowing
+ * any [CancellationException].
+ *
+ * **Heads-up:** [TimeoutCancellationException] extends [CancellationException] but catching it does
+ * not breaks structured concurrency and therefore, will not be rethrown. Therefore, you can use
+ * [suspendRunCatching] with [withTimeout], and handle any timeout gracefully.
+ *
+ * @see <a href="https://github.com/Kotlin/kotlinx.coroutines/issues/1814">link</a>
+ */
+suspend inline fun <T> suspendRunCatching(crossinline block: suspend () -> T): Result<T> =
+ try {
+ Result.success(block())
+ } catch (e: Throwable) {
+ // Ensures the try-catch block will not break structured concurrency.
+ currentCoroutineContext().ensureActive()
+ Result.failure(e)
+ }
+
+/**
+ * Calls the specified function [block] and returns its encapsulated result if invocation was
+ * successful, catching any [Throwable] exception that was thrown from the block function execution
+ * and encapsulating it as a failure.
+ *
+ * Unlike [runCatching], [suspendRunCatching] does not break structured concurrency by rethrowing
+ * any [CancellationException].
+ *
+ * **Heads-up:** [TimeoutCancellationException] extends [CancellationException] but catching it does
+ * not breaks structured concurrency and therefore, will not be rethrown. Therefore, you can use
+ * [suspendRunCatching] with [withTimeout], and handle any timeout gracefully.
+ *
+ * @see <a href="https://github.com/Kotlin/kotlinx.coroutines/issues/1814">link</a>
+ */
+suspend inline fun <T, R> T.suspendRunCatching(crossinline block: suspend T.() -> R): Result<R> =
+ // Overload with a `this` receiver, matches with `kotlin.runCatching` functions.
+ // Qualified name needs to be used to avoid a recursive call.
+ com.android.systemui.common.coroutine.suspendRunCatching { block(this) }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 27466d4..7509a8a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -19,11 +19,11 @@
import android.content.Context
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.UserTracker
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 4bb5d04..d1c34a8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -31,6 +31,7 @@
import android.app.UiModeManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
+import android.app.ambientcontext.AmbientContextManager;
import android.app.job.JobScheduler;
import android.app.role.RoleManager;
import android.app.smartspace.SmartspaceManager;
@@ -79,6 +80,7 @@
import android.safetycenter.SafetyCenterManager;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
+import android.service.vr.IVrManager;
import android.telecom.TelecomManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
@@ -146,6 +148,13 @@
return Optional.ofNullable(context.getSystemService(SystemUpdateManager.class));
}
+ @Provides
+ @Nullable
+ @Singleton
+ static AmbientContextManager provideAmbientContextManager(Context context) {
+ return context.getSystemService(AmbientContextManager.class);
+ }
+
/** */
@Provides
public AmbientDisplayConfiguration provideAmbientDisplayConfiguration(Context context) {
@@ -261,6 +270,13 @@
@Provides
@Singleton
@Nullable
+ static IVrManager provideIVrManager() {
+ return IVrManager.Stub.asInterface(ServiceManager.getService(Context.VR_SERVICE));
+ }
+
+ @Provides
+ @Singleton
+ @Nullable
static FaceManager provideFaceManager(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)) {
return context.getSystemService(FaceManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemPropertiesFlagsModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemPropertiesFlagsModule.kt
new file mode 100644
index 0000000..c6f833b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemPropertiesFlagsModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dagger
+
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags
+import dagger.Module
+import dagger.Provides
+
+/** A module which provides access to the default [SystemUiSystemPropertiesFlags.FlagResolver] */
+@Module
+object SystemPropertiesFlagsModule {
+ /** provide the default FlagResolver. */
+ @Provides
+ fun provideFlagResolver(): SystemUiSystemPropertiesFlags.FlagResolver =
+ SystemUiSystemPropertiesFlags.getResolver()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 6274a26..c3ee9be 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -139,6 +139,7 @@
DemoModeModule.class,
FalsingModule.class,
FlagsModule.class,
+ SystemPropertiesFlagsModule.class,
FooterActionsModule.class,
LogModule.class,
MediaProjectionModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 4bac697..58fe094 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -40,7 +40,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
import org.jetbrains.annotations.NotNull;
@@ -74,7 +73,6 @@
private final FlagManager mFlagManager;
private final Context mContext;
private final GlobalSettings mGlobalSettings;
- private final SecureSettings mSecureSettings;
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
private final ServerFlagReader mServerFlagReader;
@@ -87,8 +85,9 @@
private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
new ServerFlagReader.ChangeListener() {
@Override
- public void onChange() {
- mRestarter.restartSystemUI();
+ public void onChange(Flag<?> flag) {
+ mRestarter.restartSystemUI(
+ "Server flag change: " + flag.getNamespace() + "." + flag.getName());
}
};
@@ -97,7 +96,6 @@
FlagManager flagManager,
Context context,
GlobalSettings globalSettings,
- SecureSettings secureSettings,
SystemPropertiesHelper systemProperties,
@Main Resources resources,
ServerFlagReader serverFlagReader,
@@ -106,7 +104,6 @@
mFlagManager = flagManager;
mContext = context;
mGlobalSettings = globalSettings;
- mSecureSettings = secureSettings;
mResources = resources;
mSystemProperties = systemProperties;
mServerFlagReader = serverFlagReader;
@@ -119,7 +116,8 @@
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG);
filter.addAction(ACTION_GET_FLAGS);
- mFlagManager.setOnSettingsChangedAction(this::restartSystemUI);
+ mFlagManager.setOnSettingsChangedAction(
+ suppressRestart -> restartSystemUI(suppressRestart, "Settings changed"));
mFlagManager.setClearCacheAction(this::removeFromCache);
mContext.registerReceiver(mReceiver, filter, null, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
@@ -233,6 +231,10 @@
Boolean result = readBooleanFlagOverride(flag.getName());
if (result == null) {
result = readBooleanFlagOverride(flag.getId());
+ if (result != null) {
+ // Move overrides from id to name
+ setFlagValueInternal(flag.getName(), result, BooleanFlagSerializer.INSTANCE);
+ }
}
boolean hasServerOverride = mServerFlagReader.hasOverride(
flag.getNamespace(), flag.getName());
@@ -305,25 +307,38 @@
requireNonNull(value, "Cannot set a null value");
T currentValue = readFlagValueInternal(name, serializer);
if (Objects.equals(currentValue, value)) {
- Log.i(TAG, "Flag id " + name + " is already " + value);
+ Log.i(TAG, "Flag \"" + name + "\" is already " + value);
return;
}
+ setFlagValueInternal(name, value, serializer);
+ Log.i(TAG, "Set flag \"" + name + "\" to " + value);
+ removeFromCache(name);
+ mFlagManager.dispatchListenersAndMaybeRestart(
+ name,
+ suppressRestart -> restartSystemUI(
+ suppressRestart, "Flag \"" + name + "\" changed to " + value));
+ }
+
+ private <T> void setFlagValueInternal(
+ String name, @NonNull T value, FlagSerializer<T> serializer) {
final String data = serializer.toSettingsData(value);
if (data == null) {
- Log.w(TAG, "Failed to set id " + name + " to " + value);
+ Log.w(TAG, "Failed to set flag " + name + " to " + value);
return;
}
mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), data,
UserHandle.USER_CURRENT);
- Log.i(TAG, "Set id " + name + " to " + value);
- removeFromCache(name);
- mFlagManager.dispatchListenersAndMaybeRestart(name, this::restartSystemUI);
}
<T> void eraseFlag(Flag<T> flag) {
if (flag instanceof SysPropFlag) {
- mSystemProperties.erase(((SysPropFlag<T>) flag).getName());
- dispatchListenersAndMaybeRestart(flag.getName(), this::restartAndroid);
+ mSystemProperties.erase(flag.getName());
+ dispatchListenersAndMaybeRestart(
+ flag.getName(),
+ suppressRestart -> restartSystemUI(
+ suppressRestart,
+ "SysProp Flag \"" + flag.getNamespace() + "."
+ + flag.getName() + "\" reset to default."));
} else {
eraseFlag(flag.getName());
}
@@ -333,7 +348,10 @@
private void eraseFlag(String name) {
eraseInternal(name);
removeFromCache(name);
- dispatchListenersAndMaybeRestart(name, this::restartSystemUI);
+ dispatchListenersAndMaybeRestart(
+ name,
+ suppressRestart -> restartSystemUI(
+ suppressRestart, "Flag \"" + name + "\" reset to default"));
}
private void dispatchListenersAndMaybeRestart(String name, Consumer<Boolean> restartAction) {
@@ -367,20 +385,20 @@
mFlagManager.removeListener(listener);
}
- private void restartSystemUI(boolean requestSuppress) {
+ private void restartSystemUI(boolean requestSuppress, String reason) {
if (requestSuppress) {
Log.i(TAG, "SystemUI Restart Suppressed");
return;
}
- mRestarter.restartSystemUI();
+ mRestarter.restartSystemUI(reason);
}
- private void restartAndroid(boolean requestSuppress) {
+ private void restartAndroid(boolean requestSuppress, String reason) {
if (requestSuppress) {
Log.i(TAG, "Android Restart Suppressed");
return;
}
- mRestarter.restartAndroid();
+ mRestarter.restartAndroid(reason);
}
void setBooleanFlagInternal(Flag<?> flag, boolean value) {
@@ -391,8 +409,11 @@
} else if (flag instanceof SysPropBooleanFlag) {
// Store SysProp flags in SystemProperties where they can read by outside parties.
mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value);
- dispatchListenersAndMaybeRestart(flag.getName(),
- FeatureFlagsDebug.this::restartAndroid);
+ dispatchListenersAndMaybeRestart(
+ flag.getName(),
+ suppressRestart -> restartSystemUI(
+ suppressRestart,
+ "Flag \"" + flag.getName() + "\" changed to " + value));
} else {
throw new IllegalArgumentException("Unknown flag type");
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
index 069e612..a6956a4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
@@ -29,6 +29,7 @@
) : Restarter {
private var androidRestartRequested = false
+ private var pendingReason = ""
val observer =
object : WakefulnessLifecycle.Observer {
@@ -38,18 +39,20 @@
}
}
- override fun restartSystemUI() {
+ override fun restartSystemUI(reason: String) {
Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
- scheduleRestart()
+ Log.i(FeatureFlagsDebug.TAG, reason)
+ scheduleRestart(reason)
}
- override fun restartAndroid() {
+ override fun restartAndroid(reason: String) {
Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
androidRestartRequested = true
- scheduleRestart()
+ scheduleRestart(reason)
}
- fun scheduleRestart() {
+ fun scheduleRestart(reason: String) {
+ pendingReason = reason
if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
restartNow()
} else {
@@ -59,9 +62,9 @@
private fun restartNow() {
if (androidRestartRequested) {
- systemExitRestarter.restartAndroid()
+ systemExitRestarter.restartAndroid(pendingReason)
} else {
- systemExitRestarter.restartSystemUI()
+ systemExitRestarter.restartSystemUI(pendingReason)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 7e14237..9859ff6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -57,8 +57,9 @@
private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
new ServerFlagReader.ChangeListener() {
@Override
- public void onChange() {
- mRestarter.restartSystemUI();
+ public void onChange(Flag<?> flag) {
+ mRestarter.restartSystemUI(
+ "Server flag change: " + flag.getNamespace() + "." + flag.getName());
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
index 7ff3876..c08266c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -36,41 +36,43 @@
) : Restarter {
var listenersAdded = false
var pendingRestart: Runnable? = null
+ private var pendingReason = ""
var androidRestartRequested = false
val observer =
object : WakefulnessLifecycle.Observer {
override fun onFinishedGoingToSleep() {
- scheduleRestart()
+ scheduleRestart(pendingReason)
}
}
val batteryCallback =
object : BatteryController.BatteryStateChangeCallback {
override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- scheduleRestart()
+ scheduleRestart(pendingReason)
}
}
- override fun restartSystemUI() {
+ override fun restartSystemUI(reason: String) {
Log.d(
FeatureFlagsDebug.TAG,
"SystemUI Restart requested. Restarting when plugged in and idle."
)
- scheduleRestart()
+ scheduleRestart(reason)
}
- override fun restartAndroid() {
+ override fun restartAndroid(reason: String) {
Log.d(
FeatureFlagsDebug.TAG,
"Android Restart requested. Restarting when plugged in and idle."
)
androidRestartRequested = true
- scheduleRestart()
+ scheduleRestart(reason)
}
- private fun scheduleRestart() {
+ private fun scheduleRestart(reason: String) {
// Don't bother adding listeners twice.
+ pendingReason = reason
if (!listenersAdded) {
listenersAdded = true
wakefulnessLifecycle.addObserver(observer)
@@ -91,9 +93,9 @@
private fun restartNow() {
Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
if (androidRestartRequested) {
- systemExitRestarter.restartAndroid()
+ systemExitRestarter.restartAndroid(pendingReason)
} else {
- systemExitRestarter.restartSystemUI()
+ systemExitRestarter.restartSystemUI(pendingReason)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 8bacf4e..0078928 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -68,7 +68,7 @@
@JvmField val DISABLE_FSI = unreleasedFlag(265804648, "disable_fsi")
// TODO(b/254512538): Tracking Bug
- val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply", teamfood = true)
+ val INSTANT_VOICE_REPLY = releasedFlag(111, "instant_voice_reply")
// TODO(b/254512425): Tracking Bug
val NOTIFICATION_MEMORY_MONITOR_ENABLED =
@@ -85,7 +85,7 @@
// TODO(b/259217907)
@JvmField
val NOTIFICATION_GROUP_DISMISSAL_ANIMATION =
- unreleasedFlag(259217907, "notification_group_dismissal_animation", teamfood = true)
+ releasedFlag(259217907, "notification_group_dismissal_animation")
// TODO(b/257506350): Tracking Bug
@JvmField val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
@@ -208,9 +208,7 @@
unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = false)
// TODO(b/242908637): Tracking Bug
- @JvmField
- val WALLPAPER_FULLSCREEN_PREVIEW =
- unreleasedFlag(227, "wallpaper_fullscreen_preview", teamfood = true)
+ @JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag(227, "wallpaper_fullscreen_preview")
/** Whether the long-press gesture to open wallpaper picker is enabled. */
// TODO(b/266242192): Tracking Bug
@@ -312,9 +310,7 @@
val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag(803, "screen_contents_translation")
// 804 - monochromatic themes
- @JvmField
- val MONOCHROMATIC_THEMES =
- sysPropBooleanFlag(804, "persist.sysui.monochromatic", default = false)
+ @JvmField val MONOCHROMATIC_THEME = unreleasedFlag(804, "monochromatic", teamfood = true)
// 900 - media
// TODO(b/254512697): Tracking Bug
@@ -344,15 +340,13 @@
@JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise")
// TODO(b/263272731): Tracking Bug
- val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE =
- unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true)
+ val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple")
// TODO(b/263512203): Tracking Bug
val MEDIA_EXPLICIT_INDICATOR = unreleasedFlag(911, "media_explicit_indicator", teamfood = true)
// TODO(b/265813373): Tracking Bug
- val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE =
- unreleasedFlag(912, "media_ttt_dismiss_gesture", teamfood = true)
+ val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE = releasedFlag(912, "media_ttt_dismiss_gesture")
// TODO(b/266157412): Tracking Bug
val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
@@ -478,7 +472,7 @@
// TODO(b/254512728): Tracking Bug
@JvmField
- val NEW_BACK_AFFORDANCE = unreleasedFlag(1203, "new_back_affordance", teamfood = false)
+ val NEW_BACK_AFFORDANCE = unreleasedFlag(1203, "new_back_affordance", teamfood = true)
// TODO(b/255854141): Tracking Bug
@JvmField
@@ -497,7 +491,7 @@
// TODO(b/238475428): Tracking Bug
@JvmField
val WM_SHADE_ALLOW_BACK_GESTURE =
- unreleasedFlag(1207, "persist.wm.debug.shade_allow_back_gesture", teamfood = false)
+ sysPropBooleanFlag(1207, "persist.wm.debug.shade_allow_back_gesture", default = false)
// TODO(b/238475428): Tracking Bug
@JvmField
@@ -512,14 +506,14 @@
// 1300 - screenshots
// TODO(b/254513155): Tracking Bug
@JvmField
- val SCREENSHOT_WORK_PROFILE_POLICY =
- unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
+ val SCREENSHOT_WORK_PROFILE_POLICY = releasedFlag(1301, "screenshot_work_profile_policy")
// TODO(b/264916608): Tracking Bug
- @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata")
+ @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata", teamfood = true)
// TODO(b/266955521): Tracking bug
- @JvmField val SCREENSHOT_DETECTION = unreleasedFlag(1303, "screenshot_detection")
+ @JvmField
+ val SCREENSHOT_DETECTION = unreleasedFlag(1303, "screenshot_detection", teamfood = true)
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
@@ -531,16 +525,23 @@
// 1500 - chooser aka sharesheet
// TODO(b/254512507): Tracking Bug
- val CHOOSER_UNBUNDLED = unreleasedFlag(1500, "chooser_unbundled", teamfood = true)
+ val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled")
// TODO(b/266983432) Tracking Bug
- val SHARESHEET_CUSTOM_ACTIONS = unreleasedFlag(1501, "sharesheet_custom_actions")
+ val SHARESHEET_CUSTOM_ACTIONS =
+ unreleasedFlag(1501, "sharesheet_custom_actions", teamfood = true)
// TODO(b/266982749) Tracking Bug
- val SHARESHEET_RESELECTION_ACTION = unreleasedFlag(1502, "sharesheet_reselection_action")
+ val SHARESHEET_RESELECTION_ACTION =
+ unreleasedFlag(1502, "sharesheet_reselection_action", teamfood = true)
// TODO(b/266983474) Tracking Bug
- val SHARESHEET_IMAGE_AND_TEXT_PREVIEW = unreleasedFlag(1503, "sharesheet_image_text_preview")
+ val SHARESHEET_IMAGE_AND_TEXT_PREVIEW =
+ unreleasedFlag(1503, "sharesheet_image_text_preview", teamfood = true)
+
+ // TODO(b/267355521) Tracking Bug
+ val SHARESHEET_SCROLLABLE_IMAGE_PREVIEW =
+ unreleasedFlag(1504, "sharesheet_scrollable_image_preview")
// 1600 - accessibility
@JvmField
@@ -582,7 +583,7 @@
@JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
// 2300 - stylus
- @JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
+ @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used")
@JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui")
@JvmField
val ENABLE_USI_BATTERY_NOTIFICATIONS = unreleasedFlag(2302, "enable_usi_battery_notifications")
@@ -619,7 +620,11 @@
// TODO(b259590361): Tracking bug
val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
- // 2600 - keyboard shortcut
+ // 2600 - keyboard
// TODO(b/259352579): Tracking Bug
@JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = unreleasedFlag(2600, "shortcut_list_search_layout")
+
+ // TODO(b/259428678): Tracking Bug
+ @JvmField
+ val KEYBOARD_BACKLIGHT_INDICATOR = unreleasedFlag(2601, "keyboard_backlight_indicator")
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
index ce8b821..9c67795 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
@@ -16,7 +16,7 @@
package com.android.systemui.flags
interface Restarter {
- fun restartSystemUI()
+ fun restartSystemUI(reason: String)
- fun restartAndroid()
+ fun restartAndroid(reason: String)
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index a02b795..e225b10 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -17,8 +17,10 @@
package com.android.systemui.flags
import android.provider.DeviceConfig
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.TestHarness
import com.android.systemui.util.DeviceConfigProxy
import dagger.Module
import dagger.Provides
@@ -35,21 +37,27 @@
fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
interface ChangeListener {
- fun onChange()
+ fun onChange(flag: Flag<*>)
}
}
class ServerFlagReaderImpl @Inject constructor(
private val namespace: String,
private val deviceConfig: DeviceConfigProxy,
- @Background private val executor: Executor
+ @Background private val executor: Executor,
+ @TestHarness private val isTestHarness: Boolean
) : ServerFlagReader {
+ private val TAG = "ServerFlagReader"
+
private val listeners =
mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
private val onPropertiesChangedListener = object : DeviceConfig.OnPropertiesChangedListener {
override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+ if (isTestHarness) {
+ Log.w(TAG, "Ignore server flag changes in Test Harness mode.")
+ }
if (properties.namespace != namespace) {
return
}
@@ -59,7 +67,7 @@
propLoop@ for (propName in properties.keyset) {
for (flag in flags) {
if (propName == getServerOverrideName(flag.id) || propName == flag.name) {
- listener.onChange()
+ listener.onChange(flag)
break@propLoop
}
}
@@ -111,10 +119,11 @@
@SysUISingleton
fun bindsReader(
deviceConfig: DeviceConfigProxy,
- @Background executor: Executor
+ @Background executor: Executor,
+ @TestHarness isTestHarness: Boolean
): ServerFlagReader {
return ServerFlagReaderImpl(
- SYSUI_NAMESPACE, deviceConfig, executor
+ SYSUI_NAMESPACE, deviceConfig, executor, isTestHarness
)
}
}
@@ -139,7 +148,7 @@
for ((listener, flags) in listeners) {
flagLoop@ for (flag in flags) {
if (name == flag.name) {
- listener.onChange()
+ listener.onChange(flag)
break@flagLoop
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
index 89daa64..46e28a7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -16,6 +16,7 @@
package com.android.systemui.flags
+import android.util.Log
import com.android.internal.statusbar.IStatusBarService
import javax.inject.Inject
@@ -24,11 +25,13 @@
constructor(
private val barService: IStatusBarService,
) : Restarter {
- override fun restartAndroid() {
+ override fun restartAndroid(reason: String) {
+ Log.d(FeatureFlagsDebug.TAG, "Restarting Android: " + reason)
barService.restart()
}
- override fun restartSystemUI() {
+ override fun restartSystemUI(reason: String) {
+ Log.d(FeatureFlagsDebug.TAG, "Restarting SystemUI: " + reason)
System.exit(0)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 9235e10..0745456 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -24,6 +24,7 @@
import androidx.annotation.IntDef;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -64,6 +65,7 @@
2000L + KeyguardIndicationTextView.Y_IN_DURATION;
private final StatusBarStateController mStatusBarStateController;
+ private final KeyguardLogger mLogger;
private final float mMaxAlpha;
private final ColorStateList mInitialTextColorState;
@@ -85,7 +87,8 @@
public KeyguardIndicationRotateTextViewController(
KeyguardIndicationTextView view,
@Main DelayableExecutor executor,
- StatusBarStateController statusBarStateController
+ StatusBarStateController statusBarStateController,
+ KeyguardLogger logger
) {
super(view);
mMaxAlpha = view.getAlpha();
@@ -93,6 +96,7 @@
mInitialTextColorState = mView != null
? mView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
mStatusBarStateController = statusBarStateController;
+ mLogger = logger;
init();
}
@@ -259,6 +263,8 @@
mLastIndicationSwitch = SystemClock.uptimeMillis();
if (!TextUtils.equals(previousMessage, mCurrMessage)
|| previousIndicationType != mCurrIndicationType) {
+ mLogger.logKeyguardSwitchIndication(type,
+ mCurrMessage != null ? mCurrMessage.toString() : null);
mView.switchIndication(mIndicationMessages.get(type));
}
@@ -352,9 +358,10 @@
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardIndicationRotatingTextViewController:");
- pw.println(" currentMessage=" + mView.getText());
+ pw.println(" currentTextViewMessage=" + mView.getText());
+ pw.println(" currentStoredMessage=" + mView.getMessage());
pw.println(" dozing:" + mIsDozing);
- pw.println(" queue:" + mIndicationQueue.toString());
+ pw.println(" queue:" + mIndicationQueue);
pw.println(" showNextIndicationRunnable:" + mShowNextIndicationRunnable);
if (hasIndications()) {
@@ -398,4 +405,40 @@
})
@Retention(RetentionPolicy.SOURCE)
public @interface IndicationType{}
+
+ /**
+ * Get human-readable string representation of the indication type.
+ */
+ public static String indicationTypeToString(@IndicationType int type) {
+ switch (type) {
+ case INDICATION_TYPE_NONE:
+ return "none";
+ case INDICATION_TYPE_DISCLOSURE:
+ return "disclosure";
+ case INDICATION_TYPE_OWNER_INFO:
+ return "owner_info";
+ case INDICATION_TYPE_LOGOUT:
+ return "logout";
+ case INDICATION_TYPE_BATTERY:
+ return "battery";
+ case INDICATION_TYPE_ALIGNMENT:
+ return "alignment";
+ case INDICATION_TYPE_TRANSIENT:
+ return "transient";
+ case INDICATION_TYPE_TRUST:
+ return "trust";
+ case INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE:
+ return "persistent_unlock_message";
+ case INDICATION_TYPE_USER_LOCKED:
+ return "user_locked";
+ case INDICATION_TYPE_REVERSE_CHARGING:
+ return "reverse_charging";
+ case INDICATION_TYPE_BIOMETRIC_MESSAGE:
+ return "biometric_message";
+ case INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP:
+ return "biometric_message_followup";
+ default:
+ return "unknown[" + type + "]";
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 95bba13..09eaf75 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2445,15 +2445,28 @@
}
mKeyguardDisplayManager.show();
- // schedule 4hr idle timeout after which non-strong biometrics (i.e. weak or convenience
- // biometric) can't be used to unlock device until unlocking with strong biometric or
- // primary auth (i.e. PIN/pattern/password)
- mLockPatternUtils.scheduleNonStrongBiometricIdleTimeout(
- KeyguardUpdateMonitor.getCurrentUser());
+ scheduleNonStrongBiometricIdleTimeout();
Trace.endSection();
}
+ /**
+ * Schedule 4-hour idle timeout for non-strong biometrics when the device is locked
+ */
+ private void scheduleNonStrongBiometricIdleTimeout() {
+ final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
+ // If unlocking with non-strong (i.e. weak or convenience) biometrics is possible, schedule
+ // 4hr idle timeout after which non-strong biometrics can't be used to unlock device until
+ // unlocking with strong biometric or primary auth (i.e. PIN/pattern/password)
+ if (mUpdateMonitor.isUnlockingWithNonStrongBiometricsPossible(currentUser)) {
+ if (DEBUG) {
+ Log.d(TAG, "scheduleNonStrongBiometricIdleTimeout: schedule an alarm for "
+ + "currentUser=" + currentUser);
+ }
+ mLockPatternUtils.scheduleNonStrongBiometricIdleTimeout(currentUser);
+ }
+ }
+
private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
@Override
public void run() {
@@ -2947,6 +2960,8 @@
if (DEBUG) Log.d(TAG, "handleReset");
mKeyguardViewControllerLazy.get().reset(true /* hideBouncerWhenShowing */);
}
+
+ scheduleNonStrongBiometricIdleTimeout();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index 0af596a..baadc66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -20,9 +20,12 @@
import android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
import android.content.Context
import android.content.IntentFilter
+import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
import android.os.Looper
import android.os.UserHandle
import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Dumpable
import com.android.systemui.biometrics.AuthController
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -31,7 +34,9 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.user.data.repository.UserRepository
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -39,10 +44,12 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
@@ -57,6 +64,15 @@
/** Whether any fingerprints are enrolled for the current user. */
val isFingerprintEnrolled: StateFlow<Boolean>
+ /** Whether face authentication is enrolled for the current user. */
+ val isFaceEnrolled: Flow<Boolean>
+
+ /**
+ * Whether face authentication is enabled/disabled based on system settings like device policy,
+ * biometrics setting.
+ */
+ val isFaceAuthenticationEnabled: Flow<Boolean>
+
/**
* Whether the current user is allowed to use a strong biometric for device entry based on
* Android Security policies. If false, the user may be able to use primary authentication for
@@ -80,16 +96,34 @@
devicePolicyManager: DevicePolicyManager,
@Application scope: CoroutineScope,
@Background backgroundDispatcher: CoroutineDispatcher,
+ biometricManager: BiometricManager?,
@Main looper: Looper,
-) : BiometricSettingsRepository {
+ dumpManager: DumpManager,
+) : BiometricSettingsRepository, Dumpable {
+
+ init {
+ dumpManager.registerDumpable(this)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<String?>) {
+ pw.println("isFingerprintEnrolled=${isFingerprintEnrolled.value}")
+ pw.println("isStrongBiometricAllowed=${isStrongBiometricAllowed.value}")
+ pw.println("isFingerprintEnabledByDevicePolicy=${isFingerprintEnabledByDevicePolicy.value}")
+ }
/** UserId of the current selected user. */
private val selectedUserId: Flow<Int> =
userRepository.selectedUserInfo.map { it.id }.distinctUntilChanged()
+ private val devicePolicyChangedForAllUsers =
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ user = UserHandle.ALL
+ )
+
override val isFingerprintEnrolled: StateFlow<Boolean> =
selectedUserId
- .flatMapLatest {
+ .flatMapLatest { currentUserId ->
conflatedCallbackFlow {
val callback =
object : AuthController.Callback {
@@ -98,7 +132,7 @@
userId: Int,
hasEnrollments: Boolean
) {
- if (sensorBiometricType.isFingerprint) {
+ if (sensorBiometricType.isFingerprint && userId == currentUserId) {
trySendWithFailureLogging(
hasEnrollments,
TAG,
@@ -118,6 +152,77 @@
authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id)
)
+ override val isFaceEnrolled: Flow<Boolean> =
+ selectedUserId.flatMapLatest { selectedUserId: Int ->
+ conflatedCallbackFlow {
+ val callback =
+ object : AuthController.Callback {
+ override fun onEnrollmentsChanged(
+ sensorBiometricType: BiometricType,
+ userId: Int,
+ hasEnrollments: Boolean
+ ) {
+ // TODO(b/242022358), use authController.isFaceAuthEnrolled after
+ // ag/20176811 is available.
+ if (
+ sensorBiometricType == BiometricType.FACE &&
+ userId == selectedUserId
+ ) {
+ trySendWithFailureLogging(
+ hasEnrollments,
+ TAG,
+ "Face enrollment changed"
+ )
+ }
+ }
+ }
+ authController.addCallback(callback)
+ trySendWithFailureLogging(
+ authController.isFaceAuthEnrolled(selectedUserId),
+ TAG,
+ "Initial value of face auth enrollment"
+ )
+ awaitClose { authController.removeCallback(callback) }
+ }
+ }
+
+ override val isFaceAuthenticationEnabled: Flow<Boolean>
+ get() =
+ combine(isFaceEnabledByBiometricsManager, isFaceEnabledByDevicePolicy) {
+ biometricsManagerSetting,
+ devicePolicySetting ->
+ biometricsManagerSetting && devicePolicySetting
+ }
+
+ private val isFaceEnabledByDevicePolicy: Flow<Boolean> =
+ combine(selectedUserId, devicePolicyChangedForAllUsers) { userId, _ ->
+ devicePolicyManager.isFaceDisabled(userId)
+ }
+ .onStart {
+ emit(devicePolicyManager.isFaceDisabled(userRepository.getSelectedUserInfo().id))
+ }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+
+ private val isFaceEnabledByBiometricsManager =
+ conflatedCallbackFlow {
+ val callback =
+ object : IBiometricEnabledOnKeyguardCallback.Stub() {
+ override fun onChanged(enabled: Boolean, userId: Int) {
+ trySendWithFailureLogging(
+ enabled,
+ TAG,
+ "biometricsEnabled state changed"
+ )
+ }
+ }
+ biometricManager?.registerEnabledOnKeyguardCallback(callback)
+ awaitClose {}
+ }
+ // This is because the callback is binder-based and we want to avoid multiple callbacks
+ // being registered.
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
override val isStrongBiometricAllowed: StateFlow<Boolean> =
selectedUserId
.flatMapLatest { currUserId ->
@@ -155,17 +260,8 @@
override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
selectedUserId
.flatMapLatest { userId ->
- broadcastDispatcher
- .broadcastFlow(
- filter = IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- user = UserHandle.ALL
- )
- .transformLatest {
- emit(
- (devicePolicyManager.getKeyguardDisabledFeatures(null, userId) and
- DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) == 0
- )
- }
+ devicePolicyChangedForAllUsers
+ .transformLatest { emit(devicePolicyManager.isFingerprintDisabled(userId)) }
.flowOn(backgroundDispatcher)
.distinctUntilChanged()
}
@@ -173,13 +269,21 @@
scope,
started = SharingStarted.Eagerly,
initialValue =
- devicePolicyManager.getKeyguardDisabledFeatures(
- null,
+ devicePolicyManager.isFingerprintDisabled(
userRepository.getSelectedUserInfo().id
- ) and DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT == 0
+ )
)
companion object {
private const val TAG = "BiometricsRepositoryImpl"
}
}
+
+private fun DevicePolicyManager.isFaceDisabled(userId: Int): Boolean =
+ isNotActive(userId, DevicePolicyManager.KEYGUARD_DISABLE_FACE)
+
+private fun DevicePolicyManager.isFingerprintDisabled(userId: Int): Boolean =
+ isNotActive(userId, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT)
+
+private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
+ (getKeyguardDisabledFeatures(null, userId) and policy) == 0
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index b3a9cf5..7c46684 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -19,10 +19,13 @@
import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Dumpable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
@@ -49,7 +52,16 @@
constructor(
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Application scope: CoroutineScope,
-) : DeviceEntryFingerprintAuthRepository {
+ dumpManager: DumpManager,
+) : DeviceEntryFingerprintAuthRepository, Dumpable {
+
+ init {
+ dumpManager.registerDumpable(this)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<String?>) {
+ pw.println("isLockedOut=${isLockedOut.value}")
+ }
override val isLockedOut: StateFlow<Boolean> =
conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 4ac6ac8..091acad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -158,18 +158,18 @@
override val bouncerErrorMessage: CharSequence?
get() = viewMediatorCallback.consumeCustomMessage()
- init {
- setUpLogging()
- }
-
/** Values associated with the AlternateBouncer */
private val _isAlternateBouncerVisible = MutableStateFlow(false)
override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
- private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
+ private val _isAlternateBouncerUIAvailable = MutableStateFlow(false)
override val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
_isAlternateBouncerUIAvailable.asStateFlow()
+ init {
+ setUpLogging()
+ }
+
override fun setPrimaryScrimmed(isScrimmed: Boolean) {
_primaryBouncerScrimmed.value = isScrimmed
}
@@ -290,6 +290,9 @@
resourceUpdateRequests
.logDiffsForTable(buffer, "", "ResourceUpdateRequests", false)
.launchIn(applicationScope)
+ isAlternateBouncerUIAvailable
+ .logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false)
+ .launchIn(applicationScope)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 81a5828..8715d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -34,6 +34,7 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -56,9 +57,14 @@
private fun listenForDreamingToLockscreen() {
scope.launch {
- // Using isDreamingWithOverlay provides an optimized path to LOCKSCREEN state, which
- // otherwise would have gone through OCCLUDED first
- keyguardInteractor.isAbleToDream
+ // Dependending on the dream, either dream state or occluded change will change first,
+ // so listen for both
+ combine(keyguardInteractor.isAbleToDream, keyguardInteractor.isKeyguardOccluded) {
+ isAbleToDream,
+ isKeyguardOccluded ->
+ isAbleToDream && isKeyguardOccluded
+ }
+ .distinctUntilChanged()
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 3d39da6..7e86a5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -22,6 +22,8 @@
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
@@ -31,7 +33,6 @@
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.CommandQueue.Callbacks
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
@@ -41,7 +42,9 @@
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -52,6 +55,7 @@
constructor(
private val repository: KeyguardRepository,
private val commandQueue: CommandQueue,
+ featureFlags: FeatureFlags,
) {
/**
* The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
@@ -129,6 +133,29 @@
*/
val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
+ /** Keyguard is present and is not occluded. */
+ val isKeyguardVisible: Flow<Boolean> =
+ combine(isKeyguardShowing, isKeyguardOccluded) { showing, occluded -> showing && !occluded }
+
+ /** Whether camera is launched over keyguard. */
+ var isSecureCameraActive =
+ if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+ combine(
+ isKeyguardVisible,
+ repository.isBouncerShowing,
+ onCameraLaunchDetected,
+ ) { isKeyguardVisible, isBouncerShowing, cameraLaunchEvent ->
+ when {
+ isKeyguardVisible -> false
+ isBouncerShowing -> false
+ else -> cameraLaunchEvent == CameraLaunchSourceModel.POWER_DOUBLE_TAP
+ }
+ }
+ .onStart { emit(false) }
+ } else {
+ flowOf(false)
+ }
+
/** The approximate location on the screen of the fingerprint sensor, if one is available. */
val fingerprintSensorLocation: Flow<Point?> = repository.fingerprintSensorLocation
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 57c3b31..b2da793 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -367,6 +367,10 @@
name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_FULLSCREEN_PREVIEW,
value = featureFlags.isEnabled(Flags.WALLPAPER_FULLSCREEN_PREVIEW),
),
+ KeyguardPickerFlag(
+ name = Contract.FlagsTable.FLAG_NAME_MONOCHROMATIC_THEME,
+ value = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME)
+ )
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 53c80f6..84bcdf9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -42,6 +42,10 @@
constructor(
repository: KeyguardTransitionRepository,
) {
+ /** (any)->GONE transition information */
+ val anyStateToGoneTransition: Flow<TransitionStep> =
+ repository.transitions.filter { step -> step.to == KeyguardState.GONE }
+
/** (any)->AOD transition information */
val anyStateToAodTransition: Flow<TransitionStep> =
repository.transitions.filter { step -> step.to == KeyguardState.AOD }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 3319f9d..ab009f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -381,82 +381,87 @@
return when (event?.actionMasked) {
MotionEvent.ACTION_DOWN ->
if (viewModel.configKey != null) {
- longPressAnimator =
- view
- .animate()
- .scaleX(PRESSED_SCALE)
- .scaleY(PRESSED_SCALE)
- .setDuration(longPressDurationMs)
- .withEndAction {
- view.setOnClickListener {
- vibratorHelper?.vibrate(
- if (viewModel.isActivated) {
- Vibrations.Activated
- } else {
- Vibrations.Deactivated
- }
- )
- viewModel.onClicked(
- KeyguardQuickAffordanceViewModel.OnClickedParameters(
- configKey = viewModel.configKey,
- expandable = Expandable.fromView(view),
- )
- )
+ if (isUsingAccurateTool(event)) {
+ // For accurate tool types (stylus, mouse, etc.), we don't require a
+ // long-press.
+ } else {
+ // When not using a stylus, we require a long-press to activate the
+ // quick affordance, mostly to do "falsing" (e.g. protect from false
+ // clicks in the pocket/bag).
+ longPressAnimator =
+ view
+ .animate()
+ .scaleX(PRESSED_SCALE)
+ .scaleY(PRESSED_SCALE)
+ .setDuration(longPressDurationMs)
+ .withEndAction {
+ dispatchClick(viewModel.configKey)
+ cancel()
}
- view.performClick()
- view.setOnClickListener(null)
- cancel()
- }
+ }
true
} else {
false
}
MotionEvent.ACTION_MOVE -> {
- if (event.historySize > 0) {
- val distance =
- sqrt(
- (event.y - event.getHistoricalY(0)).pow(2) +
- (event.x - event.getHistoricalX(0)).pow(2)
- )
- if (distance > ViewConfiguration.getTouchSlop()) {
+ if (!isUsingAccurateTool(event)) {
+ // Moving too far while performing a long-press gesture cancels that
+ // gesture.
+ val distanceMoved = distanceMoved(event)
+ if (distanceMoved > ViewConfiguration.getTouchSlop()) {
cancel()
}
}
true
}
MotionEvent.ACTION_UP -> {
- cancel(
- onAnimationEnd =
- if (event.eventTime - event.downTime < longPressDurationMs) {
- Runnable {
- messageDisplayer.invoke(
- R.string.keyguard_affordance_press_too_short
- )
- val amplitude =
- view.context.resources
- .getDimensionPixelSize(
- R.dimen.keyguard_affordance_shake_amplitude
- )
- .toFloat()
- val shakeAnimator =
- ObjectAnimator.ofFloat(
- view,
- "translationX",
- -amplitude / 2,
- amplitude / 2,
+ if (isUsingAccurateTool(event)) {
+ // When using an accurate tool type (stylus, mouse, etc.), we don't require
+ // a long-press gesture to activate the quick affordance. Therefore, lifting
+ // the pointer performs a click.
+ if (
+ viewModel.configKey != null &&
+ distanceMoved(event) <= ViewConfiguration.getTouchSlop()
+ ) {
+ dispatchClick(viewModel.configKey)
+ }
+ } else {
+ // When not using a stylus, lifting the finger/pointer will actually cancel
+ // the long-press gesture. Calling cancel after the quick affordance was
+ // already long-press activated is a no-op, so it's safe to call from here.
+ cancel(
+ onAnimationEnd =
+ if (event.eventTime - event.downTime < longPressDurationMs) {
+ Runnable {
+ messageDisplayer.invoke(
+ R.string.keyguard_affordance_press_too_short
)
- shakeAnimator.duration =
- ShakeAnimationDuration.inWholeMilliseconds
- shakeAnimator.interpolator =
- CycleInterpolator(ShakeAnimationCycles)
- shakeAnimator.start()
+ val amplitude =
+ view.context.resources
+ .getDimensionPixelSize(
+ R.dimen.keyguard_affordance_shake_amplitude
+ )
+ .toFloat()
+ val shakeAnimator =
+ ObjectAnimator.ofFloat(
+ view,
+ "translationX",
+ -amplitude / 2,
+ amplitude / 2,
+ )
+ shakeAnimator.duration =
+ ShakeAnimationDuration.inWholeMilliseconds
+ shakeAnimator.interpolator =
+ CycleInterpolator(ShakeAnimationCycles)
+ shakeAnimator.start()
- vibratorHelper?.vibrate(Vibrations.Shake)
+ vibratorHelper?.vibrate(Vibrations.Shake)
+ }
+ } else {
+ null
}
- } else {
- null
- }
- )
+ )
+ }
true
}
MotionEvent.ACTION_CANCEL -> {
@@ -467,6 +472,28 @@
}
}
+ private fun dispatchClick(
+ configKey: String,
+ ) {
+ view.setOnClickListener {
+ vibratorHelper?.vibrate(
+ if (viewModel.isActivated) {
+ Vibrations.Activated
+ } else {
+ Vibrations.Deactivated
+ }
+ )
+ viewModel.onClicked(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = configKey,
+ expandable = Expandable.fromView(view),
+ )
+ )
+ }
+ view.performClick()
+ view.setOnClickListener(null)
+ }
+
private fun cancel(onAnimationEnd: Runnable? = null) {
longPressAnimator?.cancel()
longPressAnimator = null
@@ -475,6 +502,40 @@
companion object {
private const val PRESSED_SCALE = 1.5f
+
+ /**
+ * Returns `true` if the tool type at the given pointer index is an accurate tool (like
+ * stylus or mouse), which means we can trust it to not be a false click; `false`
+ * otherwise.
+ */
+ private fun isUsingAccurateTool(
+ event: MotionEvent,
+ pointerIndex: Int = 0,
+ ): Boolean {
+ return when (event.getToolType(pointerIndex)) {
+ MotionEvent.TOOL_TYPE_STYLUS -> true
+ MotionEvent.TOOL_TYPE_MOUSE -> true
+ else -> false
+ }
+ }
+
+ /**
+ * Returns the amount of distance the pointer moved since the historical record at the
+ * [since] index.
+ */
+ private fun distanceMoved(
+ event: MotionEvent,
+ since: Int = 0,
+ ): Float {
+ return if (event.historySize > 0) {
+ sqrt(
+ (event.y - event.getHistoricalY(since)).pow(2) +
+ (event.x - event.getHistoricalX(since)).pow(2)
+ )
+ } else {
+ 0f
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index adde595..403576c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -38,6 +38,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
import dagger.assisted.Assisted
@@ -69,6 +70,8 @@
KeyguardQuickAffordancePreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
false,
)
+ private val shouldHideClock: Boolean =
+ bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false)
private var host: SurfaceControlViewHost
@@ -104,7 +107,9 @@
val rootView = FrameLayout(context)
setUpBottomArea(rootView)
- setUpClock(rootView)
+ if (!shouldHideClock) {
+ setUpClock(rootView)
+ }
rootView.measure(
View.MeasureSpec.makeMeasureSpec(
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
index 348d941..ccd4060 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -79,10 +79,10 @@
}
}
-/**
- * Each time the boolean flow is updated with a new value that's different from the previous value,
- * logs the new value to the given [tableLogBuffer].
- */
+// Here and below: Various Flow<SomeType> extension functions that are effectively equivalent to the
+// above [logDiffsForTable] method.
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
fun Flow<Boolean>.logDiffsForTable(
tableLogBuffer: TableLogBuffer,
columnPrefix: String,
@@ -100,10 +100,8 @@
newVal
}
}
-/**
- * Each time the Int flow is updated with a new value that's different from the previous value, logs
- * the new value to the given [tableLogBuffer].
- */
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
fun Flow<Int>.logDiffsForTable(
tableLogBuffer: TableLogBuffer,
columnPrefix: String,
@@ -122,10 +120,26 @@
}
}
-/**
- * Each time the String? flow is updated with a new value that's different from the previous value,
- * logs the new value to the given [tableLogBuffer].
- */
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+fun Flow<Int?>.logDiffsForTable(
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String,
+ columnName: String,
+ initialValue: Int?,
+): Flow<Int?> {
+ val initialValueFun = {
+ tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
+ initialValue
+ }
+ return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int? ->
+ if (prevVal != newVal) {
+ tableLogBuffer.logChange(columnPrefix, columnName, newVal)
+ }
+ newVal
+ }
+}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
fun Flow<String?>.logDiffsForTable(
tableLogBuffer: TableLogBuffer,
columnPrefix: String,
@@ -143,3 +157,23 @@
newVal
}
}
+
+/** See [logDiffsForTable(TableLogBuffer, String, T)]. */
+fun <T> Flow<List<T>>.logDiffsForTable(
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String,
+ columnName: String,
+ initialValue: List<T>,
+): Flow<List<T>> {
+ val initialValueFun = {
+ tableLogBuffer.logChange(columnPrefix, columnName, initialValue.toString())
+ initialValue
+ }
+ return this.pairwiseBy(initialValueFun) { prevVal, newVal: List<T> ->
+ if (prevVal != newVal) {
+ // TODO(b/267761156): Can we log list changes without using toString?
+ tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString())
+ }
+ newVal
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
index 68c297f..4880f80 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
@@ -27,7 +27,7 @@
var columnName: String = "",
var type: DataType = DataType.EMPTY,
var bool: Boolean = false,
- var int: Int = 0,
+ var int: Int? = null,
var str: String? = null,
) {
/** Resets to default values so that the object can be recycled. */
@@ -54,7 +54,7 @@
}
/** Sets this to store an int change. */
- fun set(value: Int) {
+ fun set(value: Int?) {
type = DataType.INT
int = value
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 2c299d6..1712dab 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -138,7 +138,7 @@
}
/** Logs a Int change. */
- fun logChange(prefix: String, columnName: String, value: Int) {
+ fun logChange(prefix: String, columnName: String, value: Int?) {
logChange(systemClock.currentTimeMillis(), prefix, columnName, value)
}
@@ -155,7 +155,7 @@
change.set(value)
}
- private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int) {
+ private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int?) {
val change = obtain(timestamp, prefix, columnName)
change.set(value)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index a692ad7..52d4171 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -28,12 +28,15 @@
import android.os.UserHandle
import android.view.ViewGroup
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider
import com.android.internal.app.ChooserActivity
import com.android.internal.app.ResolverListController
import com.android.internal.app.chooser.NotSelectableTargetInfo
import com.android.internal.app.chooser.TargetInfo
import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorResultHandler
@@ -47,6 +50,7 @@
class MediaProjectionAppSelectorActivity(
private val componentFactory: MediaProjectionAppSelectorComponent.Factory,
private val activityLauncher: AsyncActivityLauncher,
+ private val featureFlags: FeatureFlags,
/** This is used to override the dependency in a screenshot test */
@VisibleForTesting
private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
@@ -56,7 +60,8 @@
constructor(
componentFactory: MediaProjectionAppSelectorComponent.Factory,
activityLauncher: AsyncActivityLauncher,
- ) : this(componentFactory, activityLauncher, null)
+ featureFlags: FeatureFlags
+ ) : this(componentFactory, activityLauncher, featureFlags, listControllerFactory = null)
private lateinit var configurationController: ConfigurationController
private lateinit var controller: MediaProjectionAppSelectorController
@@ -91,6 +96,13 @@
override fun appliedThemeResId(): Int = R.style.Theme_SystemUI_MediaProjectionAppSelector
+ override fun createBlockerEmptyStateProvider(): EmptyStateProvider =
+ if (featureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
+ component.emptyStateProvider
+ } else {
+ super.createBlockerEmptyStateProvider()
+ }
+
override fun createListController(userHandle: UserHandle): ResolverListController =
listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index d830fc4..c4e76b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -45,33 +45,46 @@
import android.util.Log;
import android.view.Window;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
import com.android.systemui.screenrecord.MediaProjectionPermissionDialog;
import com.android.systemui.screenrecord.ScreenShareOption;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.Utils;
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
public class MediaProjectionPermissionActivity extends Activity
implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
private static final String TAG = "MediaProjectionPermissionActivity";
private static final float MAX_APP_NAME_SIZE_PX = 500f;
private static final String ELLIPSIS = "\u2026";
+ private final FeatureFlags mFeatureFlags;
+ private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
+
private String mPackageName;
private int mUid;
private IMediaProjectionManager mService;
- private FeatureFlags mFeatureFlags;
private AlertDialog mDialog;
+ @Inject
+ public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
+ Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
+ mFeatureFlags = featureFlags;
+ mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
+ }
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- mFeatureFlags = Dependency.get(FeatureFlags.class);
mPackageName = getCallingPackage();
IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
mService = IMediaProjectionManager.Stub.asInterface(b);
@@ -104,6 +117,12 @@
return;
}
+ if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
+ if (showScreenCaptureDisabledDialogIfNeeded()) {
+ return;
+ }
+ }
+
TextPaint paint = new TextPaint();
paint.setTextSize(42);
@@ -171,16 +190,7 @@
mDialog = dialogBuilder.create();
}
- SystemUIDialog.registerDismissListener(mDialog);
- SystemUIDialog.applyFlags(mDialog);
- SystemUIDialog.setDialogSize(mDialog);
-
- mDialog.setOnCancelListener(this);
- mDialog.create();
- mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
-
- final Window w = mDialog.getWindow();
- w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ setUpDialog(mDialog);
mDialog.show();
}
@@ -200,6 +210,32 @@
}
}
+ private void setUpDialog(AlertDialog dialog) {
+ SystemUIDialog.registerDismissListener(dialog);
+ SystemUIDialog.applyFlags(dialog);
+ SystemUIDialog.setDialogSize(dialog);
+
+ dialog.setOnCancelListener(this);
+ dialog.create();
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+
+ final Window w = dialog.getWindow();
+ w.addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ }
+
+ private boolean showScreenCaptureDisabledDialogIfNeeded() {
+ final UserHandle hostUserHandle = getHostUserHandle();
+ if (mScreenCaptureDevicePolicyResolver.get()
+ .isScreenCaptureCompletelyDisabled(hostUserHandle)) {
+ AlertDialog dialog = new ScreenCaptureDisabledDialog(this);
+ setUpDialog(dialog);
+ dialog.show();
+ return true;
+ }
+
+ return false;
+ }
+
private void grantMediaProjectionPermission(int screenShareMode) {
try {
if (screenShareMode == ENTIRE_SCREEN) {
@@ -211,7 +247,7 @@
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
projection.asBinder());
intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
- UserHandle.getUserHandleForUid(getLaunchedFromUid()));
+ getHostUserHandle());
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
// Start activity from the current foreground user to avoid creating a separate
@@ -230,6 +266,10 @@
}
}
+ private UserHandle getHostUserHandle() {
+ return UserHandle.getUserHandleForUid(getLaunchedFromUid());
+ }
+
private IMediaProjection createProjection(int uid, String packageName) throws RemoteException {
return mService.createProjection(uid, packageName,
MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 0a94803..520edef 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -50,6 +50,7 @@
import android.util.Log
import androidx.media.utils.MediaConstants
import com.android.internal.logging.InstanceId
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -67,6 +68,7 @@
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.resume.MediaResumeListener
+import com.android.systemui.media.controls.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.controls.util.MediaFlags
@@ -176,6 +178,7 @@
private val mediaFlags: MediaFlags,
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -240,6 +243,7 @@
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : this(
context,
backgroundExecutor,
@@ -263,6 +267,7 @@
mediaFlags,
logger,
smartspaceManager,
+ keyguardUpdateMonitor,
)
private val appChangeReceiver =
@@ -1335,7 +1340,9 @@
Assert.isMainThread()
val removed = mediaEntries.remove(key) ?: return
- if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) {
+ if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) {
+ logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
+ } else if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) {
convertToResumePlayer(removed)
} else if (mediaFlags.isRetainingPlayersEnabled()) {
handlePossibleRemoval(removed, notificationRemoved = true)
@@ -1431,6 +1438,22 @@
notifyMediaDataLoaded(key = pkg, oldKey = pkg, info = updated)
}
logger.logActiveConvertedToResume(updated.appUid, pkg, updated.instanceId)
+
+ // Limit total number of resume controls
+ val resumeEntries = mediaEntries.filter { (key, data) -> data.resumption }
+ val numResume = resumeEntries.size
+ if (numResume > ResumeMediaBrowser.MAX_RESUMPTION_CONTROLS) {
+ resumeEntries
+ .toList()
+ .sortedBy { (key, data) -> data.lastActive }
+ .subList(0, numResume - ResumeMediaBrowser.MAX_RESUMPTION_CONTROLS)
+ .forEach { (key, data) ->
+ Log.d(TAG, "Removing excess control $key")
+ mediaEntries.remove(key)
+ notifyMediaDataRemoved(key)
+ logger.logMediaRemoved(data.appUid, data.packageName, data.instanceId)
+ }
+ }
}
fun setMediaResumptionEnabled(isEnabled: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
index aa46b14..878962d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
@@ -40,7 +40,7 @@
@VisibleForTesting
val RESUME_MEDIA_TIMEOUT =
- SystemProperties.getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3))
+ SystemProperties.getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(2))
/** Controller responsible for keeping track of playback states and expiring inactive streams. */
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index fac1d5e..b72923a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -30,13 +30,20 @@
import android.view.animation.PathInterpolator
import android.widget.LinearLayout
import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
import com.android.internal.logging.InstanceId
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
@@ -63,6 +70,10 @@
import java.util.TreeMap
import javax.inject.Inject
import javax.inject.Provider
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
private const val TAG = "MediaCarouselController"
private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS)
@@ -91,6 +102,8 @@
private val logger: MediaUiEventLogger,
private val debugLogger: MediaCarouselControllerLogger,
private val mediaFlags: MediaFlags,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : Dumpable {
/** The current width of the carousel */
private var currentCarouselWidth: Int = 0
@@ -213,6 +226,17 @@
}
}
+ private val keyguardUpdateMonitorCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onStrongAuthStateChanged(userId: Int) {
+ if (keyguardUpdateMonitor.isUserInLockdown(userId)) {
+ hideMediaCarousel()
+ } else if (keyguardUpdateMonitor.isUserUnlocked(userId)) {
+ showMediaCarousel()
+ }
+ }
+ }
+
/**
* Update MediaCarouselScrollHandler.visibleToUser to reflect media card container visibility.
* It will be called when the container is out of view.
@@ -487,6 +511,13 @@
}
}
)
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ mediaCarousel.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // A backup to show media carousel (if available) once the keyguard is gone.
+ listenForAnyStateToGoneKeyguardTransition(this)
+ }
+ }
}
private fun inflateSettingsButton() {
@@ -516,6 +547,23 @@
return mediaCarousel
}
+ private fun hideMediaCarousel() {
+ mediaCarousel.visibility = View.GONE
+ }
+
+ private fun showMediaCarousel() {
+ mediaCarousel.visibility = View.VISIBLE
+ }
+
+ @VisibleForTesting
+ internal fun listenForAnyStateToGoneKeyguardTransition(scope: CoroutineScope): Job {
+ return scope.launch {
+ keyguardTransitionInteractor.anyStateToGoneTransition
+ .filter { it.transitionState == TransitionState.FINISHED }
+ .collect { showMediaCarousel() }
+ }
+ }
+
private fun reorderAllPlayers(
previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?,
key: String? = null
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 0b4b668a..7f420a8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -1136,8 +1136,10 @@
/* pixelDensity= */ getContext().getResources().getDisplayMetrics().density,
mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
/* opacity= */ 100,
- /* shouldFillRipple= */ false,
/* sparkleStrength= */ 0f,
+ /* baseRingFadeParams= */ null,
+ /* sparkleRingFadeParams= */ null,
+ /* centerFillFadeParams= */ null,
/* shouldDistort= */ false
)
);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 1e6002c..b9b0459 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -311,16 +311,15 @@
}
// media player
- val controlsTop =
- calculateWidgetGroupAlphaForSquishiness(
- controlIds,
- squishedViewState.measureHeight.toFloat(),
- squishedViewState,
- squishFraction
- )
+ calculateWidgetGroupAlphaForSquishiness(
+ controlIds,
+ squishedViewState.measureHeight.toFloat(),
+ squishedViewState,
+ squishFraction
+ )
calculateWidgetGroupAlphaForSquishiness(
detailIds,
- controlsTop,
+ squishedViewState.measureHeight.toFloat(),
squishedViewState,
squishFraction
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
index 85282a1..e95106e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
@@ -16,6 +16,7 @@
package com.android.systemui.media.controls.util;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -61,8 +62,9 @@
* @param extras
* @return the progress value between 0-1 inclusive if prsent, otherwise null
*/
- public static Double getDescriptionProgress(Bundle extras) {
- if (!extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS)) {
+ public static Double getDescriptionProgress(@Nullable Bundle extras) {
+ if (extras == null
+ || !extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS)) {
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index a3ae943..720c44a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -44,25 +44,37 @@
* @param appPackageName the package name of the app playing the media.
* @param onPackageNotFoundException a function run if a
* [PackageManager.NameNotFoundException] occurs.
+ * @param isReceiver indicates whether the icon is displayed in a receiver view.
*/
fun getIconInfoFromPackageName(
context: Context,
appPackageName: String?,
+ isReceiver: Boolean,
onPackageNotFoundException: () -> Unit,
): IconInfo {
if (appPackageName != null) {
val packageManager = context.packageManager
try {
+ val appName =
+ packageManager
+ .getApplicationInfo(
+ appPackageName,
+ PackageManager.ApplicationInfoFlags.of(0),
+ )
+ .loadLabel(packageManager)
+ .toString()
val contentDescription =
- ContentDescription.Loaded(
- packageManager
- .getApplicationInfo(
- appPackageName,
- PackageManager.ApplicationInfoFlags.of(0)
+ if (isReceiver) {
+ ContentDescription.Loaded(
+ context.getString(
+ R.string
+ .media_transfer_receiver_content_description_with_app_name,
+ appName
)
- .loadLabel(packageManager)
- .toString()
- )
+ )
+ } else {
+ ContentDescription.Loaded(appName)
+ }
return IconInfo(
contentDescription,
MediaTttIcon.Loaded(packageManager.getApplicationIcon(appPackageName)),
@@ -74,7 +86,15 @@
}
}
return IconInfo(
- ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name),
+ if (isReceiver) {
+ ContentDescription.Resource(
+ R.string.media_transfer_receiver_content_description_unknown_app
+ )
+ } else {
+ ContentDescription.Resource(
+ R.string.media_output_dialog_unknown_launch_app_name
+ )
+ },
MediaTttIcon.Resource(R.drawable.ic_cast),
tintAttr = android.R.attr.textColorPrimary,
isAppIcon = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 34bf74fa..fab8c06 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -16,7 +16,9 @@
package com.android.systemui.media.taptotransfer.receiver
+import android.animation.TimeInterpolator
import android.annotation.SuppressLint
+import android.animation.ValueAnimator
import android.app.StatusBarManager
import android.content.Context
import android.graphics.Rect
@@ -31,8 +33,10 @@
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
+import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE
import com.android.internal.widget.CachingIconView
import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.ui.binder.TintedIconViewBinder
import com.android.systemui.dagger.SysUISingleton
@@ -101,6 +105,13 @@
fitInsetsTypes = 0 // Ignore insets from all system bars
}
+ // Value animator that controls the bouncing animation of views.
+ private val bounceAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
+ repeatCount = ValueAnimator.INFINITE
+ repeatMode = ValueAnimator.REVERSE
+ duration = ICON_BOUNCE_ANIM_DURATION
+ }
+
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
@@ -173,7 +184,11 @@
override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
val packageName = newInfo.routeInfo.clientPackageName
- var iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, packageName) {
+ var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
+ context,
+ packageName,
+ isReceiver = true,
+ ) {
logger.logPackageNotFound(packageName)
}
@@ -199,44 +214,52 @@
val iconView = currentView.getAppIconView()
iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
- iconView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView)
+
+ val iconContainerView = currentView.getIconContainerView()
+ iconContainerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
}
override fun animateViewIn(view: ViewGroup) {
- val appIconView = view.getAppIconView()
+ val iconContainerView = view.getIconContainerView()
val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
- animateViewTranslationAndFade(appIconView, -1 * getTranslationAmount(), 1f)
- animateViewTranslationAndFade(iconRippleView, -1 * getTranslationAmount(), 1f)
+ val translationYBy = getTranslationAmount()
+ // Make the icon container view starts animation from bottom of the screen.
+ iconContainerView.translationY += rippleController.getReceiverIconSize()
+ animateViewTranslationAndFade(
+ iconContainerView,
+ translationYBy = -1 * translationYBy,
+ alphaEndValue = 1f,
+ Interpolators.EMPHASIZED_DECELERATE,
+ ) {
+ animateBouncingView(iconContainerView, translationYBy * BOUNCE_TRANSLATION_RATIO)
+ }
rippleController.expandToInProgressState(rippleView, iconRippleView)
}
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
- val appIconView = view.getAppIconView()
- val iconRippleView: ReceiverChipRippleView = view.requireViewById(R.id.icon_glow_ripple)
+ val iconContainerView = view.getIconContainerView()
val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
+ val translationYBy = getTranslationAmount()
+
+ // Remove update listeners from bounce animator to prevent any conflict with
+ // translation animation.
+ bounceAnimator.removeAllUpdateListeners()
+ bounceAnimator.cancel()
if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
rippleController.expandToSuccessState(rippleView, onAnimationEnd)
animateViewTranslationAndFade(
- iconRippleView,
- -1 * getTranslationAmount(),
- 0f,
- translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
- alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
- )
- animateViewTranslationAndFade(
- appIconView,
- -1 * getTranslationAmount(),
+ iconContainerView,
+ -1 * translationYBy,
0f,
translationDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
alphaDuration = ICON_TRANSLATION_SUCCEEDED_DURATION,
)
} else {
rippleController.collapseRipple(rippleView, onAnimationEnd)
- animateViewTranslationAndFade(iconRippleView, getTranslationAmount(), 0f)
- animateViewTranslationAndFade(appIconView, getTranslationAmount(), 0f)
+ animateViewTranslationAndFade(iconContainerView, translationYBy, 0f)
}
}
@@ -248,15 +271,19 @@
/** Animation of view translation and fading. */
private fun animateViewTranslationAndFade(
- view: View,
+ view: ViewGroup,
translationYBy: Float,
alphaEndValue: Float,
+ interpolator: TimeInterpolator? = null,
translationDuration: Long = ICON_TRANSLATION_ANIM_DURATION,
alphaDuration: Long = ICON_ALPHA_ANIM_DURATION,
+ onAnimationEnd: Runnable? = null,
) {
view.animate()
.translationYBy(translationYBy)
+ .setInterpolator(interpolator)
.setDuration(translationDuration)
+ .withEndAction { onAnimationEnd?.run() }
.start()
view.animate()
.alpha(alphaEndValue)
@@ -266,17 +293,42 @@
/** Returns the amount that the chip will be translated by in its intro animation. */
private fun getTranslationAmount(): Float {
- return rippleController.getRippleSize() * 0.5f -
- rippleController.getReceiverIconSize()
+ return rippleController.getRippleSize() * 0.5f
}
private fun View.getAppIconView(): CachingIconView {
return this.requireViewById(R.id.app_icon)
}
+ private fun View.getIconContainerView(): ViewGroup {
+ return this.requireViewById(R.id.icon_container_view)
+ }
+
+ private fun animateBouncingView(iconContainerView: ViewGroup, translationYBy: Float) {
+ if (bounceAnimator.isStarted) {
+ return
+ }
+
+ addViewToBounceAnimation(iconContainerView, translationYBy)
+
+ // In order not to announce description every time the view animate.
+ iconContainerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
+ bounceAnimator.start()
+ }
+
+ private fun addViewToBounceAnimation(view: View, translationYBy: Float) {
+ val prevTranslationY = view.translationY
+ bounceAnimator.addUpdateListener { updateListener ->
+ val progress = updateListener.animatedValue as Float
+ view.translationY = prevTranslationY + translationYBy * progress
+ }
+ }
+
companion object {
private const val ICON_TRANSLATION_ANIM_DURATION = 500L
+ private const val ICON_BOUNCE_ANIM_DURATION = 750L
private const val ICON_TRANSLATION_SUCCEEDED_DURATION = 167L
+ private const val BOUNCE_TRANSLATION_RATIO = 0.15f
private val ICON_ALPHA_ANIM_DURATION = 5.frames
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index f1acae8..4ff082a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -34,7 +34,7 @@
init {
setupShader(RippleShader.RippleShape.CIRCLE)
- setRippleFill(true)
+ setupRippleFadeParams()
setSparkleStrength(0f)
isStarted = false
}
@@ -72,7 +72,7 @@
animator.removeAllUpdateListeners()
// Only show the outline as ripple expands and disappears when animation ends.
- setRippleFill(false)
+ removeRippleFill()
val startingPercentage = calculateStartingPercentage(newHeight)
animator.duration = EXPAND_TO_FULL_DURATION
@@ -103,6 +103,38 @@
return 1 - remainingPercentage
}
+ private fun setupRippleFadeParams() {
+ with(rippleShader) {
+ // No fade out for the base ring.
+ baseRingFadeParams.fadeOutStart = 1f
+ baseRingFadeParams.fadeOutEnd = 1f
+
+ // No fade in and outs for the center fill, as we always draw it.
+ centerFillFadeParams.fadeInStart = 0f
+ centerFillFadeParams.fadeInEnd = 0f
+ centerFillFadeParams.fadeOutStart = 1f
+ centerFillFadeParams.fadeOutEnd = 1f
+ }
+ }
+
+ private fun removeRippleFill() {
+ with(rippleShader) {
+ // Set back to default because we modified them in [setupRippleFadeParams].
+ baseRingFadeParams.fadeOutStart = RippleShader.DEFAULT_BASE_RING_FADE_OUT_START
+ baseRingFadeParams.fadeOutEnd = RippleShader.DEFAULT_FADE_OUT_END
+
+ centerFillFadeParams.fadeInStart = RippleShader.DEFAULT_FADE_IN_START
+ centerFillFadeParams.fadeInEnd = RippleShader.DEFAULT_CENTER_FILL_FADE_IN_END
+
+ // To avoid a seam showing up, we should match either:
+ // 1. baseRingFadeParams#fadeInEnd and centerFillFadeParams#fadeOutStart
+ // 2. baseRingFadeParams#fadeOutStart and centerFillFadeOutStart
+ // Here we go with 1 to fade in the centerFill faster.
+ centerFillFadeParams.fadeOutStart = baseRingFadeParams.fadeInEnd
+ centerFillFadeParams.fadeOutEnd = RippleShader.DEFAULT_FADE_OUT_END
+ }
+ }
+
companion object {
const val DEFAULT_DURATION = 333L
const val EXPAND_TO_FULL_DURATION = 1000L
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 89ca5d3..6bb6906 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -161,7 +161,7 @@
routeInfo.name.toString()
}
val icon =
- MediaTttUtils.getIconInfoFromPackageName(context, packageName) {
+ MediaTttUtils.getIconInfoFromPackageName(context, packageName, isReceiver = false) {
logger.logPackageNotFound(packageName)
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index e665d83..3088d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -24,6 +24,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.MediaProjectionAppSelectorActivity
import com.android.systemui.media.MediaProjectionAppSelectorActivity.Companion.EXTRA_HOST_APP_USER_HANDLE
+import com.android.systemui.media.MediaProjectionPermissionActivity
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
@@ -67,6 +68,11 @@
fun provideMediaProjectionAppSelectorActivity(
activity: MediaProjectionAppSelectorActivity
): Activity
+
+ @Binds
+ @IntoMap
+ @ClassKey(MediaProjectionPermissionActivity::class)
+ fun bindsMediaProjectionPermissionActivity(impl: MediaProjectionPermissionActivity): Activity
}
/**
@@ -104,6 +110,12 @@
@Provides
@MediaProjectionAppSelector
@MediaProjectionAppSelectorScope
+ fun provideCallerPackageName(activity: MediaProjectionAppSelectorActivity): String? =
+ activity.callingPackage
+
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
fun bindConfigurationController(
activity: MediaProjectionAppSelectorActivity
): ConfigurationController = ConfigurationControllerImpl(activity)
@@ -149,6 +161,7 @@
val controller: MediaProjectionAppSelectorController
val recentsViewController: MediaProjectionRecentsViewController
+ val emptyStateProvider: MediaProjectionBlockerEmptyStateProvider
@get:HostUserHandle val hostUserHandle: UserHandle
@get:PersonalProfile val personalProfileUserHandle: UserHandle
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 52c7ca3..219629b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -36,16 +36,16 @@
private val flags: FeatureFlags,
@HostUserHandle private val hostUserHandle: UserHandle,
@MediaProjectionAppSelector private val scope: CoroutineScope,
- @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName
+ @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName,
+ @MediaProjectionAppSelector private val callerPackageName: String?
) {
fun init() {
scope.launch {
val recentTasks = recentTaskListProvider.loadRecentTasks()
- val tasks = recentTasks
- .filterDevicePolicyRestrictedTasks()
- .sortedTasks()
+ val tasks =
+ recentTasks.filterDevicePolicyRestrictedTasks().filterAppSelector().sortedTasks()
view.bind(tasks)
}
@@ -67,8 +67,13 @@
filter { UserHandle.of(it.userId) == hostUserHandle }
}
+ private fun List<RecentTask>.filterAppSelector(): List<RecentTask> = filter {
+ // Only take tasks that is not the app selector
+ it.topActivityComponent != appSelectorComponentName
+ }
+
private fun List<RecentTask>.sortedTasks(): List<RecentTask> = sortedBy {
// Show normal tasks first and only then tasks with opened app selector
- it.topActivityComponent == appSelectorComponentName
+ it.topActivityComponent?.packageName == callerPackageName
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index 2822435..f335733 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -1,14 +1,20 @@
package com.android.systemui.navigationbar.gestural
import android.content.Context
+import android.content.res.Configuration
+import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
+import android.util.MathUtils.min
+import android.util.TypedValue
import android.view.View
+import androidx.appcompat.view.ContextThemeWrapper
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
+import com.android.internal.R.style.Theme_DeviceDefault
import com.android.internal.util.LatencyTracker
import com.android.settingslib.Utils
import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOnAnimationEndListener
@@ -16,7 +22,10 @@
private const val TAG = "BackPanel"
private const val DEBUG = false
-class BackPanel(context: Context, private val latencyTracker: LatencyTracker) : View(context) {
+class BackPanel(
+ context: Context,
+ private val latencyTracker: LatencyTracker
+) : View(context) {
var arrowsPointLeft = false
set(value) {
@@ -45,52 +54,54 @@
/**
* The length of the arrow measured horizontally. Used for animating [arrowPath]
*/
- private var arrowLength = AnimatedFloat("arrowLength", SpringForce())
+ private var arrowLength = AnimatedFloat(
+ name = "arrowLength",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS
+ )
/**
* The height of the arrow measured vertically from its center to its top (i.e. half the total
* height). Used for animating [arrowPath]
*/
- private var arrowHeight = AnimatedFloat("arrowHeight", SpringForce())
-
- private val backgroundWidth = AnimatedFloat(
- name = "backgroundWidth",
- SpringForce().apply {
- stiffness = 600f
- dampingRatio = 0.65f
- }
+ var arrowHeight = AnimatedFloat(
+ name = "arrowHeight",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ROTATION_DEGREES
)
- private val backgroundHeight = AnimatedFloat(
- name = "backgroundHeight",
- SpringForce().apply {
- stiffness = 600f
- dampingRatio = 0.65f
- }
+ val backgroundWidth = AnimatedFloat(
+ name = "backgroundWidth",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
+ minimumValue = 0f,
+ )
+
+ val backgroundHeight = AnimatedFloat(
+ name = "backgroundHeight",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
+ minimumValue = 0f,
)
/**
* Corners of the background closer to the edge of the screen (where the arrow appeared from).
* Used for animating [arrowBackgroundRect]
*/
- private val backgroundEdgeCornerRadius = AnimatedFloat(
- name = "backgroundEdgeCornerRadius",
- SpringForce().apply {
- stiffness = 400f
- dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
- }
- )
+ val backgroundEdgeCornerRadius = AnimatedFloat("backgroundEdgeCornerRadius")
/**
* Corners of the background further from the edge of the screens (toward the direction the
* arrow is being dragged). Used for animating [arrowBackgroundRect]
*/
- private val backgroundFarCornerRadius = AnimatedFloat(
- name = "backgroundDragCornerRadius",
- SpringForce().apply {
- stiffness = 2200f
- dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
- }
+ val backgroundFarCornerRadius = AnimatedFloat("backgroundFarCornerRadius")
+
+ var scale = AnimatedFloat(
+ name = "scale",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_SCALE,
+ minimumValue = 0f
+ )
+
+ val scalePivotX = AnimatedFloat(
+ name = "scalePivotX",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
+ minimumValue = backgroundWidth.pos / 2,
)
/**
@@ -98,34 +109,40 @@
* background's margin relative to the screen edge. The arrow will be centered within the
* background.
*/
- private var horizontalTranslation = AnimatedFloat("horizontalTranslation", SpringForce())
+ var horizontalTranslation = AnimatedFloat(name = "horizontalTranslation")
- private val currentAlpha: FloatPropertyCompat<BackPanel> =
- object : FloatPropertyCompat<BackPanel>("currentAlpha") {
- override fun setValue(panel: BackPanel, value: Float) {
- panel.alpha = value
- }
+ var arrowAlpha = AnimatedFloat(
+ name = "arrowAlpha",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA,
+ minimumValue = 0f,
+ maximumValue = 1f
+ )
- override fun getValue(panel: BackPanel): Float = panel.alpha
- }
+ val backgroundAlpha = AnimatedFloat(
+ name = "backgroundAlpha",
+ minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA,
+ minimumValue = 0f,
+ maximumValue = 1f
+ )
- private val alphaAnimation = SpringAnimation(this, currentAlpha)
- .setSpring(
- SpringForce()
- .setStiffness(60f)
- .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
- )
+ private val allAnimatedFloat = setOf(
+ arrowLength,
+ arrowHeight,
+ backgroundWidth,
+ backgroundEdgeCornerRadius,
+ backgroundFarCornerRadius,
+ scalePivotX,
+ scale,
+ horizontalTranslation,
+ arrowAlpha,
+ backgroundAlpha
+ )
/**
* Canvas vertical translation. How far up/down the arrow and background appear relative to the
* canvas.
*/
- private var verticalTranslation: AnimatedFloat = AnimatedFloat(
- name = "verticalTranslation",
- SpringForce().apply {
- stiffness = SpringForce.STIFFNESS_MEDIUM
- }
- )
+ var verticalTranslation = AnimatedFloat("verticalTranslation")
/**
* Use for drawing debug info. Can only be set if [DEBUG]=true
@@ -136,28 +153,67 @@
}
internal fun updateArrowPaint(arrowThickness: Float) {
- // Arrow constants
+
arrowPaint.strokeWidth = arrowThickness
- arrowPaint.color =
- Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
- arrowBackgroundPaint.color = Utils.getColorAccentDefaultColor(context)
+ val isDeviceInNightTheme = resources.configuration.uiMode and
+ Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+
+ val colorControlActivated = ContextThemeWrapper(context, Theme_DeviceDefault)
+ .run {
+ val typedValue = TypedValue()
+ val a: TypedArray = obtainStyledAttributes(typedValue.data,
+ intArrayOf(android.R.attr.colorControlActivated))
+ val color = a.getColor(0, 0)
+ a.recycle()
+ color
+ }
+
+ val colorPrimary =
+ Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+
+ arrowPaint.color = Utils.getColorAccentDefaultColor(context)
+
+ arrowBackgroundPaint.color = if (isDeviceInNightTheme) {
+ colorPrimary
+ } else {
+ colorControlActivated
+ }
}
- private inner class AnimatedFloat(name: String, springForce: SpringForce) {
+ inner class AnimatedFloat(
+ name: String,
+ private val minimumVisibleChange: Float? = null,
+ private val minimumValue: Float? = null,
+ private val maximumValue: Float? = null,
+ ) {
+
// The resting position when not stretched by a touch drag
private var restingPosition = 0f
// The current position as updated by the SpringAnimation
var pos = 0f
- set(v) {
+ private set(v) {
if (field != v) {
field = v
invalidate()
}
}
- val animation: SpringAnimation
+ private val animation: SpringAnimation
+ var spring: SpringForce
+ get() = animation.spring
+ set(value) {
+ animation.cancel()
+ animation.spring = value
+ }
+
+ val isRunning: Boolean
+ get() = animation.isRunning
+
+ fun addEndListener(listener: DelayedOnAnimationEndListener) {
+ animation.addEndListener(listener)
+ }
init {
val floatProp = object : FloatPropertyCompat<AnimatedFloat>(name) {
@@ -167,8 +223,12 @@
override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos
}
- animation = SpringAnimation(this, floatProp)
- animation.spring = springForce
+ animation = SpringAnimation(this, floatProp).apply {
+ spring = SpringForce()
+ this@AnimatedFloat.minimumValue?.let { setMinValue(it) }
+ this@AnimatedFloat.maximumValue?.let { setMaxValue(it) }
+ this@AnimatedFloat.minimumVisibleChange?.let { minimumVisibleChange = it }
+ }
}
fun snapTo(newPosition: Float) {
@@ -178,8 +238,24 @@
pos = newPosition
}
- fun stretchTo(stretchAmount: Float) {
- animation.animateToFinalPosition(restingPosition + stretchAmount)
+ fun snapToRestingPosition() {
+ snapTo(restingPosition)
+ }
+
+
+ fun stretchTo(
+ stretchAmount: Float,
+ startingVelocity: Float? = null,
+ springForce: SpringForce? = null
+ ) {
+ animation.apply {
+ startingVelocity?.let {
+ cancel()
+ setStartVelocity(it)
+ }
+ springForce?.let { spring = springForce }
+ animateToFinalPosition(restingPosition + stretchAmount)
+ }
}
/**
@@ -188,18 +264,23 @@
*
* The [restingPosition] will remain unchanged. Only the animation is updated.
*/
- fun stretchBy(finalPosition: Float, amount: Float) {
- val stretchedAmount = amount * (finalPosition - restingPosition)
+ fun stretchBy(finalPosition: Float?, amount: Float) {
+ val stretchedAmount = amount * ((finalPosition ?: 0f) - restingPosition)
animation.animateToFinalPosition(restingPosition + stretchedAmount)
}
- fun updateRestingPosition(pos: Float, animated: Boolean) {
+ fun updateRestingPosition(pos: Float?, animated: Boolean = true) {
+ if (pos == null) return
+
restingPosition = pos
- if (animated)
+ if (animated) {
animation.animateToFinalPosition(restingPosition)
- else
+ } else {
snapTo(restingPosition)
+ }
}
+
+ fun cancel() = animation.cancel()
}
init {
@@ -224,126 +305,203 @@
return arrowPath
}
- fun addEndListener(endListener: DelayedOnAnimationEndListener): Boolean {
- return if (alphaAnimation.isRunning) {
- alphaAnimation.addEndListener(endListener)
- true
- } else if (horizontalTranslation.animation.isRunning) {
- horizontalTranslation.animation.addEndListener(endListener)
+ fun addAnimationEndListener(
+ animatedFloat: AnimatedFloat,
+ endListener: DelayedOnAnimationEndListener
+ ): Boolean {
+ return if (animatedFloat.isRunning) {
+ animatedFloat.addEndListener(endListener)
true
} else {
- endListener.runNow()
+ endListener.run()
false
}
}
+ fun cancelAnimations() {
+ allAnimatedFloat.forEach { it.cancel() }
+ }
+
fun setStretch(
- horizontalTranslationStretchAmount: Float,
- arrowStretchAmount: Float,
- backgroundWidthStretchAmount: Float,
- fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens
+ horizontalTranslationStretchAmount: Float,
+ arrowStretchAmount: Float,
+ arrowAlphaStretchAmount: Float,
+ backgroundAlphaStretchAmount: Float,
+ backgroundWidthStretchAmount: Float,
+ backgroundHeightStretchAmount: Float,
+ edgeCornerStretchAmount: Float,
+ farCornerStretchAmount: Float,
+ fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens
) {
horizontalTranslation.stretchBy(
- finalPosition = fullyStretchedDimens.horizontalTranslation,
- amount = horizontalTranslationStretchAmount
+ finalPosition = fullyStretchedDimens.horizontalTranslation,
+ amount = horizontalTranslationStretchAmount
)
arrowLength.stretchBy(
- finalPosition = fullyStretchedDimens.arrowDimens.length,
- amount = arrowStretchAmount
+ finalPosition = fullyStretchedDimens.arrowDimens.length,
+ amount = arrowStretchAmount
)
arrowHeight.stretchBy(
- finalPosition = fullyStretchedDimens.arrowDimens.height,
- amount = arrowStretchAmount
+ finalPosition = fullyStretchedDimens.arrowDimens.height,
+ amount = arrowStretchAmount
+ )
+ arrowAlpha.stretchBy(
+ finalPosition = fullyStretchedDimens.arrowDimens.alpha,
+ amount = arrowAlphaStretchAmount
+ )
+ backgroundAlpha.stretchBy(
+ finalPosition = fullyStretchedDimens.backgroundDimens.alpha,
+ amount = backgroundAlphaStretchAmount
)
backgroundWidth.stretchBy(
- finalPosition = fullyStretchedDimens.backgroundDimens.width,
- amount = backgroundWidthStretchAmount
+ finalPosition = fullyStretchedDimens.backgroundDimens.width,
+ amount = backgroundWidthStretchAmount
+ )
+ backgroundHeight.stretchBy(
+ finalPosition = fullyStretchedDimens.backgroundDimens.height,
+ amount = backgroundHeightStretchAmount
+ )
+ backgroundEdgeCornerRadius.stretchBy(
+ finalPosition = fullyStretchedDimens.backgroundDimens.edgeCornerRadius,
+ amount = edgeCornerStretchAmount
+ )
+ backgroundFarCornerRadius.stretchBy(
+ finalPosition = fullyStretchedDimens.backgroundDimens.farCornerRadius,
+ amount = farCornerStretchAmount
)
}
+ fun popOffEdge(startingVelocity: Float) {
+ val heightStretchAmount = startingVelocity * 50
+ val widthStretchAmount = startingVelocity * 150
+ val scaleStretchAmount = startingVelocity * 0.8f
+ backgroundHeight.stretchTo(stretchAmount = 0f, startingVelocity = -heightStretchAmount)
+ backgroundWidth.stretchTo(stretchAmount = 0f, startingVelocity = widthStretchAmount)
+ scale.stretchTo(stretchAmount = 0f, startingVelocity = -scaleStretchAmount)
+ }
+
+ fun popScale(startingVelocity: Float) {
+ scalePivotX.snapTo(backgroundWidth.pos / 2)
+ scale.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity)
+ }
+
+ fun popArrowAlpha(startingVelocity: Float, springForce: SpringForce? = null) {
+ arrowAlpha.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity,
+ springForce = springForce)
+ }
+
fun resetStretch() {
- horizontalTranslation.stretchTo(0f)
- arrowLength.stretchTo(0f)
- arrowHeight.stretchTo(0f)
- backgroundWidth.stretchTo(0f)
- backgroundHeight.stretchTo(0f)
- backgroundEdgeCornerRadius.stretchTo(0f)
- backgroundFarCornerRadius.stretchTo(0f)
+ backgroundAlpha.snapTo(1f)
+ verticalTranslation.snapTo(0f)
+ scale.snapTo(1f)
+
+ horizontalTranslation.snapToRestingPosition()
+ arrowLength.snapToRestingPosition()
+ arrowHeight.snapToRestingPosition()
+ arrowAlpha.snapToRestingPosition()
+ backgroundWidth.snapToRestingPosition()
+ backgroundHeight.snapToRestingPosition()
+ backgroundEdgeCornerRadius.snapToRestingPosition()
+ backgroundFarCornerRadius.snapToRestingPosition()
}
/**
* Updates resting arrow and background size not accounting for stretch
*/
internal fun setRestingDimens(
- restingParams: EdgePanelParams.BackIndicatorDimens,
- animate: Boolean
+ restingParams: EdgePanelParams.BackIndicatorDimens,
+ animate: Boolean = true
) {
- horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation, animate)
+ horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation)
+ scale.updateRestingPosition(restingParams.scale)
+ arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha)
+ backgroundAlpha.updateRestingPosition(restingParams.backgroundDimens.alpha)
+
arrowLength.updateRestingPosition(restingParams.arrowDimens.length, animate)
arrowHeight.updateRestingPosition(restingParams.arrowDimens.height, animate)
+ scalePivotX.updateRestingPosition(restingParams.backgroundDimens.width, animate)
backgroundWidth.updateRestingPosition(restingParams.backgroundDimens.width, animate)
backgroundHeight.updateRestingPosition(restingParams.backgroundDimens.height, animate)
backgroundEdgeCornerRadius.updateRestingPosition(
- restingParams.backgroundDimens.edgeCornerRadius,
- animate
+ restingParams.backgroundDimens.edgeCornerRadius, animate
)
backgroundFarCornerRadius.updateRestingPosition(
- restingParams.backgroundDimens.farCornerRadius,
- animate
+ restingParams.backgroundDimens.farCornerRadius, animate
)
}
fun animateVertically(yPos: Float) = verticalTranslation.stretchTo(yPos)
- fun setArrowStiffness(arrowStiffness: Float, arrowDampingRatio: Float) {
- arrowLength.animation.spring.apply {
- stiffness = arrowStiffness
- dampingRatio = arrowDampingRatio
- }
- arrowHeight.animation.spring.apply {
- stiffness = arrowStiffness
- dampingRatio = arrowDampingRatio
- }
+ fun setSpring(
+ horizontalTranslation: SpringForce? = null,
+ verticalTranslation: SpringForce? = null,
+ scale: SpringForce? = null,
+ arrowLength: SpringForce? = null,
+ arrowHeight: SpringForce? = null,
+ arrowAlpha: SpringForce? = null,
+ backgroundAlpha: SpringForce? = null,
+ backgroundFarCornerRadius: SpringForce? = null,
+ backgroundEdgeCornerRadius: SpringForce? = null,
+ backgroundWidth: SpringForce? = null,
+ backgroundHeight: SpringForce? = null,
+ ) {
+ arrowLength?.let { this.arrowLength.spring = it }
+ arrowHeight?.let { this.arrowHeight.spring = it }
+ arrowAlpha?.let { this.arrowAlpha.spring = it }
+ backgroundAlpha?.let { this.backgroundAlpha.spring = it }
+ backgroundFarCornerRadius?.let { this.backgroundFarCornerRadius.spring = it }
+ backgroundEdgeCornerRadius?.let { this.backgroundEdgeCornerRadius.spring = it }
+ scale?.let { this.scale.spring = it }
+ backgroundWidth?.let { this.backgroundWidth.spring = it }
+ backgroundHeight?.let { this.backgroundHeight.spring = it }
+ horizontalTranslation?.let { this.horizontalTranslation.spring = it }
+ verticalTranslation?.let { this.verticalTranslation.spring = it }
}
override fun hasOverlappingRendering() = false
override fun onDraw(canvas: Canvas) {
- var edgeCorner = backgroundEdgeCornerRadius.pos
+ val edgeCorner = backgroundEdgeCornerRadius.pos
val farCorner = backgroundFarCornerRadius.pos
val halfHeight = backgroundHeight.pos / 2
+ val canvasWidth = width
+ val backgroundWidth = backgroundWidth.pos
+ val scalePivotX = scalePivotX.pos
canvas.save()
- if (!isLeftPanel) canvas.scale(-1f, 1f, width / 2.0f, 0f)
+ if (!isLeftPanel) canvas.scale(-1f, 1f, canvasWidth / 2.0f, 0f)
canvas.translate(
- horizontalTranslation.pos,
- height * 0.5f + verticalTranslation.pos
+ horizontalTranslation.pos,
+ height * 0.5f + verticalTranslation.pos
)
+ canvas.scale(scale.pos, scale.pos, scalePivotX, 0f)
+
val arrowBackground = arrowBackgroundRect.apply {
left = 0f
top = -halfHeight
- right = backgroundWidth.pos
+ right = backgroundWidth
bottom = halfHeight
}.toPathWithRoundCorners(
- topLeft = edgeCorner,
- bottomLeft = edgeCorner,
- topRight = farCorner,
- bottomRight = farCorner
+ topLeft = edgeCorner,
+ bottomLeft = edgeCorner,
+ topRight = farCorner,
+ bottomRight = farCorner
)
- canvas.drawPath(arrowBackground, arrowBackgroundPaint)
+ canvas.drawPath(arrowBackground,
+ arrowBackgroundPaint.apply { alpha = (255 * backgroundAlpha.pos).toInt() })
val dx = arrowLength.pos
val dy = arrowHeight.pos
// How far the arrow bounding box should be from the edge of the screen. Measured from
// either the tip or the back of the arrow, whichever is closer
- var arrowOffset = (backgroundWidth.pos - dx) / 2
+ val arrowOffset = (backgroundWidth - dx) / 2
canvas.translate(
- /* dx= */ arrowOffset,
- /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
+ /* dx= */ arrowOffset,
+ /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
)
val arrowPointsAwayFromEdge = !arrowsPointLeft.xor(isLeftPanel)
@@ -355,6 +513,8 @@
}
val arrowPath = calculateArrowPath(dx = dx, dy = dy)
+ val arrowPaint = arrowPaint
+ .apply { alpha = (255 * min(arrowAlpha.pos, backgroundAlpha.pos)).toInt() }
canvas.drawPath(arrowPath, arrowPaint)
canvas.restore()
@@ -372,26 +532,17 @@
}
private fun RectF.toPathWithRoundCorners(
- topLeft: Float = 0f,
- topRight: Float = 0f,
- bottomRight: Float = 0f,
- bottomLeft: Float = 0f
+ topLeft: Float = 0f,
+ topRight: Float = 0f,
+ bottomRight: Float = 0f,
+ bottomLeft: Float = 0f
): Path = Path().apply {
val corners = floatArrayOf(
- topLeft, topLeft,
- topRight, topRight,
- bottomRight, bottomRight,
- bottomLeft, bottomLeft
+ topLeft, topLeft,
+ topRight, topRight,
+ bottomRight, bottomRight,
+ bottomLeft, bottomLeft
)
addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW)
}
-
- fun cancelAlphaAnimations() {
- alphaAnimation.cancel()
- alpha = 1f
- }
-
- fun fadeOut() {
- alphaAnimation.animateToFinalPosition(0f)
- }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 6e927b0..367d125 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -24,18 +24,16 @@
import android.os.SystemClock
import android.os.VibrationEffect
import android.util.Log
-import android.util.MathUtils.constrain
-import android.util.MathUtils.saturate
+import android.util.MathUtils
import android.view.Gravity
import android.view.MotionEvent
import android.view.VelocityTracker
-import android.view.View
import android.view.ViewConfiguration
import android.view.WindowManager
-import android.view.animation.DecelerateInterpolator
-import android.view.animation.PathInterpolator
+import androidx.annotation.VisibleForTesting
+import androidx.core.os.postDelayed
+import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
-import androidx.dynamicanimation.animation.SpringForce
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.NavigationEdgeBackPlugin
@@ -50,58 +48,42 @@
import kotlin.math.sign
private const val TAG = "BackPanelController"
-private const val DEBUG = false
-
private const val ENABLE_FAILSAFE = true
-private const val FAILSAFE_DELAY_MS: Long = 350
+private const val PX_PER_SEC = 1000
+private const val PX_PER_MS = 1
-/**
- * The time required between the arrow-appears vibration effect and the back-committed vibration
- * effect. If the arrow is flung quickly, the phone only vibrates once. However, if the arrow is
- * held on the screen for a long time, it will vibrate a second time when the back gesture is
- * committed.
- */
-private const val GESTURE_DURATION_FOR_CLICK_MS = 400
+internal const val MIN_DURATION_ACTIVE_ANIMATION = 300L
+private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
+private const val MIN_DURATION_COMMITTED_ANIMATION = 200L
+private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
+private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L
-/**
- * The min duration arrow remains on screen during a fling event.
- */
-private const val FLING_MIN_APPEARANCE_DURATION = 235L
+private const val FAILSAFE_DELAY_MS = 350L
+private const val POP_ON_FLING_DELAY = 160L
-/**
- * The min duration arrow remains on screen during a fling event.
- */
-private const val MIN_FLING_VELOCITY = 3000
+internal val VIBRATE_ACTIVATED_EFFECT =
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
-/**
- * The amount of rubber banding we do for the vertical translation
- */
-private const val RUBBER_BAND_AMOUNT = 15
+internal val VIBRATE_DEACTIVATED_EFFECT =
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
-private const val ARROW_APPEAR_STIFFNESS = 600f
-private const val ARROW_APPEAR_DAMPING_RATIO = 0.4f
-private const val ARROW_DISAPPEAR_STIFFNESS = 1200f
-private const val ARROW_DISAPPEAR_DAMPING_RATIO = SpringForce.DAMPING_RATIO_NO_BOUNCY
+private const val DEBUG = false
-/**
- * The interpolator used to rubber band
- */
-private val RUBBER_BAND_INTERPOLATOR = PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f)
-
-private val DECELERATE_INTERPOLATOR = DecelerateInterpolator()
-
-private val DECELERATE_INTERPOLATOR_SLOW = DecelerateInterpolator(0.7f)
-
-class BackPanelController private constructor(
- context: Context,
- private val windowManager: WindowManager,
- private val viewConfiguration: ViewConfiguration,
- @Main private val mainHandler: Handler,
- private val vibratorHelper: VibratorHelper,
- private val configurationController: ConfigurationController,
- latencyTracker: LatencyTracker
-) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
+class BackPanelController internal constructor(
+ context: Context,
+ private val windowManager: WindowManager,
+ private val viewConfiguration: ViewConfiguration,
+ @Main private val mainHandler: Handler,
+ private val vibratorHelper: VibratorHelper,
+ private val configurationController: ConfigurationController,
+ private val latencyTracker: LatencyTracker
+) : ViewController<BackPanel>(
+ BackPanel(
+ context,
+ latencyTracker
+ )
+), NavigationEdgeBackPlugin {
/**
* Injectable instance to create a new BackPanelController.
@@ -110,44 +92,44 @@
* BackPanelController, and we need to match EdgeBackGestureHandler's context.
*/
class Factory @Inject constructor(
- private val windowManager: WindowManager,
- private val viewConfiguration: ViewConfiguration,
- @Main private val mainHandler: Handler,
- private val vibratorHelper: VibratorHelper,
- private val configurationController: ConfigurationController,
- private val latencyTracker: LatencyTracker
+ private val windowManager: WindowManager,
+ private val viewConfiguration: ViewConfiguration,
+ @Main private val mainHandler: Handler,
+ private val vibratorHelper: VibratorHelper,
+ private val configurationController: ConfigurationController,
+ private val latencyTracker: LatencyTracker
) {
/** Construct a [BackPanelController]. */
fun create(context: Context): BackPanelController {
val backPanelController = BackPanelController(
- context,
- windowManager,
- viewConfiguration,
- mainHandler,
- vibratorHelper,
- configurationController,
- latencyTracker
+ context,
+ windowManager,
+ viewConfiguration,
+ mainHandler,
+ vibratorHelper,
+ configurationController,
+ latencyTracker
)
backPanelController.init()
return backPanelController
}
}
- private var params: EdgePanelParams = EdgePanelParams(resources)
- private var currentState: GestureState = GestureState.GONE
+ @VisibleForTesting
+ internal var params: EdgePanelParams = EdgePanelParams(resources)
+ @VisibleForTesting
+ internal var currentState: GestureState = GestureState.GONE
private var previousState: GestureState = GestureState.GONE
- // Phone should only vibrate the first time the arrow is activated
- private var hasHapticPlayed = false
-
// Screen attributes
private lateinit var layoutParams: WindowManager.LayoutParams
private val displaySize = Point()
private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
-
+ private var previousXTranslationOnActiveOffset = 0f
private var previousXTranslation = 0f
private var totalTouchDelta = 0f
+ private var touchDeltaStartX = 0f
private var velocityTracker: VelocityTracker? = null
set(value) {
if (field != value) field?.recycle()
@@ -161,8 +143,18 @@
// The x,y position of the first touch event
private var startX = 0f
private var startY = 0f
+ private var startIsLeft: Boolean? = null
- private var gestureStartTime = 0L
+ private var gestureSinceActionDown = 0L
+ private var gestureEntryTime = 0L
+ private var gestureActiveTime = 0L
+ private var gestureInactiveOrEntryTime = 0L
+ private var gestureArrowStrokeVisibleTime = 0L
+
+ private val elapsedTimeSinceActionDown
+ get() = SystemClock.uptimeMillis() - gestureSinceActionDown
+ private val elapsedTimeSinceEntry
+ get() = SystemClock.uptimeMillis() - gestureEntryTime
// Whether the current gesture has moved a sufficiently large amount,
// so that we can unambiguously start showing the ENTRY animation
@@ -170,7 +162,7 @@
private val failsafeRunnable = Runnable { onFailsafe() }
- private enum class GestureState {
+ internal enum class GestureState {
/* Arrow is off the screen and invisible */
GONE,
@@ -191,17 +183,6 @@
/* back action currently cancelling, arrow soon to be GONE */
CANCELLED;
-
- /**
- * @return true if the current state responds to touch move events in some way (e.g. by
- * stretching the back indicator)
- */
- fun isInteractive(): Boolean {
- return when (this) {
- ENTRY, ACTIVE, INACTIVE -> true
- GONE, FLUNG, COMMITTED, CANCELLED -> false
- }
- }
}
/**
@@ -209,50 +190,43 @@
* runnable is not called if the animation is cancelled
*/
inner class DelayedOnAnimationEndListener internal constructor(
- private val handler: Handler,
- private val runnable: Runnable,
- private val minDuration: Long
+ private val handler: Handler,
+ private val runnableDelay: Long,
+ val runnable: Runnable,
) : DynamicAnimation.OnAnimationEndListener {
+
override fun onAnimationEnd(
- animation: DynamicAnimation<*>,
- canceled: Boolean,
- value: Float,
- velocity: Float
+ animation: DynamicAnimation<*>,
+ canceled: Boolean,
+ value: Float,
+ velocity: Float
) {
animation.removeEndListener(this)
+
if (!canceled) {
- // Total elapsed time of the gesture and the animation
- val totalElapsedTime = SystemClock.uptimeMillis() - gestureStartTime
+
// The delay between finishing this animation and starting the runnable
- val delay = max(0, minDuration - totalElapsedTime)
+ val delay = max(0, runnableDelay - elapsedTimeSinceEntry)
+
handler.postDelayed(runnable, delay)
}
}
- internal fun runNow() {
- runnable.run()
- }
+ internal fun run() = runnable.run()
}
- private val setCommittedEndListener =
- DelayedOnAnimationEndListener(
- mainHandler,
- { updateArrowState(GestureState.COMMITTED) },
- minDuration = FLING_MIN_APPEARANCE_DURATION
- )
+ private val onEndSetCommittedStateListener = DelayedOnAnimationEndListener(mainHandler, 0L) {
+ updateArrowState(GestureState.COMMITTED)
+ }
- private val setGoneEndListener =
- DelayedOnAnimationEndListener(
- mainHandler,
- {
+
+ private val onEndSetGoneStateListener =
+ DelayedOnAnimationEndListener(mainHandler, runnableDelay = 0L) {
cancelFailsafe()
updateArrowState(GestureState.GONE)
- },
- minDuration = 0
- )
+ }
- // Vibration
- private var vibrationTime: Long = 0
+ private val playAnimationThenSetGoneOnAlphaEnd = Runnable { playAnimationThenSetGoneEnd() }
// Minimum of the screen's width or the predefined threshold
private var fullyStretchedThreshold = 0f
@@ -279,7 +253,7 @@
updateConfiguration()
updateArrowDirection(configurationController.isLayoutRtl)
updateArrowState(GestureState.GONE, force = true)
- updateRestingArrowDimens(animated = false, currentState)
+ updateRestingArrowDimens()
configurationController.addCallback(configurationListener)
}
@@ -296,22 +270,57 @@
velocityTracker!!.addMovement(event)
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
- resetOnDown()
+ gestureSinceActionDown = SystemClock.uptimeMillis()
+ cancelAllPendingAnimations()
startX = event.x
startY = event.y
- gestureStartTime = SystemClock.uptimeMillis()
+
+ updateArrowState(GestureState.GONE)
+ updateYStartPosition(startY)
+
+ // reset animation properties
+ startIsLeft = mView.isLeftPanel
+ hasPassedDragSlop = false
+ mView.resetStretch()
}
MotionEvent.ACTION_MOVE -> {
- // only go to the ENTRY state after some minimum motion has occurred
if (dragSlopExceeded(event.x, startX)) {
handleMoveEvent(event)
}
}
MotionEvent.ACTION_UP -> {
- if (currentState == GestureState.ACTIVE) {
- updateArrowState(if (isFlung()) GestureState.FLUNG else GestureState.COMMITTED)
- } else if (currentState != GestureState.GONE) { // if invisible, skip animation
- updateArrowState(GestureState.CANCELLED)
+ when (currentState) {
+ GestureState.ENTRY -> {
+ if (isFlungAwayFromEdge(endX = event.x)) {
+ updateArrowState(GestureState.ACTIVE)
+ updateArrowState(GestureState.FLUNG)
+ } else {
+ updateArrowState(GestureState.CANCELLED)
+ }
+ }
+ GestureState.INACTIVE -> {
+ if (isFlungAwayFromEdge(endX = event.x)) {
+ mainHandler.postDelayed(MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION) {
+ updateArrowState(GestureState.ACTIVE)
+ updateArrowState(GestureState.FLUNG)
+ }
+ } else {
+ updateArrowState(GestureState.CANCELLED)
+ }
+ }
+ GestureState.ACTIVE -> {
+ if (elapsedTimeSinceEntry < MIN_DURATION_CONSIDERED_AS_FLING) {
+ updateArrowState(GestureState.FLUNG)
+ } else {
+ updateArrowState(GestureState.COMMITTED)
+ }
+ }
+ GestureState.GONE,
+ GestureState.FLUNG,
+ GestureState.COMMITTED,
+ GestureState.CANCELLED -> {
+ updateArrowState(GestureState.CANCELLED)
+ }
}
velocityTracker = null
}
@@ -325,6 +334,14 @@
}
}
+ private fun cancelAllPendingAnimations() {
+ cancelFailsafe()
+ mView.cancelAnimations()
+ mainHandler.removeCallbacks(onEndSetCommittedStateListener.runnable)
+ mainHandler.removeCallbacks(onEndSetGoneStateListener.runnable)
+ mainHandler.removeCallbacks(playAnimationThenSetGoneOnAlphaEnd)
+ }
+
/**
* Returns false until the current gesture exceeds the touch slop threshold,
* and returns true thereafter (we reset on the subsequent back gesture).
@@ -335,7 +352,7 @@
private fun dragSlopExceeded(curX: Float, startX: Float): Boolean {
if (hasPassedDragSlop) return true
- if (abs(curX - startX) > viewConfiguration.scaledTouchSlop) {
+ if (abs(curX - startX) > viewConfiguration.scaledEdgeSlop) {
// Reset the arrow to the side
updateArrowState(GestureState.ENTRY)
@@ -348,39 +365,46 @@
}
private fun updateArrowStateOnMove(yTranslation: Float, xTranslation: Float) {
- if (!currentState.isInteractive())
- return
+
+ val isWithinYActivationThreshold = xTranslation * 2 >= yTranslation
when (currentState) {
- // Check if we should transition from ENTRY to ACTIVE
- GestureState.ENTRY ->
- if (xTranslation > params.swipeTriggerThreshold) {
+ GestureState.ENTRY -> {
+ if (xTranslation > params.staticTriggerThreshold) {
updateArrowState(GestureState.ACTIVE)
}
+ }
+ GestureState.ACTIVE -> {
+ val isPastDynamicDeactivationThreshold =
+ totalTouchDelta <= params.deactivationSwipeTriggerThreshold
+ val isMinDurationElapsed =
+ elapsedTimeSinceActionDown > MIN_DURATION_ACTIVE_ANIMATION
- // Abort if we had continuous motion toward the edge for a while, OR the direction
- // in Y is bigger than X * 2
- GestureState.ACTIVE ->
- if ((totalTouchDelta < 0 && -totalTouchDelta > params.minDeltaForSwitch) ||
- (yTranslation > xTranslation * 2)
+ if (isMinDurationElapsed && (!isWithinYActivationThreshold ||
+ isPastDynamicDeactivationThreshold)
) {
updateArrowState(GestureState.INACTIVE)
}
+ }
+ GestureState.INACTIVE -> {
+ val isPastStaticThreshold =
+ xTranslation > params.staticTriggerThreshold
+ val isPastDynamicReactivationThreshold = totalTouchDelta > 0 &&
+ abs(totalTouchDelta) >=
+ params.reactivationTriggerThreshold
- // Re-activate if we had continuous motion away from the edge for a while
- GestureState.INACTIVE ->
- if (totalTouchDelta > 0 && totalTouchDelta > params.minDeltaForSwitch) {
+ if (isPastStaticThreshold &&
+ isPastDynamicReactivationThreshold &&
+ isWithinYActivationThreshold
+ ) {
updateArrowState(GestureState.ACTIVE)
}
-
- // By default assume the current direction is kept
+ }
else -> {}
}
}
private fun handleMoveEvent(event: MotionEvent) {
- if (!currentState.isInteractive())
- return
val x = event.x
val y = event.y
@@ -400,23 +424,44 @@
previousXTranslation = xTranslation
if (abs(xDelta) > 0) {
- if (sign(xDelta) == sign(totalTouchDelta)) {
+ val range =
+ params.run { deactivationSwipeTriggerThreshold..reactivationTriggerThreshold }
+ val isTouchInContinuousDirection =
+ sign(xDelta) == sign(totalTouchDelta) || totalTouchDelta in range
+
+ if (isTouchInContinuousDirection) {
// Direction has NOT changed, so keep counting the delta
totalTouchDelta += xDelta
} else {
// Direction has changed, so reset the delta
totalTouchDelta = xDelta
+ touchDeltaStartX = x
}
}
updateArrowStateOnMove(yTranslation, xTranslation)
+
when (currentState) {
- GestureState.ACTIVE ->
- stretchActiveBackIndicator(fullScreenStretchProgress(xTranslation))
- GestureState.ENTRY ->
- stretchEntryBackIndicator(preThresholdStretchProgress(xTranslation))
- GestureState.INACTIVE ->
- mView.resetStretch()
+ GestureState.ACTIVE -> {
+ stretchActiveBackIndicator(fullScreenProgress(xTranslation))
+ }
+ GestureState.ENTRY -> {
+ val progress = staticThresholdProgress(xTranslation)
+ stretchEntryBackIndicator(progress)
+
+ params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
+ mView.popArrowAlpha(0f, it.value)
+ }
+ }
+ GestureState.INACTIVE -> {
+ val progress = reactivationThresholdProgress(totalTouchDelta)
+ stretchInactiveBackIndicator(progress)
+
+ params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
+ gestureArrowStrokeVisibleTime = SystemClock.uptimeMillis()
+ mView.popArrowAlpha(0f, it.value)
+ }
+ }
else -> {}
}
@@ -427,21 +472,22 @@
private fun setVerticalTranslation(yOffset: Float) {
val yTranslation = abs(yOffset)
val maxYOffset = (mView.height - params.entryIndicator.backgroundDimens.height) / 2f
- val yProgress = saturate(yTranslation / (maxYOffset * RUBBER_BAND_AMOUNT))
- mView.animateVertically(
- RUBBER_BAND_INTERPOLATOR.getInterpolation(yProgress) * maxYOffset *
+ val rubberbandAmount = 15f
+ val yProgress = MathUtils.saturate(yTranslation / (maxYOffset * rubberbandAmount))
+ val yPosition = params.translationInterpolator.getInterpolation(yProgress) *
+ maxYOffset *
sign(yOffset)
- )
+ mView.animateVertically(yPosition)
}
/**
- * @return the relative position of the drag from the time after the arrow is activated until
+ * Tracks the relative position of the drag from the time after the arrow is activated until
* the arrow is fully stretched (between 0.0 - 1.0f)
*/
- private fun fullScreenStretchProgress(xTranslation: Float): Float {
- return saturate(
- (xTranslation - params.swipeTriggerThreshold) /
- (fullyStretchedThreshold - params.swipeTriggerThreshold)
+ private fun fullScreenProgress(xTranslation: Float): Float {
+ return MathUtils.saturate(
+ (xTranslation - previousXTranslationOnActiveOffset) /
+ (fullyStretchedThreshold - previousXTranslationOnActiveOffset)
)
}
@@ -449,26 +495,74 @@
* Tracks the relative position of the drag from the entry until the threshold where the arrow
* activates (between 0.0 - 1.0f)
*/
- private fun preThresholdStretchProgress(xTranslation: Float): Float {
- return saturate(xTranslation / params.swipeTriggerThreshold)
+ private fun staticThresholdProgress(xTranslation: Float): Float {
+ return MathUtils.saturate(xTranslation / params.staticTriggerThreshold)
+ }
+
+ private fun reactivationThresholdProgress(totalTouchDelta: Float): Float {
+ return MathUtils.saturate(totalTouchDelta / params.reactivationTriggerThreshold)
}
private fun stretchActiveBackIndicator(progress: Float) {
- val rubberBandIterpolation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
mView.setStretch(
- horizontalTranslationStretchAmount = rubberBandIterpolation,
- arrowStretchAmount = rubberBandIterpolation,
- backgroundWidthStretchAmount = DECELERATE_INTERPOLATOR_SLOW.getInterpolation(progress),
- params.fullyStretchedIndicator
+ horizontalTranslationStretchAmount = params.translationInterpolator
+ .getInterpolation(progress),
+ arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
+ backgroundWidthStretchAmount = params.activeWidthInterpolator
+ .getInterpolation(progress),
+ backgroundAlphaStretchAmount = 1f,
+ backgroundHeightStretchAmount = 1f,
+ arrowAlphaStretchAmount = 1f,
+ edgeCornerStretchAmount = 1f,
+ farCornerStretchAmount = 1f,
+ fullyStretchedDimens = params.fullyStretchedIndicator
)
}
private fun stretchEntryBackIndicator(progress: Float) {
mView.setStretch(
- horizontalTranslationStretchAmount = 0f,
- arrowStretchAmount = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress),
- backgroundWidthStretchAmount = DECELERATE_INTERPOLATOR.getInterpolation(progress),
- params.preThresholdIndicator
+ horizontalTranslationStretchAmount = 0f,
+ arrowStretchAmount = params.arrowAngleInterpolator
+ .getInterpolation(progress),
+ backgroundWidthStretchAmount = params.entryWidthInterpolator
+ .getInterpolation(progress),
+ backgroundHeightStretchAmount = params.heightInterpolator
+ .getInterpolation(progress),
+ backgroundAlphaStretchAmount = 1f,
+ arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value,
+ edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
+ farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
+ fullyStretchedDimens = params.preThresholdIndicator
+ )
+ }
+
+ private var previousPreThresholdWidthInterpolator = params.entryWidthTowardsEdgeInterpolator
+ fun preThresholdWidthStretchAmount(progress: Float): Float {
+ val interpolator = run {
+ val isPastSlop = abs(totalTouchDelta) > ViewConfiguration.get(context).scaledTouchSlop
+ if (isPastSlop) {
+ if (totalTouchDelta > 0) {
+ params.entryWidthInterpolator
+ } else params.entryWidthTowardsEdgeInterpolator
+ } else {
+ previousPreThresholdWidthInterpolator
+ }.also { previousPreThresholdWidthInterpolator = it }
+ }
+ return interpolator.getInterpolation(progress).coerceAtLeast(0f)
+ }
+
+ private fun stretchInactiveBackIndicator(progress: Float) {
+ mView.setStretch(
+ horizontalTranslationStretchAmount = 0f,
+ arrowStretchAmount = params.arrowAngleInterpolator.getInterpolation(progress),
+ backgroundWidthStretchAmount = preThresholdWidthStretchAmount(progress),
+ backgroundHeightStretchAmount = params.heightInterpolator
+ .getInterpolation(progress),
+ backgroundAlphaStretchAmount = 1f,
+ arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value,
+ edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress),
+ farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress),
+ fullyStretchedDimens = params.preThresholdIndicator
)
}
@@ -486,8 +580,7 @@
}
}
- override fun setInsets(insetLeft: Int, insetRight: Int) {
- }
+ override fun setInsets(insetLeft: Int, insetRight: Int) = Unit
override fun setBackCallback(callback: NavigationEdgeBackPlugin.BackCallback) {
backCallback = callback
@@ -498,62 +591,54 @@
windowManager.addView(mView, layoutParams)
}
- private fun isFlung() = velocityTracker!!.run {
- computeCurrentVelocity(1000)
- abs(xVelocity) > MIN_FLING_VELOCITY
+ private fun isDragAwayFromEdge(velocityPxPerSecThreshold: Int = 0) = velocityTracker!!.run {
+ computeCurrentVelocity(PX_PER_SEC)
+ val velocity = xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1)
+ velocity > velocityPxPerSecThreshold
}
- private fun playFlingBackAnimation() {
- playAnimation(setCommittedEndListener)
+ private fun isFlungAwayFromEdge(endX: Float, startX: Float = touchDeltaStartX): Boolean {
+ val minDistanceConsideredForFling = ViewConfiguration.get(context).scaledTouchSlop
+ val flingDistance = abs(endX - startX)
+ val isPastFlingVelocity = isDragAwayFromEdge(
+ velocityPxPerSecThreshold =
+ ViewConfiguration.get(context).scaledMinimumFlingVelocity)
+ return flingDistance > minDistanceConsideredForFling && isPastFlingVelocity
}
- private fun playCommitBackAnimation() {
- // Check if we should vibrate again
- if (previousState != GestureState.FLUNG) {
- velocityTracker!!.computeCurrentVelocity(1000)
- val isSlow = abs(velocityTracker!!.xVelocity) < 500
- val hasNotVibratedRecently =
- SystemClock.uptimeMillis() - vibrationTime >= GESTURE_DURATION_FOR_CLICK_MS
- if (isSlow || hasNotVibratedRecently) {
- vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK)
- }
- }
- // Dispatch the actual back trigger
- if (DEBUG) Log.d(TAG, "playCommitBackAnimation() invoked triggerBack() on backCallback")
- backCallback.triggerBack()
-
- playAnimation(setGoneEndListener)
- }
-
- private fun playCancelBackAnimation() {
- backCallback.cancelBack()
- playAnimation(setGoneEndListener)
- }
-
- /**
- * @return true if the animation is running, false otherwise. Some transitions don't animate
- */
- private fun playAnimation(endListener: DelayedOnAnimationEndListener) {
- updateRestingArrowDimens(animated = true, currentState)
-
- if (!mView.addEndListener(endListener)) {
+ private fun playHorizontalAnimationThen(onEnd: DelayedOnAnimationEndListener) {
+ updateRestingArrowDimens()
+ if (!mView.addAnimationEndListener(mView.horizontalTranslation, onEnd)) {
scheduleFailsafe()
}
}
- private fun resetOnDown() {
- hasPassedDragSlop = false
- hasHapticPlayed = false
- totalTouchDelta = 0f
- vibrationTime = 0
- cancelFailsafe()
+ private fun playAnimationThenSetGoneEnd() {
+ updateRestingArrowDimens()
+ if (!mView.addAnimationEndListener(mView.backgroundAlpha, onEndSetGoneStateListener)) {
+ scheduleFailsafe()
+ }
}
- private fun updateYPosition(touchY: Float) {
+ private fun playWithBackgroundWidthAnimation(
+ onEnd: DelayedOnAnimationEndListener,
+ delay: Long = 0L
+ ) {
+ if (delay == 0L) {
+ updateRestingArrowDimens()
+ if (!mView.addAnimationEndListener(mView.backgroundWidth, onEnd)) {
+ scheduleFailsafe()
+ }
+ } else {
+ mainHandler.postDelayed(delay) { playWithBackgroundWidthAnimation(onEnd, delay = 0L) }
+ }
+ }
+
+ private fun updateYStartPosition(touchY: Float) {
var yPosition = touchY - params.fingerOffset
yPosition = max(yPosition, params.minArrowYPosition.toFloat())
yPosition -= layoutParams.height / 2.0f
- layoutParams.y = constrain(yPosition.toInt(), 0, displaySize.y)
+ layoutParams.y = MathUtils.constrain(yPosition.toInt(), 0, displaySize.y)
}
override fun setDisplaySize(displaySize: Point) {
@@ -564,53 +649,135 @@
/**
* Updates resting arrow and background size not accounting for stretch
*/
- private fun updateRestingArrowDimens(animated: Boolean, currentState: GestureState) {
- if (animated) {
- when (currentState) {
- GestureState.ENTRY, GestureState.ACTIVE, GestureState.FLUNG ->
- mView.setArrowStiffness(ARROW_APPEAR_STIFFNESS, ARROW_APPEAR_DAMPING_RATIO)
- GestureState.CANCELLED -> mView.fadeOut()
- else ->
- mView.setArrowStiffness(
- ARROW_DISAPPEAR_STIFFNESS,
- ARROW_DISAPPEAR_DAMPING_RATIO
- )
+ private fun updateRestingArrowDimens() {
+ when (currentState) {
+ GestureState.GONE,
+ GestureState.ENTRY -> {
+ mView.setSpring(
+ arrowLength = params.entryIndicator.arrowDimens.lengthSpring,
+ arrowHeight = params.entryIndicator.arrowDimens.heightSpring,
+ arrowAlpha = params.entryIndicator.arrowDimens.alphaSpring,
+ scale = params.entryIndicator.scaleSpring,
+ verticalTranslation = params.entryIndicator.verticalTranslationSpring,
+ horizontalTranslation = params.entryIndicator.horizontalTranslationSpring,
+ backgroundAlpha = params.entryIndicator.backgroundDimens.alphaSpring,
+ backgroundWidth = params.entryIndicator.backgroundDimens.widthSpring,
+ backgroundHeight = params.entryIndicator.backgroundDimens.heightSpring,
+ backgroundEdgeCornerRadius = params.entryIndicator.backgroundDimens
+ .edgeCornerRadiusSpring,
+ backgroundFarCornerRadius = params.entryIndicator.backgroundDimens
+ .farCornerRadiusSpring,
+ )
}
+ GestureState.INACTIVE -> {
+ mView.setSpring(
+ arrowLength = params.preThresholdIndicator.arrowDimens.lengthSpring,
+ arrowHeight = params.preThresholdIndicator.arrowDimens.heightSpring,
+ horizontalTranslation = params.preThresholdIndicator
+ .horizontalTranslationSpring,
+ scale = params.preThresholdIndicator.scaleSpring,
+ backgroundWidth = params.preThresholdIndicator.backgroundDimens
+ .widthSpring,
+ backgroundHeight = params.preThresholdIndicator.backgroundDimens
+ .heightSpring,
+ backgroundEdgeCornerRadius = params.preThresholdIndicator.backgroundDimens
+ .edgeCornerRadiusSpring,
+ backgroundFarCornerRadius = params.preThresholdIndicator.backgroundDimens
+ .farCornerRadiusSpring,
+ )
+ }
+ GestureState.ACTIVE -> {
+ mView.setSpring(
+ arrowLength = params.activeIndicator.arrowDimens.lengthSpring,
+ arrowHeight = params.activeIndicator.arrowDimens.heightSpring,
+ scale = params.activeIndicator.scaleSpring,
+ horizontalTranslation = params.activeIndicator.horizontalTranslationSpring,
+ backgroundWidth = params.activeIndicator.backgroundDimens.widthSpring,
+ backgroundHeight = params.activeIndicator.backgroundDimens.heightSpring,
+ backgroundEdgeCornerRadius = params.activeIndicator.backgroundDimens
+ .edgeCornerRadiusSpring,
+ backgroundFarCornerRadius = params.activeIndicator.backgroundDimens
+ .farCornerRadiusSpring,
+ )
+ }
+ GestureState.FLUNG -> {
+ mView.setSpring(
+ arrowLength = params.flungIndicator.arrowDimens.lengthSpring,
+ arrowHeight = params.flungIndicator.arrowDimens.heightSpring,
+ backgroundWidth = params.flungIndicator.backgroundDimens.widthSpring,
+ backgroundHeight = params.flungIndicator.backgroundDimens.heightSpring,
+ backgroundEdgeCornerRadius = params.flungIndicator.backgroundDimens
+ .edgeCornerRadiusSpring,
+ backgroundFarCornerRadius = params.flungIndicator.backgroundDimens
+ .farCornerRadiusSpring,
+ )
+ }
+ GestureState.COMMITTED -> {
+ mView.setSpring(
+ arrowLength = params.committedIndicator.arrowDimens.lengthSpring,
+ arrowHeight = params.committedIndicator.arrowDimens.heightSpring,
+ scale = params.committedIndicator.scaleSpring,
+ backgroundWidth = params.committedIndicator.backgroundDimens.widthSpring,
+ backgroundHeight = params.committedIndicator.backgroundDimens.heightSpring,
+ backgroundEdgeCornerRadius = params.committedIndicator.backgroundDimens
+ .edgeCornerRadiusSpring,
+ backgroundFarCornerRadius = params.committedIndicator.backgroundDimens
+ .farCornerRadiusSpring,
+ )
+ }
+ else -> {}
}
+
mView.setRestingDimens(
- restingParams = EdgePanelParams.BackIndicatorDimens(
- horizontalTranslation = when (currentState) {
- GestureState.GONE -> -params.activeIndicator.backgroundDimens.width
- // Position the committed arrow slightly further off the screen so we do not
- // see part of it bouncing
- GestureState.COMMITTED ->
- -params.activeIndicator.backgroundDimens.width * 1.5f
- GestureState.FLUNG -> params.fullyStretchedIndicator.horizontalTranslation
- GestureState.ACTIVE -> params.activeIndicator.horizontalTranslation
- GestureState.ENTRY, GestureState.INACTIVE, GestureState.CANCELLED ->
- params.entryIndicator.horizontalTranslation
- },
- arrowDimens = when (currentState) {
- GestureState.ACTIVE, GestureState.INACTIVE,
- GestureState.COMMITTED, GestureState.FLUNG -> params.activeIndicator.arrowDimens
- GestureState.CANCELLED -> params.cancelledArrowDimens
- GestureState.GONE, GestureState.ENTRY -> params.entryIndicator.arrowDimens
- },
- backgroundDimens = when (currentState) {
- GestureState.GONE, GestureState.ENTRY -> params.entryIndicator.backgroundDimens
- else ->
- params.activeIndicator.backgroundDimens.copy(
- edgeCornerRadius =
- if (currentState == GestureState.INACTIVE ||
- currentState == GestureState.CANCELLED
- )
- params.cancelledEdgeCornerRadius
- else
- params.activeIndicator.backgroundDimens.edgeCornerRadius
- )
- }
- ),
- animate = animated
+ animate = !(currentState == GestureState.FLUNG ||
+ currentState == GestureState.COMMITTED),
+ restingParams = EdgePanelParams.BackIndicatorDimens(
+ scale = when (currentState) {
+ GestureState.ACTIVE,
+ GestureState.FLUNG,
+ -> params.activeIndicator.scale
+ GestureState.COMMITTED -> params.committedIndicator.scale
+ else -> params.preThresholdIndicator.scale
+ },
+ scalePivotX = when (currentState) {
+ GestureState.GONE,
+ GestureState.ENTRY,
+ GestureState.INACTIVE,
+ GestureState.CANCELLED -> params.preThresholdIndicator.scalePivotX
+ else -> params.committedIndicator.scalePivotX
+ },
+ horizontalTranslation = when (currentState) {
+ GestureState.GONE -> {
+ params.activeIndicator.backgroundDimens.width?.times(-1)
+ }
+ GestureState.ENTRY,
+ GestureState.INACTIVE -> params.entryIndicator.horizontalTranslation
+ GestureState.FLUNG -> params.activeIndicator.horizontalTranslation
+ GestureState.ACTIVE -> params.activeIndicator.horizontalTranslation
+ GestureState.CANCELLED -> {
+ params.cancelledIndicator.horizontalTranslation
+ }
+ else -> null
+ },
+ arrowDimens = when (currentState) {
+ GestureState.GONE,
+ GestureState.ENTRY,
+ GestureState.INACTIVE -> params.entryIndicator.arrowDimens
+ GestureState.ACTIVE -> params.activeIndicator.arrowDimens
+ GestureState.FLUNG,
+ GestureState.COMMITTED -> params.committedIndicator.arrowDimens
+ GestureState.CANCELLED -> params.cancelledIndicator.arrowDimens
+ },
+ backgroundDimens = when (currentState) {
+ GestureState.GONE,
+ GestureState.ENTRY,
+ GestureState.INACTIVE -> params.entryIndicator.backgroundDimens
+ GestureState.ACTIVE -> params.activeIndicator.backgroundDimens
+ GestureState.FLUNG -> params.activeIndicator.backgroundDimens
+ GestureState.COMMITTED -> params.committedIndicator.backgroundDimens
+ GestureState.CANCELLED -> params.cancelledIndicator.backgroundDimens
+ }
+ )
)
}
@@ -623,42 +790,123 @@
private fun updateArrowState(newState: GestureState, force: Boolean = false) {
if (!force && currentState == newState) return
- if (DEBUG) Log.d(TAG, "updateArrowState $currentState -> $newState")
previousState = currentState
currentState = newState
- if (currentState == GestureState.GONE) {
- mView.cancelAlphaAnimations()
- mView.visibility = View.GONE
- } else {
- mView.visibility = View.VISIBLE
+
+ when (currentState) {
+ GestureState.CANCELLED -> {
+ backCallback.cancelBack()
+ }
+ GestureState.FLUNG,
+ GestureState.COMMITTED -> {
+ // When flung, trigger back immediately but don't fire again
+ // once state resolves to committed.
+ if (previousState != GestureState.FLUNG) backCallback.triggerBack()
+ }
+ GestureState.ENTRY,
+ GestureState.INACTIVE -> {
+ backCallback.setTriggerBack(false)
+ }
+ GestureState.ACTIVE -> {
+ backCallback.setTriggerBack(true)
+ }
+ GestureState.GONE -> { }
}
when (currentState) {
// Transitioning to GONE never animates since the arrow is (presumably) already off the
// screen
- GestureState.GONE -> updateRestingArrowDimens(animated = false, currentState)
+ GestureState.GONE -> {
+ updateRestingArrowDimens()
+ mView.isVisible = false
+ }
GestureState.ENTRY -> {
- updateYPosition(startY)
- updateRestingArrowDimens(animated = true, currentState)
+ mView.isVisible = true
+
+ updateRestingArrowDimens()
+ gestureEntryTime = SystemClock.uptimeMillis()
+ gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
}
GestureState.ACTIVE -> {
- updateRestingArrowDimens(animated = true, currentState)
- // Vibrate the first time we transition to ACTIVE
- if (!hasHapticPlayed) {
- hasHapticPlayed = true
- vibrationTime = SystemClock.uptimeMillis()
- vibratorHelper.vibrate(VibrationEffect.EFFECT_TICK)
+ previousXTranslationOnActiveOffset = previousXTranslation
+ gestureActiveTime = SystemClock.uptimeMillis()
+
+ updateRestingArrowDimens()
+
+ vibratorHelper.cancel()
+ mainHandler.postDelayed(10L) {
+ vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT)
+ }
+
+ val startingVelocity = convertVelocityToSpringStartingVelocity(
+ valueOnFastVelocity = 0f,
+ valueOnSlowVelocity = if (previousState == GestureState.ENTRY) 2f else 4.5f
+ )
+
+ when (previousState) {
+ GestureState.ENTRY,
+ GestureState.INACTIVE -> {
+ mView.popOffEdge(startingVelocity)
+ }
+ GestureState.COMMITTED -> {
+ // if previous state was committed then this activation
+ // was due to a quick second swipe. Don't pop the arrow this time
+ }
+ else -> { }
}
}
+
GestureState.INACTIVE -> {
- updateRestingArrowDimens(animated = true, currentState)
+ gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
+
+ val startingVelocity = convertVelocityToSpringStartingVelocity(
+ valueOnFastVelocity = -1.05f,
+ valueOnSlowVelocity = -1.50f
+ )
+ mView.popOffEdge(startingVelocity)
+
+ vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT)
+ updateRestingArrowDimens()
}
- GestureState.FLUNG -> playFlingBackAnimation()
- GestureState.COMMITTED -> playCommitBackAnimation()
- GestureState.CANCELLED -> playCancelBackAnimation()
+ GestureState.FLUNG -> {
+ mainHandler.postDelayed(POP_ON_FLING_DELAY) { mView.popScale(1.9f) }
+ playHorizontalAnimationThen(onEndSetCommittedStateListener)
+ }
+ GestureState.COMMITTED -> {
+ if (previousState == GestureState.FLUNG) {
+ playAnimationThenSetGoneEnd()
+ } else {
+ mView.popScale(3f)
+ mainHandler.postDelayed(
+ playAnimationThenSetGoneOnAlphaEnd,
+ MIN_DURATION_COMMITTED_ANIMATION
+ )
+ }
+ }
+ GestureState.CANCELLED -> {
+ val delay = max(0, MIN_DURATION_CANCELLED_ANIMATION - elapsedTimeSinceEntry)
+ playWithBackgroundWidthAnimation(onEndSetGoneStateListener, delay)
+
+ params.arrowStrokeAlphaSpring.get(0f).takeIf { it.isNewState }?.let {
+ mView.popArrowAlpha(0f, it.value)
+ }
+ mainHandler.postDelayed(10L) { vibratorHelper.cancel() }
+ }
}
}
+ private fun convertVelocityToSpringStartingVelocity(
+ valueOnFastVelocity: Float,
+ valueOnSlowVelocity: Float,
+ ): Float {
+ val factor = velocityTracker?.run {
+ computeCurrentVelocity(PX_PER_MS)
+ MathUtils.smoothStep(0f, 3f, abs(xVelocity))
+ } ?: valueOnFastVelocity
+
+ return MathUtils.lerp(valueOnFastVelocity, valueOnSlowVelocity, 1 - factor)
+ }
+
private fun scheduleFailsafe() {
if (!ENABLE_FAILSAFE) return
cancelFailsafe()
@@ -685,24 +933,24 @@
init {
if (DEBUG) mView.drawDebugInfo = { canvas ->
val debugStrings = listOf(
- "$currentState",
- "startX=$startX",
- "startY=$startY",
- "xDelta=${"%.1f".format(totalTouchDelta)}",
- "xTranslation=${"%.1f".format(previousXTranslation)}",
- "pre=${"%.0f".format(preThresholdStretchProgress(previousXTranslation) * 100)}%",
- "post=${"%.0f".format(fullScreenStretchProgress(previousXTranslation) * 100)}%"
+ "$currentState",
+ "startX=$startX",
+ "startY=$startY",
+ "xDelta=${"%.1f".format(totalTouchDelta)}",
+ "xTranslation=${"%.1f".format(previousXTranslation)}",
+ "pre=${"%.0f".format(staticThresholdProgress(previousXTranslation) * 100)}%",
+ "post=${"%.0f".format(fullScreenProgress(previousXTranslation) * 100)}%"
)
val debugPaint = Paint().apply {
color = Color.WHITE
}
val debugInfoBottom = debugStrings.size * 32f + 4f
canvas.drawRect(
- 4f,
- 4f,
- canvas.width.toFloat(),
- debugStrings.size * 32f + 4f,
- debugPaint
+ 4f,
+ 4f,
+ canvas.width.toFloat(),
+ debugStrings.size * 32f + 4f,
+ debugPaint
)
debugPaint.apply {
color = Color.BLACK
@@ -728,9 +976,71 @@
canvas.drawLine(x, debugInfoBottom, x, canvas.height.toFloat(), debugPaint)
}
- drawVerticalLine(x = params.swipeTriggerThreshold, color = Color.BLUE)
+ drawVerticalLine(x = params.staticTriggerThreshold, color = Color.BLUE)
+ drawVerticalLine(x = params.deactivationSwipeTriggerThreshold, color = Color.BLUE)
drawVerticalLine(x = startX, color = Color.GREEN)
drawVerticalLine(x = previousXTranslation, color = Color.DKGRAY)
}
}
}
+
+/**
+ * In addition to a typical step function which returns one or two
+ * values based on a threshold, `Step` also gracefully handles quick
+ * changes in input near the threshold value that would typically
+ * result in the output rapidly changing.
+ *
+ * In the context of Back arrow, the arrow's stroke opacity should
+ * always appear transparent or opaque. Using a typical Step function,
+ * this would resulting in a flickering appearance as the output would
+ * change rapidly. `Step` addresses this by moving the threshold after
+ * it is crossed so it cannot be easily crossed again with small changes
+ * in touch events.
+ */
+class Step<T>(
+ private val threshold: Float,
+ private val factor: Float = 1.1f,
+ private val postThreshold: T,
+ private val preThreshold: T
+) {
+
+ data class Value<T>(val value: T, val isNewState: Boolean)
+
+ private val lowerFactor = 2 - factor
+
+ private lateinit var startValue: Value<T>
+ private lateinit var previousValue: Value<T>
+ private var hasCrossedUpperBoundAtLeastOnce = false
+ private var progress: Float = 0f
+
+ init {
+ reset()
+ }
+
+ fun reset() {
+ hasCrossedUpperBoundAtLeastOnce = false
+ progress = 0f
+ startValue = Value(preThreshold, false)
+ previousValue = startValue
+ }
+
+ fun get(progress: Float): Value<T> {
+ this.progress = progress
+
+ val hasCrossedUpperBound = progress > threshold * factor
+ val hasCrossedLowerBound = progress > threshold * lowerFactor
+
+ return when {
+ hasCrossedUpperBound && !hasCrossedUpperBoundAtLeastOnce -> {
+ hasCrossedUpperBoundAtLeastOnce = true
+ Value(postThreshold, true)
+ }
+ hasCrossedLowerBound -> previousValue.copy(isNewState = false)
+ hasCrossedUpperBoundAtLeastOnce -> {
+ hasCrossedUpperBoundAtLeastOnce = false
+ Value(preThreshold, true)
+ }
+ else -> startValue
+ }.also { previousValue = it }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index d56537b..0c00022 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -1,52 +1,82 @@
package com.android.systemui.navigationbar.gestural
import android.content.res.Resources
+import android.util.TypedValue
+import androidx.core.animation.PathInterpolator
+import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.R
data class EdgePanelParams(private var resources: Resources) {
data class ArrowDimens(
- val length: Float = 0f,
- val height: Float = 0f
+ val length: Float = 0f,
+ val height: Float = 0f,
+ val alpha: Float = 0f,
+ var alphaSpring: SpringForce? = null,
+ val heightSpring: SpringForce? = null,
+ val lengthSpring: SpringForce? = null,
)
data class BackgroundDimens(
- val width: Float = 0f,
- val height: Float = 0f,
- val edgeCornerRadius: Float = 0f,
- val farCornerRadius: Float = 0f
+ val width: Float? = 0f,
+ val height: Float = 0f,
+ val edgeCornerRadius: Float = 0f,
+ val farCornerRadius: Float = 0f,
+ val alpha: Float = 0f,
+ val widthSpring: SpringForce? = null,
+ val heightSpring: SpringForce? = null,
+ val farCornerRadiusSpring: SpringForce? = null,
+ val edgeCornerRadiusSpring: SpringForce? = null,
+ val alphaSpring: SpringForce? = null,
)
data class BackIndicatorDimens(
- val horizontalTranslation: Float = 0f,
- val arrowDimens: ArrowDimens = ArrowDimens(),
- val backgroundDimens: BackgroundDimens = BackgroundDimens()
+ val horizontalTranslation: Float? = 0f,
+ val scale: Float = 0f,
+ val scalePivotX: Float = 0f,
+ val arrowDimens: ArrowDimens,
+ val backgroundDimens: BackgroundDimens,
+ val verticalTranslationSpring: SpringForce? = null,
+ val horizontalTranslationSpring: SpringForce? = null,
+ val scaleSpring: SpringForce? = null,
)
- var arrowThickness: Float = 0f
+ lateinit var entryIndicator: BackIndicatorDimens
private set
- var entryIndicator = BackIndicatorDimens()
+ lateinit var activeIndicator: BackIndicatorDimens
private set
- var activeIndicator = BackIndicatorDimens()
+ lateinit var cancelledIndicator: BackIndicatorDimens
private set
- var preThresholdIndicator = BackIndicatorDimens()
+ lateinit var flungIndicator: BackIndicatorDimens
private set
- var fullyStretchedIndicator = BackIndicatorDimens()
+ lateinit var committedIndicator: BackIndicatorDimens
private set
- var cancelledEdgeCornerRadius: Float = 0f
+ lateinit var preThresholdIndicator: BackIndicatorDimens
private set
- var cancelledArrowDimens = ArrowDimens()
+ lateinit var fullyStretchedIndicator: BackIndicatorDimens
+ private set
// navigation bar edge constants
var arrowPaddingEnd: Int = 0
private set
+ var arrowThickness: Float = 0f
+ private set
+ lateinit var arrowStrokeAlphaSpring: Step<SpringForce>
+ private set
+ lateinit var arrowStrokeAlphaInterpolator: Step<Float>
+ private set
// The closest to y
var minArrowYPosition: Int = 0
private set
var fingerOffset: Int = 0
private set
- var swipeTriggerThreshold: Float = 0f
+ var staticTriggerThreshold: Float = 0f
+ private set
+ var reactivationTriggerThreshold: Float = 0f
+ private set
+ var deactivationSwipeTriggerThreshold: Float = 0f
+ get() = -field
private set
var swipeProgressThreshold: Float = 0f
private set
@@ -55,6 +85,26 @@
var minDeltaForSwitch: Int = 0
private set
+ var minDragToStartAnimation: Float = 0f
+ private set
+
+ lateinit var entryWidthInterpolator: PathInterpolator
+ private set
+ lateinit var entryWidthTowardsEdgeInterpolator: PathInterpolator
+ private set
+ lateinit var activeWidthInterpolator: PathInterpolator
+ private set
+ lateinit var arrowAngleInterpolator: PathInterpolator
+ private set
+ lateinit var translationInterpolator: PathInterpolator
+ private set
+ lateinit var farCornerInterpolator: PathInterpolator
+ private set
+ lateinit var edgeCornerInterpolator: PathInterpolator
+ private set
+ lateinit var heightInterpolator: PathInterpolator
+ private set
+
init {
update(resources)
}
@@ -63,6 +113,10 @@
return resources.getDimension(id)
}
+ private fun getDimenFloat(id: Int): Float {
+ return TypedValue().run { resources.getValue(id, this, true); float }
+ }
+
private fun getPx(id: Int): Int {
return resources.getDimensionPixelSize(id)
}
@@ -73,72 +127,200 @@
arrowPaddingEnd = getPx(R.dimen.navigation_edge_panel_padding)
minArrowYPosition = getPx(R.dimen.navigation_edge_arrow_min_y)
fingerOffset = getPx(R.dimen.navigation_edge_finger_offset)
- swipeTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold)
+ staticTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold)
+ reactivationTriggerThreshold =
+ getDimen(R.dimen.navigation_edge_action_reactivation_drag_threshold)
+ deactivationSwipeTriggerThreshold =
+ getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold)
swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold)
minDeltaForSwitch = getPx(R.dimen.navigation_edge_minimum_x_delta_for_switch)
+ minDragToStartAnimation =
+ getDimen(R.dimen.navigation_edge_action_min_distance_to_start_animation)
+
+ entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
+ entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f)
+ activeWidthInterpolator = PathInterpolator(.15f, .48f, .46f, .89f)
+ arrowAngleInterpolator = entryWidthInterpolator
+ translationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f)
+ farCornerInterpolator = PathInterpolator(.03f, .19f, .14f, 1.09f)
+ edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f)
+ heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f)
+
+ val showArrowOnProgressValue = .2f
+ val showArrowOnProgressValueFactor = 1.05f
+
+ val entryActiveHorizontalTranslationSpring = createSpring(675f, 0.8f)
+ val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f)
+ val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f)
+ val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f)
+ val flungCommittedFarCornerSpring = createSpring(10000f, 1f)
+ val flungCommittedWidthSpring = createSpring(10000f, 1f)
+ val flungCommittedHeightSpring = createSpring(10000f, 1f)
entryIndicator = BackIndicatorDimens(
- horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
- arrowDimens = ArrowDimens(
- length = getDimen(R.dimen.navigation_edge_entry_arrow_length),
- height = getDimen(R.dimen.navigation_edge_entry_arrow_height),
- ),
- backgroundDimens = BackgroundDimens(
- width = getDimen(R.dimen.navigation_edge_entry_background_width),
- height = getDimen(R.dimen.navigation_edge_entry_background_height),
- edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners),
- farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners)
- )
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
+ scale = getDimenFloat(R.dimen.navigation_edge_entry_scale),
+ scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
+ horizontalTranslationSpring = entryActiveHorizontalTranslationSpring,
+ verticalTranslationSpring = createSpring(10000f, 0.9f),
+ scaleSpring = createSpring(120f, 0.8f),
+ arrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_entry_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_entry_arrow_height),
+ alpha = 0f,
+ alphaSpring = createSpring(200f, 1f),
+ lengthSpring = createSpring(600f, 0.4f),
+ heightSpring = createSpring(600f, 0.4f),
+ ),
+ backgroundDimens = BackgroundDimens(
+ alpha = 1f,
+ width = getDimen(R.dimen.navigation_edge_entry_background_width),
+ height = getDimen(R.dimen.navigation_edge_entry_background_height),
+ edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners),
+ farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners),
+ alphaSpring = createSpring(900f, 1f),
+ widthSpring = createSpring(450f, 0.65f),
+ heightSpring = createSpring(1500f, 0.45f),
+ farCornerRadiusSpring = createSpring(300f, 0.5f),
+ edgeCornerRadiusSpring = createSpring(150f, 0.5f),
+ )
)
activeIndicator = BackIndicatorDimens(
- horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
- arrowDimens = ArrowDimens(
- length = getDimen(R.dimen.navigation_edge_active_arrow_length),
- height = getDimen(R.dimen.navigation_edge_active_arrow_height),
- ),
- backgroundDimens = BackgroundDimens(
- width = getDimen(R.dimen.navigation_edge_active_background_width),
- height = getDimen(R.dimen.navigation_edge_active_background_height),
- edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners),
- farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners)
-
- )
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
+ scale = getDimenFloat(R.dimen.navigation_edge_active_scale),
+ horizontalTranslationSpring = entryActiveHorizontalTranslationSpring,
+ scaleSpring = createSpring(450f, 0.415f),
+ arrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_active_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_active_arrow_height),
+ alpha = 1f,
+ lengthSpring = activeCommittedArrowLengthSpring,
+ heightSpring = activeCommittedArrowHeightSpring,
+ ),
+ backgroundDimens = BackgroundDimens(
+ alpha = 1f,
+ width = getDimen(R.dimen.navigation_edge_active_background_width),
+ height = getDimen(R.dimen.navigation_edge_active_background_height),
+ edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners),
+ farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners),
+ widthSpring = createSpring(375f, 0.675f),
+ heightSpring = createSpring(10000f, 1f),
+ edgeCornerRadiusSpring = createSpring(600f, 0.36f),
+ farCornerRadiusSpring = createSpring(2500f, 0.855f),
+ )
)
preThresholdIndicator = BackIndicatorDimens(
- horizontalTranslation = getDimen(R.dimen.navigation_edge_pre_threshold_margin),
- arrowDimens = ArrowDimens(
- length = entryIndicator.arrowDimens.length,
- height = entryIndicator.arrowDimens.height,
- ),
- backgroundDimens = BackgroundDimens(
- width = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
- height = getDimen(R.dimen.navigation_edge_pre_threshold_background_height),
- edgeCornerRadius = getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
- farCornerRadius = getDimen(R.dimen.navigation_edge_pre_threshold_far_corners)
- )
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_pre_threshold_margin),
+ scale = getDimenFloat(R.dimen.navigation_edge_pre_threshold_scale),
+ scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
+ scaleSpring = createSpring(120f, 0.8f),
+ horizontalTranslationSpring = createSpring(6000f, 1f),
+ arrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_height),
+ alpha = 1f,
+ lengthSpring = createSpring(100f, 0.6f),
+ heightSpring = createSpring(100f, 0.6f),
+ ),
+ backgroundDimens = BackgroundDimens(
+ alpha = 1f,
+ width = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
+ height = getDimen(R.dimen.navigation_edge_pre_threshold_background_height),
+ edgeCornerRadius =
+ getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
+ farCornerRadius =
+ getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
+ widthSpring = createSpring(200f, 0.65f),
+ heightSpring = createSpring(1500f, 0.45f),
+ farCornerRadiusSpring = createSpring(200f, 1f),
+ edgeCornerRadiusSpring = createSpring(150f, 0.5f),
+ )
+ )
+
+ committedIndicator = activeIndicator.copy(
+ horizontalTranslation = null,
+ arrowDimens = activeIndicator.arrowDimens.copy(
+ lengthSpring = activeCommittedArrowLengthSpring,
+ heightSpring = activeCommittedArrowHeightSpring,
+ ),
+ backgroundDimens = activeIndicator.backgroundDimens.copy(
+ alpha = 0f,
+ // explicitly set to null to preserve previous width upon state change
+ width = null,
+ widthSpring = flungCommittedWidthSpring,
+ heightSpring = flungCommittedHeightSpring,
+ edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
+ farCornerRadiusSpring = flungCommittedFarCornerSpring,
+ ),
+ scale = 0.85f,
+ scaleSpring = createSpring(650f, 1f),
+ )
+
+ flungIndicator = committedIndicator.copy(
+ arrowDimens = committedIndicator.arrowDimens.copy(
+ lengthSpring = createSpring(850f, 0.46f),
+ heightSpring = createSpring(850f, 0.46f),
+ ),
+ backgroundDimens = committedIndicator.backgroundDimens.copy(
+ widthSpring = flungCommittedWidthSpring,
+ heightSpring = flungCommittedHeightSpring,
+ edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
+ farCornerRadiusSpring = flungCommittedFarCornerSpring,
+ )
+ )
+
+ cancelledIndicator = entryIndicator.copy(
+ backgroundDimens = entryIndicator.backgroundDimens.copy(width = 0f)
)
fullyStretchedIndicator = BackIndicatorDimens(
- horizontalTranslation = getDimen(R.dimen.navigation_edge_stretch_margin),
- arrowDimens = ArrowDimens(
- length = getDimen(R.dimen.navigation_edge_stretched_arrow_length),
- height = getDimen(R.dimen.navigation_edge_stretched_arrow_height),
- ),
- backgroundDimens = BackgroundDimens(
- width = getDimen(R.dimen.navigation_edge_stretch_background_width),
- height = getDimen(R.dimen.navigation_edge_stretch_background_height),
- edgeCornerRadius = getDimen(R.dimen.navigation_edge_stretch_edge_corners),
- farCornerRadius = getDimen(R.dimen.navigation_edge_stretch_far_corners)
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_stretch_margin),
+ scale = getDimenFloat(R.dimen.navigation_edge_stretch_scale),
+ horizontalTranslationSpring = null,
+ verticalTranslationSpring = null,
+ scaleSpring = null,
+ arrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_stretched_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_stretched_arrow_height),
+ alpha = 1f,
+ alphaSpring = null,
+ heightSpring = null,
+ lengthSpring = null,
+ ),
+ backgroundDimens = BackgroundDimens(
+ alpha = 1f,
+ width = getDimen(R.dimen.navigation_edge_stretch_background_width),
+ height = getDimen(R.dimen.navigation_edge_stretch_background_height),
+ edgeCornerRadius = getDimen(R.dimen.navigation_edge_stretch_edge_corners),
+ farCornerRadius = getDimen(R.dimen.navigation_edge_stretch_far_corners),
+ alphaSpring = null,
+ widthSpring = null,
+ heightSpring = null,
+ edgeCornerRadiusSpring = null,
+ farCornerRadiusSpring = null,
+ )
+ )
+
+ arrowStrokeAlphaInterpolator = Step(
+ threshold = showArrowOnProgressValue,
+ factor = showArrowOnProgressValueFactor,
+ postThreshold = 1f,
+ preThreshold = 0f
+ )
+
+ entryIndicator.arrowDimens.alphaSpring?.let { alphaSpring ->
+ arrowStrokeAlphaSpring = Step(
+ threshold = showArrowOnProgressValue,
+ factor = showArrowOnProgressValueFactor,
+ postThreshold = alphaSpring,
+ preThreshold = SpringForce().setStiffness(2000f).setDampingRatio(1f)
)
- )
-
- cancelledEdgeCornerRadius = getDimen(R.dimen.navigation_edge_cancelled_edge_corners)
-
- cancelledArrowDimens = ArrowDimens(
- length = getDimen(R.dimen.navigation_edge_cancelled_arrow_length),
- height = getDimen(R.dimen.navigation_edge_cancelled_arrow_height)
- )
+ }
}
}
+
+fun createSpring(stiffness: Float, dampingRatio: Float): SpringForce {
+ return SpringForce().setStiffness(stiffness).setDampingRatio(dampingRatio)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 6bfe1a0..be615d6 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -20,9 +20,12 @@
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
+import android.content.Intent
import android.content.pm.PackageManager
import android.os.UserManager
import android.util.Log
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.util.kotlin.getOrNull
@@ -42,11 +45,12 @@
@Inject
constructor(
private val context: Context,
- private val intentResolver: NoteTaskIntentResolver,
+ private val resolver: NoteTaskInfoResolver,
private val optionalBubbles: Optional<Bubbles>,
private val optionalKeyguardManager: Optional<KeyguardManager>,
private val optionalUserManager: Optional<UserManager>,
@NoteTaskEnabledKey private val isEnabled: Boolean,
+ private val uiEventLogger: UiEventLogger,
) {
/**
@@ -64,7 +68,9 @@
*
* That will let users open other apps in full screen, and take contextual notes.
*/
- fun showNoteTask(isInMultiWindowMode: Boolean = false) {
+ @JvmOverloads
+ fun showNoteTask(isInMultiWindowMode: Boolean = false, uiEvent: ShowNoteTaskUiEvent? = null) {
+
if (!isEnabled) return
val bubbles = optionalBubbles.getOrNull() ?: return
@@ -74,9 +80,12 @@
// TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return
- val intent = intentResolver.resolveIntent() ?: return
+ val noteTaskInfo = resolver.resolveInfo() ?: return
+
+ uiEvent?.let { uiEventLogger.log(it, noteTaskInfo.uid, noteTaskInfo.packageName) }
// TODO(b/266686199): We should handle when app not available. For now, we log.
+ val intent = noteTaskInfo.toCreateNoteIntent()
try {
if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
context.startActivity(intent)
@@ -84,9 +93,7 @@
bubbles.showOrHideAppBubble(intent)
}
} catch (e: ActivityNotFoundException) {
- val message =
- "Activity not found for action: ${NoteTaskIntentResolver.ACTION_CREATE_NOTE}."
- Log.e(TAG, message, e)
+ Log.e(TAG, "Activity not found for action: $ACTION_CREATE_NOTE.", e)
}
}
@@ -114,10 +121,47 @@
)
}
+ /** IDs of UI events accepted by [showNoteTask]. */
+ enum class ShowNoteTaskUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "User opened a note by tapping on the lockscreen shortcut.")
+ NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE(1294),
+
+ /* ktlint-disable max-line-length */
+ @UiEvent(
+ doc =
+ "User opened a note by pressing the stylus tail button while the screen was unlocked."
+ )
+ NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON(1295),
+ @UiEvent(
+ doc =
+ "User opened a note by pressing the stylus tail button while the screen was locked."
+ )
+ NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED(1296),
+ @UiEvent(doc = "User opened a note by tapping on an app shortcut.")
+ NOTE_OPENED_VIA_SHORTCUT(1297);
+
+ override fun getId() = _id
+ }
+
companion object {
private val TAG = NoteTaskController::class.simpleName.orEmpty()
+ private fun NoteTaskInfoResolver.NoteTaskInfo.toCreateNoteIntent(): Intent {
+ return Intent(ACTION_CREATE_NOTE)
+ .setPackage(packageName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
+ // was used to start it.
+ .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
+ }
+
// TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
const val NOTE_TASK_KEY_EVENT = 311
+
+ // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead.
+ const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
+
+ // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead.
+ const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
new file mode 100644
index 0000000..bd822d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.util.Log
+import javax.inject.Inject
+
+internal class NoteTaskInfoResolver
+@Inject
+constructor(
+ private val context: Context,
+ private val roleManager: RoleManager,
+ private val packageManager: PackageManager,
+) {
+ fun resolveInfo(): NoteTaskInfo? {
+ // TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking.
+ val user = context.user
+ val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, user).firstOrNull()
+
+ if (packageName.isNullOrEmpty()) return null
+
+ return NoteTaskInfo(packageName, packageManager.getUidOf(packageName, user))
+ }
+
+ /** Package name and kernel user-ID of a note-taking app. */
+ data class NoteTaskInfo(val packageName: String, val uid: Int)
+
+ companion object {
+ private val TAG = NoteTaskInfoResolver::class.simpleName.orEmpty()
+
+ private val EMPTY_APPLICATION_INFO_FLAGS = PackageManager.ApplicationInfoFlags.of(0)!!
+
+ /**
+ * Returns the kernel user-ID of [packageName] for a [user]. Returns zero if the app cannot
+ * be found.
+ */
+ private fun PackageManager.getUidOf(packageName: String, user: UserHandle): Int {
+ val applicationInfo =
+ try {
+ getApplicationInfoAsUser(packageName, EMPTY_APPLICATION_INFO_FLAGS, user)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(TAG, "Couldn't find notes app UID", e)
+ return 0
+ }
+ return applicationInfo.uid
+ }
+
+ // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
+ const val ROLE_NOTES = "android.app.role.NOTES"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index d5f4a5a..d40bf2b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -16,8 +16,10 @@
package com.android.systemui.notetask
+import android.app.KeyguardManager
import androidx.annotation.VisibleForTesting
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
import javax.inject.Inject
@@ -30,6 +32,7 @@
private val noteTaskController: NoteTaskController,
private val commandQueue: CommandQueue,
@NoteTaskEnabledKey private val isEnabled: Boolean,
+ private val optionalKeyguardManager: Optional<KeyguardManager>,
) {
@VisibleForTesting
@@ -37,11 +40,21 @@
object : CommandQueue.Callbacks {
override fun handleSystemKey(keyCode: Int) {
if (keyCode == NoteTaskController.NOTE_TASK_KEY_EVENT) {
- noteTaskController.showNoteTask()
+ showNoteTask()
}
}
}
+ private fun showNoteTask() {
+ val uiEvent =
+ if (optionalKeyguardManager.isKeyguardLocked) {
+ NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+ } else {
+ NoteTaskController.ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+ }
+ noteTaskController.showNoteTask(uiEvent = uiEvent)
+ }
+
fun initialize() {
if (isEnabled && optionalBubbles.isPresent) {
commandQueue.addCallback(callbacks)
@@ -49,3 +62,7 @@
noteTaskController.setNoteTaskShortcutEnabled(isEnabled)
}
}
+
+private val Optional<KeyguardManager>.isKeyguardLocked: Boolean
+ // If there's no KeyguardManager, assume that the keyguard is not locked.
+ get() = getOrNull()?.isKeyguardLocked ?: false
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
deleted file mode 100644
index 11dc1d7..0000000
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.notetask
-
-import android.app.role.RoleManager
-import android.content.Context
-import android.content.Intent
-import javax.inject.Inject
-
-internal class NoteTaskIntentResolver
-@Inject
-constructor(
- private val context: Context,
- private val roleManager: RoleManager,
-) {
-
- fun resolveIntent(): Intent? {
- val packageName = roleManager.getRoleHoldersAsUser(ROLE_NOTES, context.user).firstOrNull()
-
- if (packageName.isNullOrEmpty()) return null
-
- return Intent(ACTION_CREATE_NOTE)
- .setPackage(packageName)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint was
- // used to start it.
- .putExtra(INTENT_EXTRA_USE_STYLUS_MODE, true)
- }
-
- companion object {
- // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead.
- const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
-
- // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
- const val ROLE_NOTES = "android.app.role.NOTES"
-
- // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead.
- const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index ec6a16a..b8800a2 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -51,7 +51,7 @@
featureFlags: FeatureFlags,
roleManager: RoleManager,
): Boolean {
- val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskIntentResolver.ROLE_NOTES)
+ val isRoleAvailable = roleManager.isRoleAvailable(NoteTaskInfoResolver.ROLE_NOTES)
val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
return isRoleAvailable && isFeatureEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index cfbaa48..43869cc 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -27,6 +27,7 @@
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.PickerScreenState
import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
import com.android.systemui.notetask.NoteTaskEnabledKey
import javax.inject.Inject
import kotlinx.coroutines.flow.flowOf
@@ -64,7 +65,9 @@
}
override fun onTriggered(expandable: Expandable?): OnTriggeredResult {
- noteTaskController.showNoteTask()
+ noteTaskController.showNoteTask(
+ uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+ )
return OnTriggeredResult.Handled
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index f203e7a..3ac5bfa 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -21,7 +21,7 @@
import android.os.Bundle
import androidx.activity.ComponentActivity
import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskIntentResolver
+import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
import javax.inject.Inject
/** Activity responsible for launching the note experience, and finish. */
@@ -34,7 +34,10 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- noteTaskController.showNoteTask(isInMultiWindowMode)
+ noteTaskController.showNoteTask(
+ isInMultiWindowMode = isInMultiWindowMode,
+ uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT,
+ )
finish()
}
@@ -46,7 +49,7 @@
return Intent(context, LaunchNoteTaskActivity::class.java).apply {
// Intent's action must be set in shortcuts, or an exception will be thrown.
// TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead.
- action = NoteTaskIntentResolver.ACTION_CREATE_NOTE
+ action = NoteTaskController.ACTION_CREATE_NOTE
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
new file mode 100644
index 0000000..7db293d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.process;
+
+/**
+ * A simple wrapper that provides access to process-related details. This facilitates testing by
+ * providing a mockable target around these details.
+ */
+public class ProcessWrapper {
+ public int getUserHandleIdentifier() {
+ return android.os.Process.myUserHandle().getIdentifier();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java b/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java
new file mode 100644
index 0000000..5a21ea0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.process.condition;
+
+import com.android.systemui.process.ProcessWrapper;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.condition.Condition;
+
+import javax.inject.Inject;
+
+/**
+ * {@link UserProcessCondition} provides a signal when the process handle belongs to the current
+ * user.
+ */
+public class UserProcessCondition extends Condition {
+ private final ProcessWrapper mProcessWrapper;
+ private final UserTracker mUserTracker;
+
+ @Inject
+ public UserProcessCondition(ProcessWrapper processWrapper, UserTracker userTracker) {
+ mProcessWrapper = processWrapper;
+ mUserTracker = userTracker;
+ }
+
+ @Override
+ protected void start() {
+ updateCondition(mUserTracker.getUserId()
+ == mProcessWrapper.getUserHandleIdentifier());
+ }
+
+ @Override
+ protected void stop() {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 628964a..25a5c61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -22,6 +22,7 @@
import android.hardware.display.NightDisplayListener;
import android.os.Handler;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.dagger.MediaModule;
import com.android.systemui.qs.AutoAddTracker;
@@ -53,6 +54,7 @@
public interface QSModule {
@Provides
+ @SysUISingleton
static AutoTileManager provideAutoTileManager(
Context context,
AutoAddTracker.Builder autoAddTrackerBuilder,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 64a8a14..ad00069 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -171,8 +171,9 @@
getHost().collapsePanels();
};
- Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
+ final Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags,
mDialogLaunchAnimator, mActivityStarter, onStartRecordingClicked);
+
ActivityStarter.OnDismissAction dismissAction = () -> {
if (shouldAnimateFromView) {
mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 1ed18c3..c0e4995 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -805,6 +805,11 @@
}
@Override
+ public void onCarrierNetworkChange(boolean active) {
+ mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+ }
+
+ @Override
@WorkerThread
public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
@Nullable WifiEntry connectedEntry, boolean hasMoreWifiEntries) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 2e6ea0e..557b718 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -206,6 +206,8 @@
protected boolean mHasEthernet = false;
@VisibleForTesting
protected ConnectedWifiInternetMonitor mConnectedWifiInternetMonitor;
+ @VisibleForTesting
+ protected boolean mCarrierNetworkChangeMode;
private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@@ -507,10 +509,13 @@
Drawable getSignalStrengthIcon(int subId, Context context, int level, int numLevels,
int iconType, boolean cutOut) {
boolean isForDds = subId == mDefaultDataSubId;
+ int levelDrawable =
+ mCarrierNetworkChangeMode ? SignalDrawable.getCarrierChangeState(numLevels)
+ : SignalDrawable.getState(level, numLevels, cutOut);
if (isForDds) {
- mSignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut));
+ mSignalDrawable.setLevel(levelDrawable);
} else {
- mSecondarySignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut));
+ mSecondarySignalDrawable.setLevel(levelDrawable);
}
// Make the network type drawable
@@ -672,10 +677,13 @@
}
int resId = Objects.requireNonNull(mapIconSets(config).get(iconKey)).dataContentDescription;
+ SignalIcon.MobileIconGroup iconGroup;
if (isCarrierNetworkActive()) {
- SignalIcon.MobileIconGroup carrierMergedWifiIconGroup =
- TelephonyIcons.CARRIER_MERGED_WIFI;
- resId = carrierMergedWifiIconGroup.dataContentDescription;
+ iconGroup = TelephonyIcons.CARRIER_MERGED_WIFI;
+ resId = iconGroup.dataContentDescription;
+ } else if (mCarrierNetworkChangeMode) {
+ iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
+ resId = iconGroup.dataContentDescription;
}
return resId != 0
@@ -1066,7 +1074,8 @@
TelephonyCallback.DisplayInfoListener,
TelephonyCallback.ServiceStateListener,
TelephonyCallback.SignalStrengthsListener,
- TelephonyCallback.UserMobileDataStateListener {
+ TelephonyCallback.UserMobileDataStateListener,
+ TelephonyCallback.CarrierNetworkListener{
private final int mSubId;
private InternetTelephonyCallback(int subId) {
@@ -1098,6 +1107,12 @@
public void onUserMobileDataStateChanged(boolean enabled) {
mCallback.onUserMobileDataStateChanged(enabled);
}
+
+ @Override
+ public void onCarrierNetworkChange(boolean active) {
+ mCarrierNetworkChangeMode = active;
+ mCallback.onCarrierNetworkChange(active);
+ }
}
private class InternetOnSubscriptionChangedListener
@@ -1267,6 +1282,8 @@
void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo);
+ void onCarrierNetworkChange(boolean active);
+
void dismissDialog();
void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index b8684ee..db2e62b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -36,6 +36,8 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
@@ -46,6 +48,8 @@
import javax.inject.Inject;
+import dagger.Lazy;
+
/**
* Helper class to initiate a screen recording
*/
@@ -60,6 +64,8 @@
private CountDownTimer mCountDownTimer = null;
private final Executor mMainExecutor;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final Context mContext;
+ private final FeatureFlags mFlags;
private final UserContextProvider mUserContextProvider;
private final UserTracker mUserTracker;
@@ -70,6 +76,8 @@
private CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
new CopyOnWriteArrayList<>();
+ private final Lazy<ScreenCaptureDevicePolicyResolver> mDevicePolicyResolver;
+
@VisibleForTesting
final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@@ -100,22 +108,44 @@
@Inject
public RecordingController(@Main Executor mainExecutor,
BroadcastDispatcher broadcastDispatcher,
+ Context context,
+ FeatureFlags flags,
UserContextProvider userContextProvider,
+ Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
UserTracker userTracker) {
mMainExecutor = mainExecutor;
+ mContext = context;
+ mFlags = flags;
+ mDevicePolicyResolver = devicePolicyResolver;
mBroadcastDispatcher = broadcastDispatcher;
mUserContextProvider = userContextProvider;
mUserTracker = userTracker;
}
- /** Create a dialog to show screen recording options to the user. */
+ /**
+ * MediaProjection host is SystemUI for the screen recorder, so return 'my user handle'
+ */
+ private UserHandle getHostUserHandle() {
+ return UserHandle.of(UserHandle.myUserId());
+ }
+
+ /** Create a dialog to show screen recording options to the user.
+ * If screen capturing is currently not allowed it will return a dialog
+ * that warns users about it. */
public Dialog createScreenRecordDialog(Context context, FeatureFlags flags,
DialogLaunchAnimator dialogLaunchAnimator,
ActivityStarter activityStarter,
@Nullable Runnable onStartRecordingClicked) {
+ if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)
+ && mDevicePolicyResolver.get()
+ .isScreenCaptureCompletelyDisabled(getHostUserHandle())) {
+ return new ScreenCaptureDisabledDialog(mContext);
+ }
+
return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
- ? new ScreenRecordPermissionDialog(context, this, activityStarter,
- dialogLaunchAnimator, mUserContextProvider, onStartRecordingClicked)
+ ? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this,
+ activityStarter, dialogLaunchAnimator, mUserContextProvider,
+ onStartRecordingClicked)
: new ScreenRecordDialog(context, this, activityStarter,
mUserContextProvider, flags, dialogLaunchAnimator, onStartRecordingClicked);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 68e3dcd..dd21be9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -42,6 +42,7 @@
/** Dialog to select screen recording options */
class ScreenRecordPermissionDialog(
context: Context?,
+ private val hostUserHandle: UserHandle,
private val controller: RecordingController,
private val activityStarter: ActivityStarter,
private val dialogLaunchAnimator: DialogLaunchAnimator,
@@ -79,11 +80,9 @@
CaptureTargetResultReceiver()
)
- // Send SystemUI's user handle as the host app user handle because SystemUI
- // is the 'host app' (the app that receives screen capture data)
intent.putExtra(
MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
- UserHandle.of(UserHandle.myUserId())
+ hostUserHandle
)
val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 296c631..cd45b32 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -685,6 +685,7 @@
private boolean mInstantExpanding;
private boolean mAnimateAfterExpanding;
private boolean mIsFlinging;
+ private boolean mLastFlingWasExpanding;
private String mViewName;
private float mInitialExpandY;
private float mInitialExpandX;
@@ -2142,6 +2143,8 @@
@VisibleForTesting
void flingToHeight(float vel, boolean expand, float target,
float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
+ mLastFlingWasExpanding = expand;
+ mShadeLog.logLastFlingWasExpanding(expand);
mHeadsUpTouchHelper.notifyFling(!expand);
mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */);
setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
@@ -2531,7 +2534,7 @@
}
// defer touches on QQS to shade while shade is collapsing. Added margin for error
// as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS.
- if (!mSplitShadeEnabled
+ if (!mSplitShadeEnabled && !mLastFlingWasExpanding
&& computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) {
mShadeLog.logMotionEvent(event,
"handleQsTouch: shade touched while collapsing, QS tracking disabled");
@@ -4125,7 +4128,7 @@
if (didFaceAuthRun) {
mUpdateMonitor.requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
"lockScreenEmptySpaceTap");
} else {
mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
@@ -4622,6 +4625,7 @@
ipw.println(mBlockingExpansionForCurrentTouch);
ipw.print("mExpectingSynthesizedDown="); ipw.println(mExpectingSynthesizedDown);
ipw.print("mLastEventSynthesizedDown="); ipw.println(mLastEventSynthesizedDown);
+ ipw.print("mLastFlingWasExpanding="); ipw.println(mLastFlingWasExpanding);
ipw.print("mInterpolatedDarkAmount="); ipw.println(mInterpolatedDarkAmount);
ipw.print("mLinearDarkAmount="); ipw.println(mLinearDarkAmount);
ipw.print("mPulsing="); ipw.println(mPulsing);
@@ -6135,6 +6139,11 @@
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ if (mTracking) {
+ // TODO(b/247126247) fix underlying issue. Should be ACTION_POINTER_DOWN.
+ mShadeLog.d("Don't intercept down event while already tracking");
+ return false;
+ }
mCentralSurfaces.userActivity();
mAnimatingOnDown = mHeightAnimator != null && !mIsSpringBackAnimation;
mMinExpandHeight = 0.0f;
@@ -6222,6 +6231,11 @@
"onTouch: duplicate down event detected... ignoring");
return true;
}
+ if (mTracking) {
+ // TODO(b/247126247) fix underlying issue. Should be ACTION_POINTER_DOWN.
+ mShadeLog.d("Don't handle down event while already tracking");
+ return true;
+ }
mLastTouchDownTime = event.getDownTime();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 11617be..26c839de 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -20,7 +20,6 @@
import com.android.systemui.log.dagger.ShadeLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
-import com.android.systemui.plugins.log.LogMessage
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -234,4 +233,19 @@
}
)
}
+
+ fun logLastFlingWasExpanding(
+ expand: Boolean
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ bool1 = expand
+ },
+ {
+ "NPVC mLastFlingWasExpanding set to: $bool1"
+ }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 6481855..f4782c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -25,7 +25,9 @@
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION;
@@ -94,6 +96,7 @@
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
@@ -126,7 +129,7 @@
@SysUISingleton
public class KeyguardIndicationController {
- private static final String TAG = "KeyguardIndication";
+ public static final String TAG = "KeyguardIndication";
private static final boolean DEBUG_CHARGING_SPEED = false;
private static final int MSG_SHOW_ACTION_TO_UNLOCK = 1;
@@ -326,9 +329,11 @@
mInitialTextColorState = mTopIndicationView != null
? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
mRotateTextViewController = new KeyguardIndicationRotateTextViewController(
- mLockScreenIndicationView,
- mExecutor,
- mStatusBarStateController);
+ mLockScreenIndicationView,
+ mExecutor,
+ mStatusBarStateController,
+ mKeyguardLogger
+ );
updateDeviceEntryIndication(false /* animate */);
updateOrganizedOwnedDevice();
if (mBroadcastReceiver == null) {
@@ -539,23 +544,23 @@
.build(),
true
);
- if (!TextUtils.isEmpty(mBiometricMessageFollowUp)) {
- mRotateTextViewController.updateIndication(
- INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
- new KeyguardIndication.Builder()
- .setMessage(mBiometricMessageFollowUp)
- .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
- .setTextColor(mInitialTextColorState)
- .build(),
- true
- );
- } else {
- mRotateTextViewController.hideIndication(
- INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
- }
} else {
- mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE);
- mRotateTextViewController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
+ mRotateTextViewController.hideIndication(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE);
+ }
+ if (!TextUtils.isEmpty(mBiometricMessageFollowUp)) {
+ mRotateTextViewController.updateIndication(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ new KeyguardIndication.Builder()
+ .setMessage(mBiometricMessageFollowUp)
+ .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION)
+ .setTextColor(mInitialTextColorState)
+ .build(),
+ true
+ );
+ } else {
+ mRotateTextViewController.hideIndication(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP);
}
}
@@ -784,7 +789,8 @@
*/
private void showBiometricMessage(CharSequence biometricMessage,
@Nullable CharSequence biometricMessageFollowUp) {
- if (TextUtils.equals(biometricMessage, mBiometricMessage)) {
+ if (TextUtils.equals(biometricMessage, mBiometricMessage)
+ && TextUtils.equals(biometricMessageFollowUp, mBiometricMessageFollowUp)) {
return;
}
@@ -793,7 +799,8 @@
mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
hideBiometricMessageDelayed(
- mBiometricMessageFollowUp != null
+ !TextUtils.isEmpty(mBiometricMessage)
+ && !TextUtils.isEmpty(mBiometricMessageFollowUp)
? IMPORTANT_MSG_MIN_DURATION * 2
: DEFAULT_HIDE_DELAY_MS
);
@@ -827,6 +834,7 @@
* may continuously be cycled through.
*/
protected final void updateDeviceEntryIndication(boolean animate) {
+ mKeyguardLogger.logUpdateDeviceEntryIndication(animate, mVisible, mDozing);
if (!mVisible) {
return;
}
@@ -1077,20 +1085,27 @@
}
}
+ final boolean faceAuthUnavailable = biometricSourceType == FACE
+ && msgId == BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
+
// TODO(b/141025588): refactor to reduce repetition of code/comments
// Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
// as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
// pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
// check of whether non-strong biometric is allowed
if (!mKeyguardUpdateMonitor
- .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) {
+ .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
+ && !faceAuthUnavailable) {
return;
}
final boolean faceAuthSoftError = biometricSourceType == FACE
- && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
+ && msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED
+ && msgId != BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
final boolean faceAuthFailed = biometricSourceType == FACE
&& msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed
+ final boolean fpAuthFailed = biometricSourceType == FINGERPRINT
+ && msgId == BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED; // ran matcher & failed
final boolean isUnlockWithFingerprintPossible = canUnlockWithFingerprint();
final boolean isCoExFaceAcquisitionMessage =
faceAuthSoftError && isUnlockWithFingerprintPossible;
@@ -1113,6 +1128,29 @@
mContext.getString(R.string.keyguard_face_failed),
mContext.getString(R.string.keyguard_suggest_fingerprint)
);
+ } else if (fpAuthFailed
+ && mKeyguardUpdateMonitor.getUserUnlockedWithFace(getCurrentUser())) {
+ // face had already previously unlocked the device, so instead of showing a
+ // fingerprint error, tell them they have already unlocked with face auth
+ // and how to enter their device
+ showBiometricMessage(
+ mContext.getString(R.string.keyguard_face_successful_unlock),
+ mContext.getString(R.string.keyguard_unlock)
+ );
+ } else if (fpAuthFailed
+ && mKeyguardUpdateMonitor.getUserHasTrust(
+ KeyguardUpdateMonitor.getCurrentUser())) {
+ showBiometricMessage(
+ getTrustGrantedIndication(),
+ mContext.getString(R.string.keyguard_unlock)
+ );
+ } else if (faceAuthUnavailable) {
+ showBiometricMessage(
+ helpString,
+ isUnlockWithFingerprintPossible
+ ? mContext.getString(R.string.keyguard_suggest_fingerprint)
+ : mContext.getString(R.string.keyguard_unlock)
+ );
} else {
showBiometricMessage(helpString);
}
@@ -1396,6 +1434,7 @@
public void onKeyguardShowingChanged() {
// All transient messages are gone the next time keyguard is shown
if (!mKeyguardStateController.isShowing()) {
+ mKeyguardLogger.log(TAG, LogLevel.DEBUG, "clear messages");
mTopIndicationView.clearMessages();
mRotateTextViewController.clearMessages();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 56c34a0..8f1e0a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -23,6 +23,7 @@
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.IndentingPrintWriter;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -52,6 +53,9 @@
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.util.DumpUtilsKt;
+
+import java.io.PrintWriter;
/**
* A notification shelf view that is placed inside the notification scroller. It manages the
@@ -86,7 +90,6 @@
private boolean mInteractive;
private boolean mAnimationsEnabled = true;
private boolean mShowNotificationShelf;
- private float mFirstElementRoundness;
private Rect mClipRect = new Rect();
private int mIndexOfFirstViewInShelf = -1;
private float mCornerAnimationDistance;
@@ -263,8 +266,7 @@
final float actualWidth = mAmbientState.isOnKeyguard()
? MathUtils.lerp(shortestWidth, getWidth(), fractionToShade)
: getWidth();
- ActivatableNotificationView anv = (ActivatableNotificationView) this;
- anv.setBackgroundWidth((int) actualWidth);
+ setBackgroundWidth((int) actualWidth);
if (mShelfIcons != null) {
mShelfIcons.setActualLayoutWidth((int) actualWidth);
}
@@ -365,9 +367,7 @@
boolean expandingAnimated = mAmbientState.isExpansionChanging()
&& !mAmbientState.isPanelTracking();
int baseZHeight = mAmbientState.getBaseZHeight();
- int backgroundTop = 0;
int clipTopAmount = 0;
- float firstElementRoundness = 0.0f;
for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
ExpandableView child = mHostLayoutController.getChildAt(i);
@@ -420,18 +420,6 @@
if (notGoneIndex != 0 || !aboveShelf) {
expandableRow.setAboveShelf(false);
}
- if (notGoneIndex == 0) {
- StatusBarIconView icon = expandableRow.getEntry().getIcons().getShelfIcon();
- NotificationIconContainer.IconState iconState = getIconState(icon);
- // The icon state might be null in rare cases where the notification is actually
- // added to the layout, but not to the shelf. An example are replied messages,
- // since they don't show up on AOD
- if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
- // only if the first icon is fully in the shelf we want to clip to it!
- backgroundTop = (int) (child.getTranslationY() - getTranslationY());
- firstElementRoundness = expandableRow.getTopRoundness();
- }
- }
previousColor = ownColorUntinted;
notGoneIndex++;
@@ -467,8 +455,6 @@
// TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
- setBackgroundTop(backgroundTop);
- setFirstElementRoundness(firstElementRoundness);
mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
mShelfIcons.calculateIconXTranslations();
mShelfIcons.applyIconStates();
@@ -570,12 +556,6 @@
}
}
- private void setFirstElementRoundness(float firstElementRoundness) {
- if (mFirstElementRoundness != firstElementRoundness) {
- mFirstElementRoundness = firstElementRoundness;
- }
- }
-
private void updateIconClipAmount(ExpandableNotificationRow row) {
float maxTop = row.getTranslationY();
if (getClipTopAmount() != 0) {
@@ -1011,6 +991,18 @@
expandableView.requestRoundnessReset(LegacySourceType.OnScroll);
}
+ @Override
+ public void dump(PrintWriter pwOriginal, String[] args) {
+ IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
+ super.dump(pw, args);
+ if (DUMP_VERBOSE) {
+ DumpUtilsKt.withIncreasedIndent(pw, () -> {
+ pw.println("mActualWidth: " + mActualWidth);
+ pw.println("mStatusBarHeight: " + mStatusBarHeight);
+ });
+ }
+ }
+
public class ShelfState extends ExpandableViewState {
private boolean hasItemsInStableShelf;
private ExpandableView firstViewInShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt
new file mode 100644
index 0000000..6148b40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerExt.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Returns a [Flow] that emits whenever [StatusBarStateController.isExpanded] changes value. */
+val StatusBarStateController.expansionChanges: Flow<Boolean>
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : StatusBarStateController.StateListener {
+ override fun onExpandedChanged(isExpanded: Boolean) {
+ trySend(isExpanded)
+ }
+ }
+ trySend(isExpanded)
+ addCallback(listener)
+ awaitClose { removeCallback(listener) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 737b481..f259284 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -62,7 +62,7 @@
*/
@SysUISingleton
-class PrivacyDotViewController @Inject constructor(
+open class PrivacyDotViewController @Inject constructor(
@Main private val mainExecutor: Executor,
private val stateController: StatusBarStateController,
private val configurationController: ConfigurationController,
@@ -176,7 +176,7 @@
}
@UiThread
- private fun hideDotView(dot: View, animate: Boolean) {
+ open fun hideDotView(dot: View, animate: Boolean) {
dot.clearAnimation()
if (animate) {
dot.animate()
@@ -195,7 +195,7 @@
}
@UiThread
- private fun showDotView(dot: View, animate: Boolean) {
+ open fun showDotView(dot: View, animate: Boolean) {
dot.clearAnimation()
if (animate) {
dot.visibility = View.VISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index b97d4e4..6ef6165 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -31,15 +31,19 @@
import android.os.UserHandle
import android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
import android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS
+import android.provider.Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED
import android.util.Log
import android.view.ContextThemeWrapper
import android.view.View
import android.view.ViewGroup
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.settingslib.Utils
+import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
@@ -54,12 +58,13 @@
import com.android.systemui.shared.regionsampling.UpdateColorCallback
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
-import com.android.systemui.statusbar.Weather
+import com.android.systemui.plugins.Weather
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.settings.SecureSettings
+import java.io.PrintWriter
import java.time.Instant
import java.util.Optional
import java.util.concurrent.Executor
@@ -83,6 +88,8 @@
private val statusBarStateController: StatusBarStateController,
private val deviceProvisionedController: DeviceProvisionedController,
private val bypassController: KeyguardBypassController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val dumpManager: DumpManager,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Background private val bgExecutor: Executor,
@@ -93,7 +100,7 @@
optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
optionalPlugin: Optional<BcSmartspaceDataPlugin>,
optionalConfigPlugin: Optional<BcSmartspaceConfigPlugin>,
-) {
+) : Dumpable {
companion object {
private const val TAG = "LockscreenSmartspaceController"
}
@@ -175,10 +182,10 @@
}
if (weatherTarget != null) {
val weatherData = Weather.fromBundle(weatherTarget.baseAction.extras)
+ keyguardUpdateMonitor.sendWeatherData(weatherData)
}
}
-
private val userTrackerCallback = object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
execution.assertIsMainThread()
@@ -227,6 +234,7 @@
init {
deviceProvisionedController.addCallback(deviceProvisionedListener)
+ dumpManager.registerDumpable(this)
}
fun isEnabled(): Boolean {
@@ -242,6 +250,17 @@
datePlugin != null && weatherPlugin != null
}
+ fun isWeatherEnabled(): Boolean {
+ execution.assertIsMainThread()
+ val defaultValue = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_lockscreenWeatherEnabledByDefault)
+ val showWeather = secureSettings.getIntForUser(
+ LOCK_SCREEN_WEATHER_ENABLED,
+ if (defaultValue) 1 else 0,
+ userTracker.userId) == 1
+ return showWeather
+ }
+
private fun updateBypassEnabled() {
val bypassEnabled = bypassController.bypassEnabled
smartspaceViews.forEach { it.setKeyguardBypassEnabled(bypassEnabled) }
@@ -529,4 +548,11 @@
}
return null
}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("Region Samplers: ${regionSamplers.size}")
+ regionSamplers.map { (_, sampler) ->
+ sampler.dump(pw)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 4856759..fc89be2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -17,13 +17,16 @@
package com.android.systemui.statusbar.notification
import android.content.Context
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import javax.inject.Inject
class NotifPipelineFlags @Inject constructor(
val context: Context,
- val featureFlags: FeatureFlags
+ val featureFlags: FeatureFlags,
+ val sysPropFlags: FlagResolver,
) {
init {
featureFlags.addListener(Flags.DISABLE_FSI) { event -> event.requestNoRestart() }
@@ -39,11 +42,21 @@
fun disableFsi(): Boolean = featureFlags.isEnabled(Flags.DISABLE_FSI)
- val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy {
- featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
- }
+ fun forceDemoteFsi(): Boolean =
+ sysPropFlags.isEnabled(NotificationFlags.FSI_FORCE_DEMOTE)
- val isNoHunForOldWhenEnabled: Boolean by lazy {
- featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
- }
+ fun showStickyHunForDeniedFsi(): Boolean =
+ sysPropFlags.isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI)
+
+ fun allowDismissOngoing(): Boolean =
+ sysPropFlags.isEnabled(NotificationFlags.ALLOW_DISMISS_ONGOING)
+
+ fun isOtpRedactionEnabled(): Boolean =
+ sysPropFlags.isEnabled(NotificationFlags.OTP_REDACTION)
+
+ val shouldFilterUnseenNotifsOnKeyguard: Boolean
+ get() = featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
+
+ val isNoHunForOldWhenEnabled: Boolean
+ get() = featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index e996b78..6bf7668 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -16,16 +16,15 @@
package com.android.systemui.statusbar.notification.collection.coordinator
-import android.database.ContentObserver
import android.os.UserHandle
import android.provider.Settings
import androidx.annotation.VisibleForTesting
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.expansionChanges
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -35,15 +34,14 @@
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxy
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
-import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
@@ -60,6 +58,7 @@
@Inject
constructor(
@Background private val bgDispatcher: CoroutineDispatcher,
+ private val headsUpManager: HeadsUpManager,
private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
private val keyguardRepository: KeyguardRepository,
private val notifPipelineFlags: NotifPipelineFlags,
@@ -87,28 +86,53 @@
private fun attachUnseenFilter(pipeline: NotifPipeline) {
pipeline.addFinalizeFilter(unseenNotifFilter)
pipeline.addCollectionListener(collectionListener)
- scope.launch { clearUnseenWhenKeyguardIsDismissed() }
+ scope.launch { trackUnseenNotificationsWhileUnlocked() }
scope.launch { invalidateWhenUnseenSettingChanges() }
}
- private suspend fun clearUnseenWhenKeyguardIsDismissed() {
- // Use collectLatest so that the suspending block is cancelled if isKeyguardShowing changes
- // during the timeout period
+ private suspend fun trackUnseenNotificationsWhileUnlocked() {
+ // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
+ // showing again
keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
if (!isKeyguardShowing) {
unseenNotifFilter.invalidateList("keyguard no longer showing")
- delay(SEEN_TIMEOUT)
+ trackUnseenNotifications()
+ }
+ }
+ }
+
+ private suspend fun trackUnseenNotifications() {
+ coroutineScope {
+ launch { clearUnseenNotificationsWhenShadeIsExpanded() }
+ launch { markHeadsUpNotificationsAsSeen() }
+ }
+ }
+
+ private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() {
+ statusBarStateController.expansionChanges.collect { isExpanded ->
+ if (isExpanded) {
unseenNotifications.clear()
}
}
}
+ private suspend fun markHeadsUpNotificationsAsSeen() {
+ headsUpManager.allEntries
+ .filter { it.isRowPinned }
+ .forEach { unseenNotifications.remove(it) }
+ headsUpManager.headsUpEvents.collect { (entry, isHun) ->
+ if (isHun) {
+ unseenNotifications.remove(entry)
+ }
+ }
+ }
+
private suspend fun invalidateWhenUnseenSettingChanges() {
secureSettings
// emit whenever the setting has changed
- .settingChangesForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ .observerFlow(
UserHandle.USER_ALL,
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
)
// perform a query immediately
.onStart { emit(Unit) }
@@ -136,13 +160,17 @@
private val collectionListener =
object : NotifCollectionListener {
override fun onEntryAdded(entry: NotificationEntry) {
- if (keyguardRepository.isKeyguardShowing()) {
+ if (
+ keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
+ ) {
unseenNotifications.add(entry)
}
}
override fun onEntryUpdated(entry: NotificationEntry) {
- if (keyguardRepository.isKeyguardShowing()) {
+ if (
+ keyguardRepository.isKeyguardShowing() || !statusBarStateController.isExpanded
+ ) {
unseenNotifications.add(entry)
}
}
@@ -212,18 +240,5 @@
companion object {
private const val TAG = "KeyguardCoordinator"
- private val SEEN_TIMEOUT = 5.seconds
}
}
-
-private fun SettingsProxy.settingChangesForUser(name: String, userHandle: Int): Flow<Unit> =
- conflatedCallbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
- registerContentObserverForUser(name, observer, userHandle)
- awaitClose { unregisterContentObserver(observer) }
- }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 58f59be..a37bbbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -62,7 +62,7 @@
* are not.
*/
public class NotificationLogger implements StateListener {
- private static final String TAG = "NotificationLogger";
+ static final String TAG = "NotificationLogger";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
/** The minimum delay in ms between reports of notification visibility. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
index ec8501a..cc1103d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt
@@ -18,6 +18,7 @@
package com.android.systemui.statusbar.notification.logging
import android.app.StatsManager
+import android.util.Log
import android.util.StatsEvent
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -25,6 +26,7 @@
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.util.traceSection
+import java.lang.Exception
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlin.math.roundToInt
@@ -82,43 +84,56 @@
return StatsManager.PULL_SKIP
}
- // Notifications can only be retrieved on the main thread, so switch to that thread.
- val notifications = getAllNotificationsOnMainThread()
- val notificationMemoryUse =
- NotificationMemoryMeter.notificationMemoryUse(notifications)
- .sortedWith(
- compareBy(
- { it.packageName },
- { it.objectUsage.style },
- { it.notificationKey }
+ try {
+ // Notifications can only be retrieved on the main thread, so switch to that thread.
+ val notifications = getAllNotificationsOnMainThread()
+ val notificationMemoryUse =
+ NotificationMemoryMeter.notificationMemoryUse(notifications)
+ .sortedWith(
+ compareBy(
+ { it.packageName },
+ { it.objectUsage.style },
+ { it.notificationKey }
+ )
+ )
+ val usageData = aggregateMemoryUsageData(notificationMemoryUse)
+ usageData.forEach { (_, use) ->
+ data.add(
+ SysUiStatsLog.buildStatsEvent(
+ SysUiStatsLog.NOTIFICATION_MEMORY_USE,
+ use.uid,
+ use.style,
+ use.count,
+ use.countWithInflatedViews,
+ toKb(use.smallIconObject),
+ use.smallIconBitmapCount,
+ toKb(use.largeIconObject),
+ use.largeIconBitmapCount,
+ toKb(use.bigPictureObject),
+ use.bigPictureBitmapCount,
+ toKb(use.extras),
+ toKb(use.extenders),
+ toKb(use.smallIconViews),
+ toKb(use.largeIconViews),
+ toKb(use.systemIconViews),
+ toKb(use.styleViews),
+ toKb(use.customViews),
+ toKb(use.softwareBitmaps),
+ use.seenCount
)
)
- val usageData = aggregateMemoryUsageData(notificationMemoryUse)
- usageData.forEach { (_, use) ->
- data.add(
- SysUiStatsLog.buildStatsEvent(
- SysUiStatsLog.NOTIFICATION_MEMORY_USE,
- use.uid,
- use.style,
- use.count,
- use.countWithInflatedViews,
- toKb(use.smallIconObject),
- use.smallIconBitmapCount,
- toKb(use.largeIconObject),
- use.largeIconBitmapCount,
- toKb(use.bigPictureObject),
- use.bigPictureBitmapCount,
- toKb(use.extras),
- toKb(use.extenders),
- toKb(use.smallIconViews),
- toKb(use.largeIconViews),
- toKb(use.systemIconViews),
- toKb(use.styleViews),
- toKb(use.customViews),
- toKb(use.softwareBitmaps),
- use.seenCount
- )
- )
+ }
+ } catch (e: InterruptedException) {
+ // This can happen if the device is sleeping or view walking takes too long.
+ // The statsd collector will interrupt the thread and we need to handle it
+ // gracefully.
+ Log.w(NotificationLogger.TAG, "Timed out when measuring notification memory.", e)
+ return@traceSection StatsManager.PULL_SKIP
+ } catch (e: Exception) {
+ // Error while collecting data, this should not crash prod SysUI. Just
+ // log WTF and move on.
+ Log.wtf(NotificationLogger.TAG, "Failed to measure notification memory.", e)
+ return@traceSection StatsManager.PULL_SKIP
}
return StatsManager.PULL_SUCCESS
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
index 2d04211..6491223 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt
@@ -184,19 +184,21 @@
private fun computeDrawableUse(drawable: Drawable, seenObjects: HashSet<Int>): Int =
when (drawable) {
is BitmapDrawable -> {
- val ref = System.identityHashCode(drawable.bitmap)
- if (seenObjects.contains(ref)) {
- 0
- } else {
- seenObjects.add(ref)
- drawable.bitmap.allocationByteCount
- }
+ drawable.bitmap?.let {
+ val ref = System.identityHashCode(it)
+ if (seenObjects.contains(ref)) {
+ 0
+ } else {
+ seenObjects.add(ref)
+ it.allocationByteCount
+ }
+ } ?: 0
}
else -> 0
}
private fun isDrawableSoftwareBitmap(drawable: Drawable) =
- drawable is BitmapDrawable && drawable.bitmap.config != Bitmap.Config.HARDWARE
+ drawable is BitmapDrawable && drawable.bitmap?.config != Bitmap.Config.HARDWARE
private fun identifierForView(view: View) =
if (view.id == View.NO_ID) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 7addc8f..68ad49be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -23,6 +23,7 @@
import android.graphics.Canvas;
import android.graphics.Point;
import android.util.AttributeSet;
+import android.util.IndentingPrintWriter;
import android.util.MathUtils;
import android.view.Choreographer;
import android.view.MotionEvent;
@@ -43,7 +44,9 @@
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.util.DumpUtilsKt;
+import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;
@@ -651,11 +654,6 @@
mBackgroundNormal.setRadius(topRadius, bottomRadius);
}
- @Override
- protected void setBackgroundTop(int backgroundTop) {
- mBackgroundNormal.setBackgroundTop(backgroundTop);
- }
-
protected abstract View getContentView();
public int calculateBgColor() {
@@ -819,6 +817,22 @@
mOnDetachResetRoundness.add(sourceType);
}
+ @Override
+ public void dump(PrintWriter pwOriginal, String[] args) {
+ IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
+ super.dump(pw, args);
+ if (DUMP_VERBOSE) {
+ DumpUtilsKt.withIncreasedIndent(pw, () -> {
+ pw.println("mBackgroundNormal: " + mBackgroundNormal);
+ if (mBackgroundNormal != null) {
+ DumpUtilsKt.withIncreasedIndent(pw, () -> {
+ mBackgroundNormal.dump(pw, args);
+ });
+ }
+ });
+ }
+ }
+
public interface OnActivatedListener {
void onActivated(ActivatableNotificationView view);
void onActivationReset(ActivatableNotificationView view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a6b71dc..9275e2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -591,7 +591,6 @@
}
mShowingPublicInitialized = false;
updateNotificationColor();
- updateLongClickable();
if (mMenuRow != null) {
mMenuRow.onNotificationUpdated(mEntry.getSbn());
mMenuRow.setAppName(mAppName);
@@ -1197,26 +1196,8 @@
return getShowingLayout().getVisibleWrapper();
}
- private boolean isNotificationRowLongClickable() {
- if (mLongPressListener == null) {
- return false;
- }
-
- if (!areGutsExposed()) { // guts is not opened
- return true;
- }
-
- // if it is leave behind, it shouldn't be long clickable.
- return !isGutsLeaveBehind();
- }
-
- private void updateLongClickable() {
- setLongClickable(isNotificationRowLongClickable());
- }
-
public void setLongPressListener(LongPressListener longPressListener) {
mLongPressListener = longPressListener;
- updateLongClickable();
}
public void setDragController(ExpandableNotificationRowDragController dragController) {
@@ -2063,13 +2044,11 @@
void onGutsOpened() {
resetTranslation();
updateContentAccessibilityImportanceForGuts(false /* isEnabled */);
- updateLongClickable();
}
void onGutsClosed() {
updateContentAccessibilityImportanceForGuts(true /* isEnabled */);
mIsSnoozed = false;
- updateLongClickable();
}
/**
@@ -2968,10 +2947,6 @@
return (mGuts != null && mGuts.isExposed());
}
- private boolean isGutsLeaveBehind() {
- return (mGuts != null && mGuts.isLeavebehind());
- }
-
@Override
public boolean isContentExpandable() {
if (mIsSummaryWithChildren && !shouldShowPublic()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 2041245..197caa2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -24,12 +24,16 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
+import android.util.IndentingPrintWriter;
import android.view.View;
import android.view.ViewOutlineProvider;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.RoundableState;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
+import com.android.systemui.util.DumpUtilsKt;
+
+import java.io.PrintWriter;
/**
* Like {@link ExpandableView}, but setting an outline for the height and clipping.
@@ -43,7 +47,6 @@
private float mOutlineAlpha = -1f;
private boolean mAlwaysRoundBothCorners;
private Path mTmpPath = new Path();
- private int mBackgroundTop;
/**
* {@code false} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -59,7 +62,7 @@
// Only when translating just the contents, does the outline need to be shifted.
int translation = !mDismissUsingRowTranslationX ? (int) getTranslation() : 0;
int left = Math.max(translation, 0);
- int top = mClipTopAmount + mBackgroundTop;
+ int top = mClipTopAmount;
int right = getWidth() + Math.min(translation, 0);
int bottom = Math.max(getActualHeight() - mClipBottomAmount, top);
outline.setRect(left, top, right, bottom);
@@ -92,7 +95,7 @@
? (int) getTranslation() : 0;
int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
left = Math.max(translation, 0) - halfExtraWidth;
- top = mClipTopAmount + mBackgroundTop;
+ top = mClipTopAmount;
right = getWidth() + halfExtraWidth + Math.min(translation, 0);
// If the top is rounded we want the bottom to be at most at the top roundness, in order
// to avoid the shadow changing when scrolling up.
@@ -228,13 +231,6 @@
super.applyRoundnessAndInvalidate();
}
- protected void setBackgroundTop(int backgroundTop) {
- if (mBackgroundTop != backgroundTop) {
- mBackgroundTop = backgroundTop;
- invalidateOutline();
- }
- }
-
public void onDensityOrFontScaleChanged() {
initDimens();
applyRoundnessAndInvalidate();
@@ -350,4 +346,18 @@
public Path getCustomClipPath(View child) {
return null;
}
+
+ @Override
+ public void dump(PrintWriter pwOriginal, String[] args) {
+ IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
+ super.dump(pw, args);
+ DumpUtilsKt.withIncreasedIndent(pw, () -> {
+ pw.println("Roundness: " + getRoundableState().debugString());
+ if (DUMP_VERBOSE) {
+ pw.println("mCustomOutline: " + mCustomOutline + " mOutlineRect: " + mOutlineRect);
+ pw.println("mOutlineAlpha: " + mOutlineAlpha);
+ pw.println("mAlwaysRoundBothCorners: " + mAlwaysRoundBothCorners);
+ }
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 955d7c1..25c7264 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -51,6 +51,8 @@
*/
public abstract class ExpandableView extends FrameLayout implements Dumpable, Roundable {
private static final String TAG = "ExpandableView";
+ /** whether the dump() for this class should include verbose details */
+ protected static final boolean DUMP_VERBOSE = false;
private RoundableState mRoundableState = null;
protected OnHeightChangedListener mOnHeightChangedListener;
@@ -825,6 +827,14 @@
viewState.dump(pw, args);
pw.println();
}
+ if (DUMP_VERBOSE) {
+ pw.println("mClipTopAmount: " + mClipTopAmount);
+ pw.println("mClipBottomAmount " + mClipBottomAmount);
+ pw.println("mClipToActualHeight: " + mClipToActualHeight);
+ pw.println("mExtraWidthForClipping: " + mExtraWidthForClipping);
+ pw.println("mMinimumHeightForClipping: " + mMinimumHeightForClipping);
+ pw.println("getClipBounds(): " + getClipBounds());
+ }
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 5171569..da8d2d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -28,12 +28,16 @@
import android.view.View;
import com.android.internal.util.ArrayUtils;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
/**
* A view that can be used for both the dimmed and normal background of an notification.
*/
-public class NotificationBackgroundView extends View {
+public class NotificationBackgroundView extends View implements Dumpable {
private final boolean mDontModifyCorners;
private Drawable mBackground;
@@ -42,7 +46,6 @@
private int mTintColor;
private final float[] mCornerRadii = new float[8];
private boolean mBottomIsRounded;
- private int mBackgroundTop;
private boolean mBottomAmountClips = true;
private int mActualHeight = -1;
private int mActualWidth = -1;
@@ -60,8 +63,7 @@
@Override
protected void onDraw(Canvas canvas) {
- if (mClipTopAmount + mClipBottomAmount < getActualHeight() - mBackgroundTop
- || mExpandAnimationRunning) {
+ if (mClipTopAmount + mClipBottomAmount < getActualHeight() || mExpandAnimationRunning) {
canvas.save();
if (!mExpandAnimationRunning) {
canvas.clipRect(0, mClipTopAmount, getWidth(),
@@ -74,7 +76,7 @@
private void draw(Canvas canvas, Drawable drawable) {
if (drawable != null) {
- int top = mBackgroundTop;
+ int top = 0;
int bottom = getActualHeight();
if (mBottomIsRounded
&& mBottomAmountClips
@@ -261,11 +263,6 @@
}
}
- public void setBackgroundTop(int backgroundTop) {
- mBackgroundTop = backgroundTop;
- invalidate();
- }
-
/** Set the current expand animation size. */
public void setExpandAnimationSize(int width, int height) {
mExpandAnimationHeight = height;
@@ -291,4 +288,16 @@
public void setPressedAllowed(boolean allowed) {
mIsPressedAllowed = allowed;
}
+
+ @Override
+ public void dump(PrintWriter pw, String[] args) {
+ pw.println("mDontModifyCorners: " + mDontModifyCorners);
+ pw.println("mClipTopAmount: " + mClipTopAmount);
+ pw.println("mClipBottomAmount: " + mClipBottomAmount);
+ pw.println("mCornerRadii: " + Arrays.toString(mCornerRadii));
+ pw.println("mBottomIsRounded: " + mBottomIsRounded);
+ pw.println("mBottomAmountClips: " + mBottomAmountClips);
+ pw.println("mActualWidth: " + mActualWidth);
+ pw.println("mActualHeight: " + mActualHeight);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index c534860..39e4000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -28,8 +28,11 @@
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.CancellationSignal;
+import android.os.Trace;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.util.Log;
@@ -38,6 +41,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ImageMessageConsumer;
+import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
@@ -468,6 +472,7 @@
result.packageContext,
parentLayout,
remoteViewClickHandler);
+ validateView(v, entry, row.getResources());
v.setIsRootNamespace(true);
applyCallback.setResultView(v);
} else {
@@ -475,6 +480,7 @@
result.packageContext,
existingView,
remoteViewClickHandler);
+ validateView(existingView, entry, row.getResources());
existingWrapper.onReinflated();
}
} catch (Exception e) {
@@ -496,6 +502,13 @@
@Override
public void onViewApplied(View v) {
+ String invalidReason = isValidView(v, entry, row.getResources());
+ if (invalidReason != null) {
+ handleInflationError(runningInflations, new InflationException(invalidReason),
+ row.getEntry(), callback);
+ runningInflations.remove(inflationId);
+ return;
+ }
if (isNewView) {
v.setIsRootNamespace(true);
applyCallback.setResultView(v);
@@ -553,6 +566,65 @@
runningInflations.put(inflationId, cancellationSignal);
}
+ /**
+ * Checks if the given View is a valid notification View.
+ *
+ * @return null == valid, non-null == invalid, String represents reason for rejection.
+ */
+ @VisibleForTesting
+ @Nullable
+ static String isValidView(View view,
+ NotificationEntry entry,
+ Resources resources) {
+ if (!satisfiesMinHeightRequirement(view, entry, resources)) {
+ return "inflated notification does not meet minimum height requirement";
+ }
+ return null;
+ }
+
+ private static boolean satisfiesMinHeightRequirement(View view,
+ NotificationEntry entry,
+ Resources resources) {
+ if (!requiresHeightCheck(entry)) {
+ return true;
+ }
+ Trace.beginSection("NotificationContentInflater#satisfiesMinHeightRequirement");
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ int referenceWidth = resources.getDimensionPixelSize(
+ R.dimen.notification_validation_reference_width);
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth, View.MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+ int minHeight = resources.getDimensionPixelSize(
+ R.dimen.notification_validation_minimum_allowed_height);
+ boolean result = view.getMeasuredHeight() >= minHeight;
+ Trace.endSection();
+ return result;
+ }
+
+ private static boolean requiresHeightCheck(NotificationEntry entry) {
+ // Undecorated custom views are disallowed from S onwards
+ if (entry.targetSdk >= Build.VERSION_CODES.S) {
+ return false;
+ }
+ // No need to check if the app isn't using any custom views
+ Notification notification = entry.getSbn().getNotification();
+ if (notification.contentView == null
+ && notification.bigContentView == null
+ && notification.headsUpContentView == null) {
+ return false;
+ }
+ return true;
+ }
+
+ private static void validateView(View view,
+ NotificationEntry entry,
+ Resources resources) throws InflationException {
+ String invalidReason = isValidView(view, entry, resources);
+ if (invalidReason != null) {
+ throw new InflationException(invalidReason);
+ }
+ }
+
private static void handleInflationError(
HashMap<Integer, CancellationSignal> runningInflations, Exception e,
NotificationEntry notification, @Nullable InflationCallback callback) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index efcbb3c..37ff11d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -586,9 +586,7 @@
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) view;
- if (view.isLongClickable()) {
- view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- }
+ view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if (row.areGutsExposed()) {
closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 8b1a02b..576df7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -29,6 +29,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.ReduceBrightColorsController;
@@ -47,6 +48,7 @@
import com.android.systemui.util.settings.SecureSettings;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Objects;
import javax.inject.Named;
@@ -165,9 +167,10 @@
if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) {
mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback);
}
- if (!mAutoTracker.isAdded(DEVICE_CONTROLS)) {
- mDeviceControlsController.setCallback(mDeviceControlsCallback);
- }
+ // We always want this callback, because if the feature stops being supported,
+ // we want to remove the tile from AutoAddTracker. That way it will be re-added when the
+ // feature is reenabled (similar to work tile).
+ mDeviceControlsController.setCallback(mDeviceControlsCallback);
if (!mAutoTracker.isAdded(WALLET)) {
initWalletController();
}
@@ -323,14 +326,30 @@
@Override
public void onControlsUpdate(@Nullable Integer position) {
if (mAutoTracker.isAdded(DEVICE_CONTROLS)) return;
- if (position != null) {
+ if (position != null && !hasTile(DEVICE_CONTROLS)) {
mHost.addTile(DEVICE_CONTROLS, position);
+ mAutoTracker.setTileAdded(DEVICE_CONTROLS);
}
- mAutoTracker.setTileAdded(DEVICE_CONTROLS);
mHandler.post(() -> mDeviceControlsController.removeCallback());
}
+
+ @Override
+ public void removeControlsAutoTracker() {
+ mAutoTracker.setTileRemoved(DEVICE_CONTROLS);
+ }
};
+ private boolean hasTile(String tileSpec) {
+ if (tileSpec == null) return false;
+ Collection<QSTile> tiles = mHost.getTiles();
+ for (QSTile tile : tiles) {
+ if (tileSpec.equals(tile.getTileSpec())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void initWalletController() {
if (mAutoTracker.isAdded(WALLET)) return;
Integer position = mWalletController.getWalletPosition();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 01af486..c163a89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -89,14 +89,19 @@
private float mPanelExpansion;
/**
- * Burn-in prevention x translation.
+ * Max burn-in prevention x translation.
*/
- private int mBurnInPreventionOffsetX;
+ private int mMaxBurnInPreventionOffsetX;
/**
- * Burn-in prevention y translation for clock layouts.
+ * Max burn-in prevention y translation for clock layouts.
*/
- private int mBurnInPreventionOffsetYClock;
+ private int mMaxBurnInPreventionOffsetYClock;
+
+ /**
+ * Current burn-in prevention y translation.
+ */
+ private float mCurrentBurnInOffsetY;
/**
* Doze/AOD transition amount.
@@ -155,9 +160,9 @@
mContainerTopPadding =
res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
- mBurnInPreventionOffsetX = res.getDimensionPixelSize(
+ mMaxBurnInPreventionOffsetX = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_x);
- mBurnInPreventionOffsetYClock = res.getDimensionPixelSize(
+ mMaxBurnInPreventionOffsetYClock = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_y_clock);
}
@@ -215,7 +220,10 @@
if (mBypassEnabled) {
return (int) (mUnlockedStackScrollerPadding + mOverStretchAmount);
} else if (mIsSplitShade) {
- return clockYPosition - mSplitShadeTopNotificationsMargin + mUserSwitchHeight;
+ // mCurrentBurnInOffsetY is subtracted to make notifications not follow clock adjustment
+ // for burn-in. It can make pulsing notification go too high and it will get clipped
+ return clockYPosition - mSplitShadeTopNotificationsMargin + mUserSwitchHeight
+ - (int) mCurrentBurnInOffsetY;
} else {
return clockYPosition + mKeyguardStatusHeight;
}
@@ -255,11 +263,11 @@
// This will keep the clock at the top but out of the cutout area
float shift = 0;
- if (clockY - mBurnInPreventionOffsetYClock < mCutoutTopInset) {
- shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYClock);
+ if (clockY - mMaxBurnInPreventionOffsetYClock < mCutoutTopInset) {
+ shift = mCutoutTopInset - (clockY - mMaxBurnInPreventionOffsetYClock);
}
- int burnInPreventionOffsetY = mBurnInPreventionOffsetYClock; // requested offset
+ int burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock; // requested offset
final boolean hasUdfps = mUdfpsTop > -1;
if (hasUdfps && !mIsClockTopAligned) {
// ensure clock doesn't overlap with the udfps icon
@@ -267,8 +275,8 @@
// sometimes the clock textView extends beyond udfps, so let's just use the
// space above the KeyguardStatusView/clock as our burn-in offset
burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2;
- if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
- burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
+ if (mMaxBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock;
}
shift = -burnInPreventionOffsetY;
} else {
@@ -276,16 +284,18 @@
float lowerSpace = mUdfpsTop - mClockBottom;
// center the burn-in offset within the upper + lower space
burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2;
- if (mBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
- burnInPreventionOffsetY = mBurnInPreventionOffsetYClock;
+ if (mMaxBurnInPreventionOffsetYClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mMaxBurnInPreventionOffsetYClock;
}
shift = (lowerSpace - upperSpace) / 2;
}
}
+ float fullyDarkBurnInOffset = burnInPreventionOffsetY(burnInPreventionOffsetY);
float clockYDark = clockY
- + burnInPreventionOffsetY(burnInPreventionOffsetY)
+ + fullyDarkBurnInOffset
+ shift;
+ mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount);
return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
}
@@ -325,7 +335,7 @@
}
private float burnInPreventionOffsetX() {
- return getBurnInOffset(mBurnInPreventionOffsetX, true /* xAxis */);
+ return getBurnInOffset(mMaxBurnInPreventionOffsetX, true /* xAxis */);
}
public static class Result {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index d24469e..b1553b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -165,6 +165,13 @@
}
}
+ /**
+ * Get the message that should be shown after the previous text animates out.
+ */
+ public CharSequence getMessage() {
+ return mMessage;
+ }
+
private AnimatorSet getOutAnimator() {
AnimatorSet animatorSet = new AnimatorSet();
Animator fadeOut = ObjectAnimator.ofFloat(this, View.ALPHA, 0f);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 4550cb2..8ee2c6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -76,7 +76,7 @@
FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
)
keyguardUpdateMonitor.requestActiveUnlock(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE,
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE,
"KeyguardLiftController")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 416bc71..0727c5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -163,7 +163,7 @@
for (int i = currentSlots.size() - 1; i >= 0; i--) {
Slot s = currentSlots.get(i);
slotsToReAdd.put(s, s.getHolderList());
- removeAllIconsForSlot(s.getName());
+ removeAllIconsForSlot(s.getName(), /* fromNewPipeline */ false);
}
// Add them all back
@@ -285,7 +285,7 @@
// Because of the way we cache the icon holders, we need to remove everything any time
// we get a new set of subscriptions. This might change in the future, but is required
// to support demo mode for now
- removeAllIconsForSlot(slotName);
+ removeAllIconsForSlot(slotName, /* fromNewPipeline */ true);
Collections.reverse(subIds);
@@ -428,6 +428,14 @@
/** */
@Override
public void removeIcon(String slot, int tag) {
+ // If the new pipeline is on for this icon, don't allow removal, since the new pipeline
+ // will never call this method
+ if (mStatusBarPipelineFlags.isIconControlledByFlags(slot)) {
+ Log.i(TAG, "Ignoring removal of (" + slot + "). "
+ + "It should be controlled elsewhere");
+ return;
+ }
+
if (mStatusBarIconList.getIconHolder(slot, tag) == null) {
return;
}
@@ -444,6 +452,18 @@
/** */
@Override
public void removeAllIconsForSlot(String slotName) {
+ removeAllIconsForSlot(slotName, /* fromNewPipeline */ false);
+ }
+
+ private void removeAllIconsForSlot(String slotName, boolean fromNewPipeline) {
+ // If the new pipeline is on for this icon, don't allow removal, since the new pipeline
+ // will never call this method
+ if (!fromNewPipeline && mStatusBarPipelineFlags.isIconControlledByFlags(slotName)) {
+ Log.i(TAG, "Ignoring removal of (" + slotName + "). "
+ + "It should be controlled elsewhere");
+ return;
+ }
+
Slot slot = mStatusBarIconList.getSlot(slotName);
if (!slot.hasIconsInSlot()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index fd46571..39281da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -753,7 +753,7 @@
mKeyguardMessageAreaController.setMessage("");
}
mBypassController.setAltBouncerShowing(isShowingAlternateBouncer);
- mKeyguardUpdateManager.setUdfpsBouncerShowing(isShowingAlternateBouncer);
+ mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer);
if (updateScrim) {
mCentralSurfaces.updateScrimController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 15fed32..4a684d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline
+import android.content.Context
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -23,7 +24,15 @@
/** All flagging methods related to the new status bar pipeline (see b/238425913). */
@SysUISingleton
-class StatusBarPipelineFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+class StatusBarPipelineFlags
+@Inject
+constructor(
+ context: Context,
+ private val featureFlags: FeatureFlags,
+) {
+ private val mobileSlot = context.getString(com.android.internal.R.string.status_bar_mobile)
+ private val wifiSlot = context.getString(com.android.internal.R.string.status_bar_wifi)
+
/** True if we should display the mobile icons using the new status bar data pipeline. */
fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
@@ -54,4 +63,13 @@
*/
fun useDebugColoring(): Boolean =
featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING)
+
+ /**
+ * For convenience in the StatusBarIconController, we want to gate some actions based on slot
+ * name and the flag together.
+ *
+ * @return true if this icon is controlled by any of the status bar pipeline flags
+ */
+ fun isIconControlledByFlags(slotName: String): Boolean =
+ slotName == wifiSlot && useNewWifiIcon() || slotName == mobileSlot && useNewMobileIcons()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileSummaryLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileSummaryLog.kt
new file mode 100644
index 0000000..2ac9ab3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileSummaryLog.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * Logs for mobile data that's **the same across all connections**.
+ *
+ * This buffer should only be used for the mobile parent classes like [MobileConnectionsRepository]
+ * and [MobileIconsInteractor]. It should *not* be used for classes that represent an individual
+ * connection, like [MobileConnectionRepository] or [MobileIconInteractor].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class MobileSummaryLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 0993ab370..60de1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -25,6 +25,7 @@
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigCoreStartable
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcher
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
@@ -82,6 +83,11 @@
@ClassKey(MobileUiAdapter::class)
abstract fun bindFeature(impl: MobileUiAdapter): CoreStartable
+ @Binds
+ @IntoMap
+ @ClassKey(CarrierConfigCoreStartable::class)
+ abstract fun bindCarrierConfigStartable(impl: CarrierConfigCoreStartable): CoreStartable
+
companion object {
@Provides
@SysUISingleton
@@ -112,5 +118,12 @@
fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
return factory.create("AirplaneTableLog", 30)
}
+
+ @Provides
+ @SysUISingleton
+ @MobileSummaryLog
+ fun provideMobileSummaryLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("MobileSummaryLog", 100)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
index 5479b92..85729c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -20,16 +20,21 @@
import android.telephony.TelephonyManager.DATA_CONNECTING
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_HANDOVER_IN_PROGRESS
+import android.telephony.TelephonyManager.DATA_SUSPENDED
import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.DataState
/** Internal enum representation of the telephony data connection states */
-enum class DataConnectionState(@DataState val dataState: Int) {
- Connected(DATA_CONNECTED),
- Connecting(DATA_CONNECTING),
- Disconnected(DATA_DISCONNECTED),
- Disconnecting(DATA_DISCONNECTING),
- Unknown(DATA_UNKNOWN),
+enum class DataConnectionState {
+ Connected,
+ Connecting,
+ Disconnected,
+ Disconnecting,
+ Suspended,
+ HandoverInProgress,
+ Unknown,
+ Invalid,
}
fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
@@ -38,6 +43,8 @@
DATA_CONNECTING -> DataConnectionState.Connecting
DATA_DISCONNECTED -> DataConnectionState.Disconnected
DATA_DISCONNECTING -> DataConnectionState.Disconnecting
+ DATA_SUSPENDED -> DataConnectionState.Suspended
+ DATA_HANDOVER_IN_PROGRESS -> DataConnectionState.HandoverInProgress
DATA_UNKNOWN -> DataConnectionState.Unknown
- else -> throw IllegalArgumentException("unknown data state received $this")
+ else -> DataConnectionState.Invalid
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
index 012b9ec..ed7f60b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
@@ -26,6 +26,7 @@
import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
import com.android.systemui.log.table.Diffable
import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
@@ -94,7 +95,7 @@
) : Diffable<MobileConnectionModel> {
override fun logDiffs(prevVal: MobileConnectionModel, row: TableRowLogger) {
if (prevVal.dataConnectionState != dataConnectionState) {
- row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+ row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
}
if (prevVal.isEmergencyOnly != isEmergencyOnly) {
@@ -125,8 +126,12 @@
row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
}
- if (prevVal.dataActivityDirection != dataActivityDirection) {
- row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString())
+ if (prevVal.dataActivityDirection.hasActivityIn != dataActivityDirection.hasActivityIn) {
+ row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
+ }
+
+ if (prevVal.dataActivityDirection.hasActivityOut != dataActivityDirection.hasActivityOut) {
+ row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
}
if (prevVal.carrierNetworkChangeActive != carrierNetworkChangeActive) {
@@ -139,7 +144,7 @@
}
override fun logFull(row: TableRowLogger) {
- row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+ row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
row.logChange(COL_EMERGENCY, isEmergencyOnly)
row.logChange(COL_ROAMING, isRoaming)
row.logChange(COL_OPERATOR, operatorAlphaShort)
@@ -147,11 +152,13 @@
row.logChange(COL_IS_GSM, isGsm)
row.logChange(COL_CDMA_LEVEL, cdmaLevel)
row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
- row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString())
+ row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
+ row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
}
+ @VisibleForTesting
companion object {
const val COL_EMERGENCY = "EmergencyOnly"
const val COL_ROAMING = "Roaming"
@@ -161,7 +168,8 @@
const val COL_CDMA_LEVEL = "CdmaLevel"
const val COL_PRIMARY_LEVEL = "PrimaryLevel"
const val COL_CONNECTION_STATE = "ConnectionState"
- const val COL_ACTIVITY_DIRECTION = "DataActivity"
+ const val COL_ACTIVITY_DIRECTION_IN = "DataActivity.In"
+ const val COL_ACTIVITY_DIRECTION_OUT = "DataActivity.Out"
const val COL_CARRIER_NETWORK_CHANGE = "CarrierNetworkChangeActive"
const val COL_RESOLVED_NETWORK_TYPE = "NetworkType"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
index e618905..97a537a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.net.NetworkCapabilities
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
/** Provides information about a mobile network connection */
data class MobileConnectivityModel(
@@ -24,4 +26,24 @@
val isConnected: Boolean = false,
/** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
val isValidated: Boolean = false,
-)
+) : Diffable<MobileConnectivityModel> {
+ // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
+ override fun logDiffs(prevVal: MobileConnectivityModel, row: TableRowLogger) {
+ if (prevVal.isConnected != isConnected) {
+ row.logChange(COL_IS_CONNECTED, isConnected)
+ }
+ if (prevVal.isValidated != isValidated) {
+ row.logChange(COL_IS_VALIDATED, isValidated)
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_IS_CONNECTED, isConnected)
+ row.logChange(COL_IS_VALIDATED, isValidated)
+ }
+
+ companion object {
+ private const val COL_IS_CONNECTED = "isConnected"
+ private const val COL_IS_VALIDATED = "isValidated"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
index c50d82a..78231e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
@@ -48,15 +48,31 @@
* This name has been derived from telephony intents. see
* [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED]
*/
- data class Derived(override val name: String) : NetworkNameModel {
+ data class IntentDerived(override val name: String) : NetworkNameModel {
override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) {
- if (prevVal !is Derived || prevVal.name != name) {
- row.logChange(COL_NETWORK_NAME, "Derived($name)")
+ if (prevVal !is IntentDerived || prevVal.name != name) {
+ row.logChange(COL_NETWORK_NAME, "IntentDerived($name)")
}
}
override fun logFull(row: TableRowLogger) {
- row.logChange(COL_NETWORK_NAME, "Derived($name)")
+ row.logChange(COL_NETWORK_NAME, "IntentDerived($name)")
+ }
+ }
+
+ /**
+ * This name has been derived from the sim via
+ * [android.telephony.TelephonyManager.getSimOperatorName].
+ */
+ data class SimDerived(override val name: String) : NetworkNameModel {
+ override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) {
+ if (prevVal !is SimDerived || prevVal.name != name) {
+ row.logChange(COL_NETWORK_NAME, "SimDerived($name)")
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_NETWORK_NAME, "SimDerived($name)")
}
}
@@ -84,5 +100,5 @@
str.append(spn)
}
- return if (str.isNotEmpty()) NetworkNameModel.Derived(str.toString()) else null
+ return if (str.isNotEmpty()) NetworkNameModel.IntentDerived(str.toString()) else null
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
new file mode 100644
index 0000000..8c82fba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.model
+
+import android.os.PersistableBundle
+import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
+import androidx.annotation.VisibleForTesting
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Represents, for a given subscription ID, the set of keys about which SystemUI cares.
+ *
+ * Upon first creation, this config represents only the default configuration (see
+ * [android.telephony.CarrierConfigManager.getDefaultConfig]).
+ *
+ * Upon request (see
+ * [com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository]), an
+ * instance of this class may be created for a given subscription Id, and will default to
+ * representing the default carrier configuration. However, once a carrier config is received for
+ * this [subId], all fields will reflect those in the received config, using [PersistableBundle]'s
+ * default of false for any config that is not present in the override.
+ *
+ * To keep things relatively simple, this class defines a wrapper around each config key which
+ * exposes a StateFlow<Boolean> for each config we care about. It also tracks whether or not it is
+ * using the default config for logging purposes.
+ *
+ * NOTE to add new keys to be tracked:
+ * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig]
+ * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config]
+ * 3. Add the new [BooleanCarrierConfig] to the list of tracked configs, so they are properly
+ * updated when a new carrier config comes down
+ */
+class SystemUiCarrierConfig
+internal constructor(
+ val subId: Int,
+ defaultConfig: PersistableBundle,
+) {
+ @VisibleForTesting
+ var isUsingDefault = true
+ private set
+
+ private val inflateSignalStrength =
+ BooleanCarrierConfig(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, defaultConfig)
+ /** Flow tracking the [KEY_INFLATE_SIGNAL_STRENGTH_BOOL] carrier config */
+ val shouldInflateSignalStrength: StateFlow<Boolean> = inflateSignalStrength.config
+
+ private val showOperatorName =
+ BooleanCarrierConfig(KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, defaultConfig)
+ /** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */
+ val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config
+
+ private val trackedConfigs =
+ listOf(
+ inflateSignalStrength,
+ showOperatorName,
+ )
+
+ /** Ingest a new carrier config, and switch all of the tracked keys over to the new values */
+ fun processNewCarrierConfig(config: PersistableBundle) {
+ isUsingDefault = false
+ trackedConfigs.forEach { it.update(config) }
+ }
+
+ /** For dumpsys, shortcut if we haven't overridden any keys */
+ fun toStringConsideringDefaults(): String {
+ return if (isUsingDefault) {
+ "using defaults"
+ } else {
+ trackedConfigs.joinToString { it.toString() }
+ }
+ }
+
+ override fun toString(): String = trackedConfigs.joinToString { it.toString() }
+}
+
+/** Extracts [key] from the carrier config, and stores it in a flow */
+private class BooleanCarrierConfig(
+ val key: String,
+ defaultConfig: PersistableBundle,
+) {
+ private val _configValue = MutableStateFlow(defaultConfig.getBoolean(key))
+ val config = _configValue.asStateFlow()
+
+ fun update(config: PersistableBundle) {
+ _configValue.value = config.getBoolean(key)
+ }
+
+ override fun toString(): String {
+ return "$key=${config.value}"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigCoreStartable.kt
new file mode 100644
index 0000000..af58999
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigCoreStartable.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Core startable which configures the [CarrierConfigRepository] to listen for updates for the
+ * lifetime of the process
+ */
+class CarrierConfigCoreStartable
+@Inject
+constructor(
+ private val carrierConfigRepository: CarrierConfigRepository,
+ @Application private val scope: CoroutineScope,
+) : CoreStartable {
+
+ override fun start() {
+ scope.launch { carrierConfigRepository.startObservingCarrierConfigUpdates() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
new file mode 100644
index 0000000..5769f90
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.content.IntentFilter
+import android.os.PersistableBundle
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionManager
+import android.util.SparseArray
+import androidx.annotation.VisibleForTesting
+import androidx.core.util.getOrElse
+import androidx.core.util.isEmpty
+import androidx.core.util.keyIterator
+import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
+
+/**
+ * Meant to be the source of truth regarding CarrierConfigs. These are configuration objects defined
+ * on a per-subscriptionId basis, and do not trigger a device configuration event.
+ *
+ * Designed to supplant [com.android.systemui.util.CarrierConfigTracker].
+ *
+ * See [SystemUiCarrierConfig] for details on how to add carrier config keys to be tracked
+ */
+@SysUISingleton
+class CarrierConfigRepository
+@Inject
+constructor(
+ broadcastDispatcher: BroadcastDispatcher,
+ private val carrierConfigManager: CarrierConfigManager,
+ dumpManager: DumpManager,
+ logger: ConnectivityPipelineLogger,
+ @Application scope: CoroutineScope,
+) : Dumpable {
+ private var isListening = false
+ private val defaultConfig: PersistableBundle by lazy { CarrierConfigManager.getDefaultConfig() }
+ // Used for logging the default config in the dumpsys
+ private val defaultConfigForLogs: SystemUiCarrierConfig by lazy {
+ SystemUiCarrierConfig(-1, defaultConfig)
+ }
+
+ private val configs = SparseArray<SystemUiCarrierConfig>()
+
+ init {
+ dumpManager.registerNormalDumpable(this)
+ }
+
+ @VisibleForTesting
+ val carrierConfigStream: SharedFlow<Pair<Int, PersistableBundle>> =
+ broadcastDispatcher
+ .broadcastFlow(IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
+ intent,
+ _ ->
+ intent.getIntExtra(
+ CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ )
+ }
+ .onEach { logger.logCarrierConfigChanged(it) }
+ .filter { SubscriptionManager.isValidSubscriptionId(it) }
+ .mapNotNull { subId ->
+ val config = carrierConfigManager.getConfigForSubId(subId)
+ config?.let { subId to it }
+ }
+ .shareIn(scope, SharingStarted.WhileSubscribed())
+
+ /**
+ * Start this repository observing broadcasts for **all** carrier configuration updates. Must be
+ * called in order to keep SystemUI in sync with [CarrierConfigManager].
+ */
+ suspend fun startObservingCarrierConfigUpdates() {
+ isListening = true
+ carrierConfigStream.collect { updateCarrierConfig(it.first, it.second) }
+ }
+
+ /** Update or create the [SystemUiCarrierConfig] for subId with the override */
+ private fun updateCarrierConfig(subId: Int, config: PersistableBundle) {
+ val configToUpdate = getOrCreateConfigForSubId(subId)
+ configToUpdate.processNewCarrierConfig(config)
+ }
+
+ /** Gets a cached [SystemUiCarrierConfig], or creates a new one which will track the defaults */
+ fun getOrCreateConfigForSubId(subId: Int): SystemUiCarrierConfig {
+ return configs.getOrElse(subId) {
+ val config = SystemUiCarrierConfig(subId, defaultConfig)
+ val carrierConfig = carrierConfigManager.getConfigForSubId(subId)
+ if (carrierConfig != null) config.processNewCarrierConfig(carrierConfig)
+ configs.put(subId, config)
+ config
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("isListening: $isListening")
+ if (configs.isEmpty()) {
+ pw.println("no carrier configs loaded")
+ } else {
+ pw.println("Carrier configs by subId")
+ configs.keyIterator().forEach {
+ pw.println(" subId=$it")
+ pw.println(" config=${configs.get(it).toStringConsideringDefaults()}")
+ }
+ // Finally, print the default config
+ pw.println("Default config:")
+ pw.println(" $defaultConfigForLogs")
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index e0d156a..be30ea4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
-import android.provider.Settings
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
import com.android.settingslib.SignalIcon.MobileIconGroup
@@ -35,8 +34,14 @@
/** Observable list of current mobile subscriptions */
val subscriptions: StateFlow<List<SubscriptionModel>>
- /** Observable for the subscriptionId of the current mobile data connection */
- val activeMobileDataSubscriptionId: StateFlow<Int>
+ /**
+ * Observable for the subscriptionId of the current mobile data connection. Null if we don't
+ * have a valid subscription id
+ */
+ val activeMobileDataSubscriptionId: StateFlow<Int?>
+
+ /** Repo that tracks the current [activeMobileDataSubscriptionId] */
+ val activeMobileDataRepository: StateFlow<MobileConnectionRepository?>
/**
* Observable event for when the active data sim switches but the group stays the same. E.g.,
@@ -53,9 +58,6 @@
/** Get or create a repository for the line of service for the given subscription ID */
fun getRepoForSubId(subId: Int): MobileConnectionRepository
- /** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
- val globalMobileDataSettingChangedEvent: Flow<Unit>
-
/**
* [Config] is an object that tracks relevant configuration flags for a given subscription ID.
* In the case of [MobileMappings], it's hard-coded to check the default data subscription's
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index b939856..d54531a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -47,7 +47,6 @@
* interface in its own repository, completely separate from the real version, while still using all
* of the prod implementations for the rest of the pipeline (interactors and onward). Looks
* something like this:
- *
* ```
* RealRepository
* │
@@ -115,7 +114,7 @@
.flatMapLatest { it.subscriptions }
.stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.subscriptions.value)
- override val activeMobileDataSubscriptionId: StateFlow<Int> =
+ override val activeMobileDataSubscriptionId: StateFlow<Int?> =
activeRepo
.flatMapLatest { it.activeMobileDataSubscriptionId }
.stateIn(
@@ -124,6 +123,15 @@
realRepository.activeMobileDataSubscriptionId.value
)
+ override val activeMobileDataRepository: StateFlow<MobileConnectionRepository?> =
+ activeRepo
+ .flatMapLatest { it.activeMobileDataRepository }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ realRepository.activeMobileDataRepository.value
+ )
+
override val activeSubChangedInGroupEvent: Flow<Unit> =
activeRepo.flatMapLatest { it.activeSubChangedInGroupEvent }
@@ -156,9 +164,6 @@
realRepository.defaultMobileNetworkConnectivity.value
)
- override val globalMobileDataSettingChangedEvent: Flow<Unit> =
- activeRepo.flatMapLatest { it.globalMobileDataSettingChangedEvent }
-
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
if (isDemoMode.value) {
return demoMobileConnectionsRepository.getRepoForSubId(subId)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 1088345..e924832 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -55,6 +55,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
@@ -121,6 +122,15 @@
subscriptions.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
)
+ override val activeMobileDataRepository: StateFlow<MobileConnectionRepository?> =
+ activeMobileDataSubscriptionId
+ .map { getRepoForSubId(it) }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ getRepoForSubId(activeMobileDataSubscriptionId.value)
+ )
+
// TODO(b/261029387): consider adding a demo command for this
override val activeSubChangedInGroupEvent: Flow<Unit> = flowOf()
@@ -185,8 +195,6 @@
return CacheContainer(repo, lastMobileState = null)
}
- override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
-
fun startProcessingCommands() {
mobileDemoCommandJob =
scope.launch {
@@ -242,7 +250,7 @@
// This is always true here, because we split out disabled states at the data-source level
connection.dataEnabled.value = true
- connection.networkName.value = NetworkNameModel.Derived(state.name)
+ connection.networkName.value = NetworkNameModel.IntentDerived(state.name)
connection.cdmaRoaming.value = state.roaming
connection.connectionInfo.value = state.toMobileConnectionModel()
@@ -260,10 +268,13 @@
maybeCreateSubscription(subId)
carrierMergedSubId = subId
+ // TODO(b/261029387): until we have a command, use the most recent subId
+ defaultDataSubId.value = subId
+
val connection = getRepoForSubId(subId)
// This is always true here, because we split out disabled states at the data-source level
connection.dataEnabled.value = true
- connection.networkName.value = NetworkNameModel.Derived(CARRIER_MERGED_NAME)
+ connection.networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME)
connection.numberOfLevels.value = event.numberOfLevels
connection.cdmaRoaming.value = false
connection.connectionInfo.value = event.toMobileConnectionModel()
@@ -338,7 +349,10 @@
}
private fun FakeWifiEventModel.CarrierMerged.toMobileConnectionModel(): MobileConnectionModel {
- return createCarrierMergedConnectionModel(this.level)
+ return createCarrierMergedConnectionModel(
+ this.level,
+ activity.toMobileDataActivityModel(),
+ )
}
private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
@@ -373,5 +387,5 @@
override val cdmaRoaming = MutableStateFlow(false)
- override val networkName = MutableStateFlow(NetworkNameModel.Derived("demo network"))
+ override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index c783b12..938c734 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.telephony.TelephonyManager
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -37,7 +38,6 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -54,10 +54,18 @@
class CarrierMergedConnectionRepository(
override val subId: Int,
override val tableLogBuffer: TableLogBuffer,
- defaultNetworkName: NetworkNameModel,
+ private val telephonyManager: TelephonyManager,
@Application private val scope: CoroutineScope,
val wifiRepository: WifiRepository,
) : MobileConnectionRepository {
+ init {
+ if (telephonyManager.subscriptionId != subId) {
+ throw IllegalStateException(
+ "CarrierMergedRepo: TelephonyManager should be created with subId($subId). " +
+ "Found ${telephonyManager.subscriptionId} instead."
+ )
+ }
+ }
/**
* Outputs the carrier merged network to use, or null if we don't have a valid carrier merged
@@ -87,20 +95,28 @@
}
override val connectionInfo: StateFlow<MobileConnectionModel> =
- network
- .map { it.toMobileConnectionModel() }
+ combine(network, wifiRepository.wifiActivity) { network, activity ->
+ if (network == null) {
+ MobileConnectionModel()
+ } else {
+ createCarrierMergedConnectionModel(network.level, activity)
+ }
+ }
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel())
- // TODO(b/238425913): Add logging to this class.
- // TODO(b/238425913): Make sure SignalStrength.getEmptyState is used when appropriate.
+ override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(ROAMING).asStateFlow()
- // Carrier merged is never roaming.
- override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(false).asStateFlow()
-
- // TODO(b/238425913): Fetch the carrier merged network name.
override val networkName: StateFlow<NetworkNameModel> =
- flowOf(defaultNetworkName)
- .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
+ network
+ // The SIM operator name should be the same throughout the lifetime of a subId, **but**
+ // it may not be available when this repo is created because it takes time to load. To
+ // be safe, we re-fetch it each time the network has changed.
+ .map { NetworkNameModel.SimDerived(telephonyManager.simOperatorName) }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ NetworkNameModel.SimDerived(telephonyManager.simOperatorName),
+ )
override val numberOfLevels: StateFlow<Int> =
wifiRepository.wifiNetwork
@@ -115,37 +131,24 @@
override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
- private fun WifiNetworkModel.CarrierMerged?.toMobileConnectionModel(): MobileConnectionModel {
- if (this == null) {
- return MobileConnectionModel()
- }
-
- return createCarrierMergedConnectionModel(level)
- }
-
companion object {
/**
* Creates an instance of [MobileConnectionModel] that represents a carrier merged network
- * with the given [level].
+ * with the given [level] and [activity].
*/
- fun createCarrierMergedConnectionModel(level: Int): MobileConnectionModel {
+ fun createCarrierMergedConnectionModel(
+ level: Int,
+ activity: DataActivityModel,
+ ): MobileConnectionModel {
return MobileConnectionModel(
primaryLevel = level,
cdmaLevel = level,
- // A [WifiNetworkModel.CarrierMerged] instance is always connected.
- // (A [WifiNetworkModel.Inactive] represents a disconnected network.)
- dataConnectionState = DataConnectionState.Connected,
- // TODO(b/238425913): This should come from [WifiRepository.wifiActivity].
- dataActivityDirection =
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = false,
- ),
+ dataActivityDirection = activity,
+ // Here and below: These values are always the same for every carrier-merged
+ // connection.
resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
- // Carrier merged is never roaming
- isRoaming = false,
-
- // TODO(b/238425913): Verify that these fields never change for carrier merged.
+ dataConnectionState = DataConnectionState.Connected,
+ isRoaming = ROAMING,
isEmergencyOnly = false,
operatorAlphaShort = null,
isInService = true,
@@ -153,24 +156,27 @@
carrierNetworkChangeActive = false,
)
}
+
+ // Carrier merged is never roaming
+ private const val ROAMING = false
}
@SysUISingleton
class Factory
@Inject
constructor(
+ private val telephonyManager: TelephonyManager,
@Application private val scope: CoroutineScope,
private val wifiRepository: WifiRepository,
) {
fun build(
subId: Int,
mobileLogger: TableLogBuffer,
- defaultNetworkName: NetworkNameModel,
): MobileConnectionRepository {
return CarrierMergedConnectionRepository(
subId,
mobileLogger,
- defaultNetworkName,
+ telephonyManager.createForSubscriptionId(subId),
scope,
wifiRepository,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index 0f30ae2..a39ea0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -26,7 +26,6 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -50,7 +49,6 @@
override val tableLogBuffer: TableLogBuffer,
private val defaultNetworkName: NetworkNameModel,
private val networkNameSeparator: String,
- private val globalMobileDataSettingChangedEvent: Flow<Unit>,
@Application scope: CoroutineScope,
private val mobileRepoFactory: MobileConnectionRepositoryImpl.Factory,
private val carrierMergedRepoFactory: CarrierMergedConnectionRepository.Factory,
@@ -84,12 +82,11 @@
tableLogBuffer,
defaultNetworkName,
networkNameSeparator,
- globalMobileDataSettingChangedEvent,
)
}
private val carrierMergedRepo: MobileConnectionRepository by lazy {
- carrierMergedRepoFactory.build(subId, tableLogBuffer, defaultNetworkName)
+ carrierMergedRepoFactory.build(subId, tableLogBuffer)
}
@VisibleForTesting
@@ -120,11 +117,22 @@
override val connectionInfo =
activeRepo
.flatMapLatest { it.connectionInfo }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ initialValue = activeRepo.value.connectionInfo.value,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)
override val dataEnabled =
activeRepo
.flatMapLatest { it.dataEnabled }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = "dataEnabled",
+ initialValue = activeRepo.value.dataEnabled.value,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.dataEnabled.value)
override val numberOfLevels =
@@ -135,6 +143,11 @@
override val networkName =
activeRepo
.flatMapLatest { it.networkName }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ initialValue = activeRepo.value.networkName.value,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value)
class Factory
@@ -150,7 +163,6 @@
startingIsCarrierMerged: Boolean,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
- globalMobileDataSettingChangedEvent: Flow<Unit>,
): FullMobileConnectionRepository {
val mobileLogger =
logFactory.getOrCreate(tableBufferLogName(subId), MOBILE_CONNECTION_BUFFER_SIZE)
@@ -161,7 +173,6 @@
mobileLogger,
defaultNetworkName,
networkNameSeparator,
- globalMobileDataSettingChangedEvent,
scope,
mobileRepoFactory,
carrierMergedRepoFactory,
@@ -173,7 +184,7 @@
const val MOBILE_CONNECTION_BUFFER_SIZE = 100
/** Returns a log buffer name for a mobile connection with the given [subId]. */
- fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
+ fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]"
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 3f2ce40..dcce0ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -18,8 +18,6 @@
import android.content.Context
import android.content.IntentFilter
-import android.database.ContentObserver
-import android.provider.Settings.Global
import android.telephony.CellSignalStrength
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
@@ -38,20 +36,20 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
-import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -60,13 +58,15 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.scan
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
/**
@@ -81,19 +81,18 @@
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
private val telephonyManager: TelephonyManager,
- private val globalSettings: GlobalSettings,
+ systemUiCarrierConfig: SystemUiCarrierConfig,
broadcastDispatcher: BroadcastDispatcher,
- globalMobileDataSettingChangedEvent: Flow<Unit>,
- mobileMappingsProxy: MobileMappingsProxy,
+ private val mobileMappingsProxy: MobileMappingsProxy,
bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger,
- mobileLogger: TableLogBuffer,
+ override val tableLogBuffer: TableLogBuffer,
scope: CoroutineScope,
) : MobileConnectionRepository {
init {
if (telephonyManager.subscriptionId != subId) {
throw IllegalStateException(
- "TelephonyManager should be created with subId($subId). " +
+ "MobileRepo: TelephonyManager should be created with subId($subId). " +
"Found ${telephonyManager.subscriptionId} instead."
)
}
@@ -101,10 +100,15 @@
private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
- override val tableLogBuffer: TableLogBuffer = mobileLogger
-
- override val connectionInfo: StateFlow<MobileConnectionModel> = run {
- var state = MobileConnectionModel()
+ /**
+ * This flow defines the single shared connection to system_server via TelephonyCallback. Any
+ * new callback should be added to this listener and funneled through callbackEvents via a data
+ * class. See [CallbackEvent] for defining new callbacks.
+ *
+ * The reason we need to do this is because TelephonyManager limits the number of registered
+ * listeners per-process, so we don't want to create a new listener for every callback.
+ */
+ private val callbackEvents: SharedFlow<CallbackEvent> =
conflatedCallbackFlow {
val callback =
object :
@@ -114,41 +118,16 @@
TelephonyCallback.DataConnectionStateListener,
TelephonyCallback.DataActivityListener,
TelephonyCallback.CarrierNetworkListener,
- TelephonyCallback.DisplayInfoListener {
+ TelephonyCallback.DisplayInfoListener,
+ TelephonyCallback.DataEnabledListener {
override fun onServiceStateChanged(serviceState: ServiceState) {
logger.logOnServiceStateChanged(serviceState, subId)
- state =
- state.copy(
- isEmergencyOnly = serviceState.isEmergencyOnly,
- isRoaming = serviceState.roaming,
- operatorAlphaShort = serviceState.operatorAlphaShort,
- isInService = Utils.isInService(serviceState),
- )
- trySend(state)
+ trySend(CallbackEvent.OnServiceStateChanged(serviceState))
}
override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
logger.logOnSignalStrengthsChanged(signalStrength, subId)
- val cdmaLevel =
- signalStrength
- .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
- .let { strengths ->
- if (!strengths.isEmpty()) {
- strengths[0].level
- } else {
- CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
- }
- }
-
- val primaryLevel = signalStrength.level
-
- state =
- state.copy(
- cdmaLevel = cdmaLevel,
- primaryLevel = primaryLevel,
- isGsm = signalStrength.isGsm,
- )
- trySend(state)
+ trySend(CallbackEvent.OnSignalStrengthChanged(signalStrength))
}
override fun onDataConnectionStateChanged(
@@ -156,101 +135,131 @@
networkType: Int
) {
logger.logOnDataConnectionStateChanged(dataState, networkType, subId)
- state =
- state.copy(dataConnectionState = dataState.toDataConnectionType())
- trySend(state)
+ trySend(CallbackEvent.OnDataConnectionStateChanged(dataState))
}
override fun onDataActivity(direction: Int) {
logger.logOnDataActivity(direction, subId)
- state =
- state.copy(
- dataActivityDirection = direction.toMobileDataActivityModel()
- )
- trySend(state)
+ trySend(CallbackEvent.OnDataActivity(direction))
}
override fun onCarrierNetworkChange(active: Boolean) {
logger.logOnCarrierNetworkChange(active, subId)
- state = state.copy(carrierNetworkChangeActive = active)
- trySend(state)
+ trySend(CallbackEvent.OnCarrierNetworkChange(active))
}
override fun onDisplayInfoChanged(
telephonyDisplayInfo: TelephonyDisplayInfo
) {
logger.logOnDisplayInfoChanged(telephonyDisplayInfo, subId)
+ trySend(CallbackEvent.OnDisplayInfoChanged(telephonyDisplayInfo))
+ }
- val networkType =
- if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
- UnknownNetworkType
- } else if (
- telephonyDisplayInfo.overrideNetworkType ==
- OVERRIDE_NETWORK_TYPE_NONE
- ) {
- DefaultNetworkType(
- mobileMappingsProxy.toIconKey(
- telephonyDisplayInfo.networkType
- )
- )
- } else {
- OverrideNetworkType(
- mobileMappingsProxy.toIconKeyOverride(
- telephonyDisplayInfo.overrideNetworkType
- )
- )
- }
- state = state.copy(resolvedNetworkType = networkType)
- trySend(state)
+ override fun onDataEnabledChanged(enabled: Boolean, reason: Int) {
+ logger.logOnDataEnabledChanged(enabled, subId)
+ trySend(CallbackEvent.OnDataEnabledChanged(enabled))
}
}
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
- .onEach { telephonyCallbackEvent.tryEmit(Unit) }
- .logDiffsForTable(
- mobileLogger,
- columnPrefix = "MobileConnection ($subId)",
- initialValue = state,
- )
- .stateIn(scope, SharingStarted.WhileSubscribed(), state)
+ .shareIn(scope, SharingStarted.WhileSubscribed())
+
+ private fun updateConnectionState(
+ prevState: MobileConnectionModel,
+ callbackEvent: CallbackEvent,
+ ): MobileConnectionModel =
+ when (callbackEvent) {
+ is CallbackEvent.OnServiceStateChanged -> {
+ val serviceState = callbackEvent.serviceState
+ prevState.copy(
+ isEmergencyOnly = serviceState.isEmergencyOnly,
+ isRoaming = serviceState.roaming,
+ operatorAlphaShort = serviceState.operatorAlphaShort,
+ isInService = Utils.isInService(serviceState),
+ )
+ }
+ is CallbackEvent.OnSignalStrengthChanged -> {
+ val signalStrength = callbackEvent.signalStrength
+ val cdmaLevel =
+ signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
+ strengths ->
+ if (!strengths.isEmpty()) {
+ strengths[0].level
+ } else {
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+ }
+ }
+
+ val primaryLevel = signalStrength.level
+
+ prevState.copy(
+ cdmaLevel = cdmaLevel,
+ primaryLevel = primaryLevel,
+ isGsm = signalStrength.isGsm,
+ )
+ }
+ is CallbackEvent.OnDataConnectionStateChanged -> {
+ prevState.copy(dataConnectionState = callbackEvent.dataState.toDataConnectionType())
+ }
+ is CallbackEvent.OnDataActivity -> {
+ prevState.copy(
+ dataActivityDirection = callbackEvent.direction.toMobileDataActivityModel()
+ )
+ }
+ is CallbackEvent.OnCarrierNetworkChange -> {
+ prevState.copy(carrierNetworkChangeActive = callbackEvent.active)
+ }
+ is CallbackEvent.OnDisplayInfoChanged -> {
+ val telephonyDisplayInfo = callbackEvent.telephonyDisplayInfo
+ val networkType =
+ if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
+ UnknownNetworkType
+ } else if (
+ telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
+ ) {
+ DefaultNetworkType(
+ mobileMappingsProxy.toIconKey(telephonyDisplayInfo.networkType)
+ )
+ } else {
+ OverrideNetworkType(
+ mobileMappingsProxy.toIconKeyOverride(
+ telephonyDisplayInfo.overrideNetworkType
+ )
+ )
+ }
+ prevState.copy(resolvedNetworkType = networkType)
+ }
+ is CallbackEvent.OnDataEnabledChanged -> {
+ // Not part of this object, handled in a separate flow
+ prevState
+ }
+ }
+
+ override val connectionInfo = run {
+ val initial = MobileConnectionModel()
+ callbackEvents
+ .scan(initial, ::updateConnectionState)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
- // This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
- // once it's wired up inside of [CarrierConfigTracker].
- override val numberOfLevels: StateFlow<Int> =
- flowOf(DEFAULT_NUM_LEVELS)
- .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
-
- /** Produces whenever the mobile data setting changes for this subId */
- private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
+ override val numberOfLevels =
+ systemUiCarrierConfig.shouldInflateSignalStrength
+ .map { shouldInflate ->
+ if (shouldInflate) {
+ DEFAULT_NUM_LEVELS + 1
+ } else {
+ DEFAULT_NUM_LEVELS
}
}
-
- globalSettings.registerContentObserver(
- globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"),
- /* notifyForDescendants */ true,
- observer
- )
-
- awaitClose { context.contentResolver.unregisterContentObserver(observer) }
- }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
/**
* There are a few cases where we will need to poll [TelephonyManager] so we can update some
* internal state where callbacks aren't provided. Any of those events should be merged into
* this flow, which can be used to trigger the polling.
*/
- private val telephonyPollingEvent: Flow<Unit> =
- merge(
- telephonyCallbackEvent,
- localMobileDataSettingChangedEvent,
- globalMobileDataSettingChangedEvent,
- )
+ private val telephonyPollingEvent: Flow<Unit> = callbackEvents.map { Unit }
override val cdmaRoaming: StateFlow<Boolean> =
telephonyPollingEvent
@@ -259,39 +268,23 @@
override val networkName: StateFlow<NetworkNameModel> =
broadcastDispatcher
- .broadcastFlow(IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)) {
- intent,
- _ ->
- if (intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) != subId) {
- defaultNetworkName
- } else {
- intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName
- }
- }
- .distinctUntilChanged()
- .logDiffsForTable(
- mobileLogger,
- columnPrefix = "",
- initialValue = defaultNetworkName,
+ .broadcastFlow(
+ filter = IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED),
+ map = { intent, _ -> intent },
)
+ .filter { intent ->
+ intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) == subId
+ }
+ .map { intent -> intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName }
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
- override val dataEnabled: StateFlow<Boolean> = run {
- val initial = dataConnectionAllowed()
- telephonyPollingEvent
- .mapLatest { dataConnectionAllowed() }
- .distinctUntilChanged()
- .logDiffsForTable(
- mobileLogger,
- columnPrefix = "",
- columnName = "dataEnabled",
- initialValue = initial,
- )
+ override val dataEnabled = run {
+ val initial = telephonyManager.isDataConnectionAllowed
+ callbackEvents
+ .mapNotNull { (it as? CallbackEvent.OnDataEnabledChanged)?.enabled }
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
- private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
-
class Factory
@Inject
constructor(
@@ -299,7 +292,7 @@
private val context: Context,
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
- private val globalSettings: GlobalSettings,
+ private val carrierConfigRepository: CarrierConfigRepository,
private val mobileMappingsProxy: MobileMappingsProxy,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
@@ -309,7 +302,6 @@
mobileLogger: TableLogBuffer,
defaultNetworkName: NetworkNameModel,
networkNameSeparator: String,
- globalMobileDataSettingChangedEvent: Flow<Unit>,
): MobileConnectionRepository {
return MobileConnectionRepositoryImpl(
context,
@@ -317,9 +309,8 @@
defaultNetworkName,
networkNameSeparator,
telephonyManager.createForSubscriptionId(subId),
- globalSettings,
+ carrierConfigRepository.getOrCreateConfigForSubId(subId),
broadcastDispatcher,
- globalMobileDataSettingChangedEvent,
mobileMappingsProxy,
bgDispatcher,
logger,
@@ -329,3 +320,17 @@
}
}
}
+
+/**
+ * Wrap every [TelephonyCallback] we care about in a data class so we can accept them in a single
+ * shared flow and then split them back out into other flows.
+ */
+private sealed interface CallbackEvent {
+ data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent
+ data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent
+ data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent
+ data class OnDataActivity(val direction: Int) : CallbackEvent
+ data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent
+ data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent
+ data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 39ad31f..c9049d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -19,14 +19,12 @@
import android.annotation.SuppressLint
import android.content.Context
import android.content.IntentFilter
-import android.database.ContentObserver
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
-import android.provider.Settings.Global.MOBILE_DATA
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
@@ -44,6 +42,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -54,7 +55,6 @@
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.util.kotlin.pairwise
-import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -85,9 +85,9 @@
private val subscriptionManager: SubscriptionManager,
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
+ @MobileSummaryLog private val tableLogger: TableLogBuffer,
mobileMappingsProxy: MobileMappingsProxy,
broadcastDispatcher: BroadcastDispatcher,
- private val globalSettings: GlobalSettings,
private val context: Context,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
@@ -118,6 +118,12 @@
}
}
.distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "carrierMergedSubId",
+ initialValue = null,
+ )
.stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
private val mobileSubscriptionsChangeEvent: Flow<Unit> = conflatedCallbackFlow {
@@ -143,17 +149,26 @@
override val subscriptions: StateFlow<List<SubscriptionModel>> =
merge(mobileSubscriptionsChangeEvent, carrierMergedSubId)
.mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
- .logInputChange(logger, "onSubscriptionsChanged")
.onEach { infos -> updateRepos(infos) }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "subscriptions",
+ initialValue = listOf(),
+ )
.stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
- /** StateFlow that keeps track of the current active mobile data subscription */
- override val activeMobileDataSubscriptionId: StateFlow<Int> =
+ override val activeMobileDataSubscriptionId: StateFlow<Int?> =
conflatedCallbackFlow {
val callback =
object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
override fun onActiveDataSubscriptionIdChanged(subId: Int) {
- trySend(subId)
+ if (subId != INVALID_SUBSCRIPTION_ID) {
+ trySend(subId)
+ } else {
+ trySend(null)
+ }
}
}
@@ -161,8 +176,24 @@
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
.distinctUntilChanged()
- .logInputChange(logger, "onActiveDataSubscriptionIdChanged")
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "activeSubId",
+ initialValue = INVALID_SUBSCRIPTION_ID,
+ )
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
+
+ override val activeMobileDataRepository =
+ activeMobileDataSubscriptionId
+ .map { activeSubId ->
+ if (activeSubId == null) {
+ null
+ } else {
+ getOrCreateRepoForSubId(activeSubId)
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
MutableSharedFlow(extraBufferCapacity = 1)
@@ -175,7 +206,12 @@
intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
}
.distinctUntilChanged()
- .logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "defaultSubId",
+ initialValue = SubscriptionManager.getDefaultDataSubscriptionId(),
+ )
.onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
.stateIn(
scope,
@@ -218,32 +254,12 @@
)
}
- return subIdRepositoryCache[subId]
- ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
+ return getOrCreateRepoForSubId(subId)
}
- /**
- * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
- * connection repositories also observe the URI for [MOBILE_DATA] + subId.
- */
- override val globalMobileDataSettingChangedEvent: Flow<Unit> =
- conflatedCallbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
-
- globalSettings.registerContentObserver(
- globalSettings.getUriFor(MOBILE_DATA),
- true,
- observer
- )
-
- awaitClose { context.contentResolver.unregisterContentObserver(observer) }
- }
- .logInputChange(logger, "globalMobileDataSettingChangedEvent")
+ private fun getOrCreateRepoForSubId(subId: Int) =
+ subIdRepositoryCache[subId]
+ ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
@SuppressLint("MissingPermission")
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
@@ -274,7 +290,11 @@
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
.distinctUntilChanged()
- .logInputChange(logger, "defaultMobileNetworkConnectivity")
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = "$LOGGING_PREFIX.defaultConnection",
+ initialValue = MobileConnectivityModel(),
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
/**
@@ -289,7 +309,9 @@
override val activeSubChangedInGroupEvent =
activeMobileDataSubscriptionId
.pairwise()
- .mapNotNull { (prevVal: Int, newVal: Int) ->
+ .mapNotNull { (prevVal: Int?, newVal: Int?) ->
+ if (prevVal == null || newVal == null) return@mapNotNull null
+
val prevSub = subscriptionManager.getActiveSubscriptionInfo(prevVal)?.groupUuid
val nextSub = subscriptionManager.getActiveSubscriptionInfo(newVal)?.groupUuid
@@ -297,15 +319,7 @@
}
.flowOn(bgDispatcher)
- private fun isValidSubId(subId: Int): Boolean {
- subscriptions.value.forEach {
- if (it.subscriptionId == subId) {
- return true
- }
- }
-
- return false
- }
+ private fun isValidSubId(subId: Int): Boolean = checkSub(subId, subscriptions.value)
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
@@ -315,7 +329,6 @@
isCarrierMerged(subId),
defaultNetworkName,
networkNameSeparator,
- globalMobileDataSettingChangedEvent,
)
}
@@ -333,12 +346,27 @@
private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
// Remove any connection repository from the cache that isn't in the new set of IDs. They
// will get garbage collected once their subscribers go away
- val currentValidSubscriptionIds = newInfos.map { it.subscriptionId }
-
subIdRepositoryCache =
- subIdRepositoryCache
- .filter { currentValidSubscriptionIds.contains(it.key) }
- .toMutableMap()
+ subIdRepositoryCache.filter { checkSub(it.key, newInfos) }.toMutableMap()
+ }
+
+ /**
+ * True if the checked subId is in the list of current subs or the active mobile data subId
+ *
+ * @param checkedSubs the list to validate [subId] against. To invalidate the cache, pass in the
+ * new subscription list. Otherwise use [subscriptions.value] to validate a subId against the
+ * current known subscriptions
+ */
+ private fun checkSub(subId: Int, checkedSubs: List<SubscriptionModel>): Boolean {
+ if (activeMobileDataSubscriptionId.value == subId) return true
+
+ checkedSubs.forEach {
+ if (it.subscriptionId == subId) {
+ return true
+ }
+ }
+
+ return false
}
private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
@@ -349,4 +377,8 @@
subscriptionId = subscriptionId,
isOpportunistic = isOpportunistic,
)
+
+ companion object {
+ private const val LOGGING_PREFIX = "Repo"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 9cdff96..7b0f952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -33,7 +33,9 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
interface MobileIconInteractor {
@@ -109,6 +111,9 @@
/** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
val numberOfLevels: StateFlow<Int>
+
+ /** See [MobileIconsInteractor.isForceHidden]. */
+ val isForceHidden: Flow<Boolean>
}
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
@@ -124,6 +129,7 @@
defaultMobileIconGroup: StateFlow<MobileIconGroup>,
defaultDataSubId: StateFlow<Int>,
override val isDefaultConnectionFailed: StateFlow<Boolean>,
+ override val isForceHidden: Flow<Boolean>,
connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
private val connectionInfo = connectionRepository.connectionInfo
@@ -152,7 +158,7 @@
if (
networkName is NetworkNameModel.Default && connection.operatorAlphaShort != null
) {
- NetworkNameModel.Derived(connection.operatorAlphaShort)
+ NetworkNameModel.IntentDerived(connection.operatorAlphaShort)
} else {
networkName
}
@@ -181,6 +187,16 @@
else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
}
}
+ .distinctUntilChanged()
+ .onEach {
+ // Doesn't use [logDiffsForTable] because [MobileIconGroup] can't implement the
+ // [Diffable] interface.
+ tableLogBuffer.logChange(
+ prefix = "",
+ columnName = "networkTypeIcon",
+ value = it.name
+ )
+ }
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
override val isEmergencyOnly: StateFlow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 0e4a432..72d5113 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -18,17 +18,21 @@
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionManager
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.util.CarrierConfigTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -42,8 +46,8 @@
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
@@ -88,6 +92,10 @@
val isDefaultConnectionFailed: StateFlow<Boolean>
/** True once the user has been set up */
val isUserSetup: StateFlow<Boolean>
+
+ /** True if we're configured to force-hide the mobile icons and false otherwise. */
+ val isForceHidden: Flow<Boolean>
+
/**
* Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
* subId. Will throw if the ID is invalid
@@ -104,25 +112,13 @@
private val mobileConnectionsRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker,
private val logger: ConnectivityPipelineLogger,
+ @MobileSummaryLog private val tableLogger: TableLogBuffer,
+ connectivityRepository: ConnectivityRepository,
userSetupRepo: UserSetupRepository,
@Application private val scope: CoroutineScope,
) : MobileIconsInteractor {
- private val activeMobileDataSubscriptionId =
- mobileConnectionsRepo.activeMobileDataSubscriptionId
-
- private val activeMobileDataConnectionRepo: StateFlow<MobileConnectionRepository?> =
- activeMobileDataSubscriptionId
- .mapLatest { activeId ->
- if (activeId == INVALID_SUBSCRIPTION_ID) {
- null
- } else {
- mobileConnectionsRepo.getRepoForSubId(activeId)
- }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
-
override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
- activeMobileDataConnectionRepo
+ mobileConnectionsRepo.activeMobileDataRepository
.flatMapLatest { it?.dataEnabled ?: flowOf(false) }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
@@ -143,37 +139,45 @@
* and by checking which subscription is opportunistic, or which one is active.
*/
override val filteredSubscriptions: Flow<List<SubscriptionModel>> =
- combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
- ->
- // Based on the old logic,
- if (unfilteredSubs.size != 2) {
- return@combine unfilteredSubs
- }
+ combine(
+ unfilteredSubscriptions,
+ mobileConnectionsRepo.activeMobileDataSubscriptionId,
+ ) { unfilteredSubs, activeId ->
+ // Based on the old logic,
+ if (unfilteredSubs.size != 2) {
+ return@combine unfilteredSubs
+ }
- val info1 = unfilteredSubs[0]
- val info2 = unfilteredSubs[1]
- // If both subscriptions are primary, show both
- if (!info1.isOpportunistic && !info2.isOpportunistic) {
- return@combine unfilteredSubs
- }
+ val info1 = unfilteredSubs[0]
+ val info2 = unfilteredSubs[1]
+ // If both subscriptions are primary, show both
+ if (!info1.isOpportunistic && !info2.isOpportunistic) {
+ return@combine unfilteredSubs
+ }
- // NOTE: at this point, we are now returning a single SubscriptionInfo
+ // NOTE: at this point, we are now returning a single SubscriptionInfo
- // If carrier required, always show the icon of the primary subscription.
- // Otherwise, show whichever subscription is currently active for internet.
- if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
- // return the non-opportunistic info
- return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
- } else {
- return@combine if (info1.subscriptionId == activeId) {
- listOf(info1)
+ // If carrier required, always show the icon of the primary subscription.
+ // Otherwise, show whichever subscription is currently active for internet.
+ if (carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) {
+ // return the non-opportunistic info
+ return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
} else {
- listOf(info2)
+ return@combine if (info1.subscriptionId == activeId) {
+ listOf(info1)
+ } else {
+ listOf(info2)
+ }
}
}
- }
.distinctUntilChanged()
- .onEach { logger.logFilteredSubscriptionsChanged(it) }
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "filteredSubscriptions",
+ initialValue = listOf(),
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId
@@ -195,6 +199,12 @@
delay(2000)
emit(false)
}
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "forcingValidation",
+ initialValue = false,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
@@ -211,6 +221,12 @@
networkConnectivity
}
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = "$LOGGING_PREFIX.defaultConnection",
+ initialValue = mobileConnectionsRepo.defaultMobileNetworkConnectivity.value,
+ )
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
@@ -259,10 +275,21 @@
!connectivityModel.isValidated
}
}
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "isDefaultConnectionFailed",
+ initialValue = false,
+ )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
+ override val isForceHidden: Flow<Boolean> =
+ connectivityRepository.forceHiddenSlots
+ .map { it.contains(ConnectivitySlot.MOBILE) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
/** Vends out new [MobileIconInteractor] for a particular subId */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
MobileIconInteractorImpl(
@@ -275,6 +302,11 @@
defaultMobileIconGroup,
defaultDataSubId,
isDefaultConnectionFailed,
+ isForceHidden,
mobileConnectionsRepo.getRepoForSubId(subId),
)
+
+ companion object {
+ private const val LOGGING_PREFIX = "Intr"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index a4b2abc..db585e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -91,10 +91,17 @@
}
}
+ launch { viewModel.isVisible.collect { isVisible -> view.isVisible = isVisible } }
+
// Set the icon for the triangle
launch {
- viewModel.iconId.distinctUntilChanged().collect { iconId ->
- mobileDrawable.level = iconId
+ viewModel.icon.distinctUntilChanged().collect { icon ->
+ mobileDrawable.level =
+ SignalDrawable.getState(
+ icon.level,
+ icon.numberOfLevels,
+ icon.showExclamationMark,
+ )
}
}
@@ -148,8 +155,7 @@
return object : ModernStatusBarViewBinding {
override fun getShouldIconBeVisible(): Boolean {
- // If this view model exists, then the icon should be visible.
- return true
+ return viewModel.isVisible.value
}
override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
new file mode 100644
index 0000000..16e1766
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.model
+
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */
+data class SignalIconModel(
+ val level: Int,
+ val numberOfLevels: Int,
+ val showExclamationMark: Boolean,
+) : Diffable<SignalIconModel> {
+ // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes?
+ override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) {
+ if (prevVal.level != level) {
+ row.logChange(COL_LEVEL, level)
+ }
+ if (prevVal.numberOfLevels != numberOfLevels) {
+ row.logChange(COL_NUM_LEVELS, numberOfLevels)
+ }
+ if (prevVal.showExclamationMark != showExclamationMark) {
+ row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_LEVEL, level)
+ row.logChange(COL_NUM_LEVELS, numberOfLevels)
+ row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark)
+ }
+
+ companion object {
+ /** Creates a [SignalIconModel] representing an empty and invalidated state. */
+ fun createEmptyState(numberOfLevels: Int) =
+ SignalIconModel(level = 0, numberOfLevels, showExclamationMark = true)
+
+ private const val COL_LEVEL = "level"
+ private const val COL_NUM_LEVELS = "numLevels"
+ private const val COL_SHOW_EXCLAMATION = "showExclamation"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 9e2024a..0496278 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -22,10 +22,11 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,14 +38,14 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** Common interface for all of the location-based mobile icon view models. */
interface MobileIconViewModelCommon {
val subscriptionId: Int
- /** An int consumable by [SignalDrawable] for display */
- val iconId: Flow<Int>
+ /** True if this view should be visible at all. */
+ val isVisible: StateFlow<Boolean>
+ val icon: Flow<SignalIconModel>
val contentDescription: Flow<ContentDescription>
val roaming: Flow<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
@@ -73,7 +74,7 @@
constructor(
override val subscriptionId: Int,
iconInteractor: MobileIconInteractor,
- logger: ConnectivityPipelineLogger,
+ airplaneModeInteractor: AirplaneModeInteractor,
constants: ConnectivityConstants,
scope: CoroutineScope,
) : MobileIconViewModelCommon {
@@ -81,8 +82,28 @@
private val showExclamationMark: Flow<Boolean> =
iconInteractor.isDefaultDataEnabled.mapLatest { !it }
- override val iconId: Flow<Int> = run {
- val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value)
+ override val isVisible: StateFlow<Boolean> =
+ if (!constants.hasDataCapabilities) {
+ flowOf(false)
+ } else {
+ combine(
+ airplaneModeInteractor.isAirplaneMode,
+ iconInteractor.isForceHidden,
+ ) { isAirplaneMode, isForceHidden ->
+ !isAirplaneMode && !isForceHidden
+ }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnPrefix = "",
+ columnName = "visible",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val icon: Flow<SignalIconModel> = run {
+ val initial = SignalIconModel.createEmptyState(iconInteractor.numberOfLevels.value)
combine(
iconInteractor.level,
iconInteractor.numberOfLevels,
@@ -90,16 +111,15 @@
iconInteractor.isInService,
) { level, numberOfLevels, showExclamationMark, isInService ->
if (!isInService) {
- SignalDrawable.getEmptyState(numberOfLevels)
+ SignalIconModel.createEmptyState(numberOfLevels)
} else {
- SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
+ SignalIconModel(level, numberOfLevels, showExclamationMark)
}
}
.distinctUntilChanged()
.logDiffsForTable(
iconInteractor.tableLogBuffer,
- columnPrefix = "",
- columnName = "iconId",
+ columnPrefix = "icon",
initialValue = initial,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
@@ -124,14 +144,22 @@
private val showNetworkTypeIcon: Flow<Boolean> =
combine(
- iconInteractor.isDataConnected,
- iconInteractor.isDataEnabled,
- iconInteractor.isDefaultConnectionFailed,
- iconInteractor.alwaysShowDataRatIcon,
- iconInteractor.isConnected,
- ) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected ->
- alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected)
- }
+ iconInteractor.isDataConnected,
+ iconInteractor.isDataEnabled,
+ iconInteractor.isDefaultConnectionFailed,
+ iconInteractor.alwaysShowDataRatIcon,
+ iconInteractor.isConnected,
+ ) { dataConnected, dataEnabled, failedConnection, alwaysShow, connected ->
+ alwaysShow || (dataConnected && dataEnabled && !failedConnection && connected)
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ iconInteractor.tableLogBuffer,
+ columnPrefix = "",
+ columnName = "showNetworkTypeIcon",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val networkTypeIcon: Flow<Icon?> =
combine(
@@ -149,14 +177,6 @@
}
}
.distinctUntilChanged()
- .onEach {
- // This is done as an onEach side effect since Icon is not Diffable (yet)
- iconInteractor.tableLogBuffer.logChange(
- prefix = "",
- columnName = "networkTypeIcon",
- value = it.toString(),
- )
- }
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
override val roaming: StateFlow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 24370d2..185b668 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -17,9 +17,11 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
@@ -39,6 +41,7 @@
constructor(
val subscriptionIdsFlow: StateFlow<List<Int>>,
private val interactor: MobileIconsInteractor,
+ private val airplaneModeInteractor: AirplaneModeInteractor,
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
@@ -56,7 +59,7 @@
?: MobileIconViewModel(
subId,
interactor.createMobileConnectionInteractorForSubId(subId),
- logger,
+ airplaneModeInteractor,
constants,
scope,
)
@@ -74,10 +77,12 @@
subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
}
+ @SysUISingleton
class Factory
@Inject
constructor(
private val interactor: MobileIconsInteractor,
+ private val airplaneModeInteractor: AirplaneModeInteractor,
private val logger: ConnectivityPipelineLogger,
private val constants: ConnectivityConstants,
@Application private val scope: CoroutineScope,
@@ -87,6 +92,7 @@
return MobileIconsViewModel(
subscriptionIdsFlow,
interactor,
+ airplaneModeInteractor,
logger,
constants,
scope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index 7c7ffaf..45036969 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -204,15 +204,6 @@
// TODO(b/238425913): We should split this class into mobile-specific and wifi-specific loggers.
- fun logFilteredSubscriptionsChanged(subs: List<SubscriptionModel>) {
- buffer.log(
- SB_LOGGING_TAG,
- LogLevel.INFO,
- { str1 = subs.toString() },
- { "Filtered subscriptions updated: $str1" },
- )
- }
-
fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
buffer.log(
SB_LOGGING_TAG,
@@ -231,6 +222,27 @@
)
}
+ fun logCarrierConfigChanged(subId: Int) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { int1 = subId },
+ { "onCarrierConfigChanged: subId=$int1" },
+ )
+ }
+
+ fun logOnDataEnabledChanged(enabled: Boolean, subId: Int) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ int1 = subId
+ bool1 = enabled
+ },
+ { "onDataEnabledChanged: subId=$int1 enabled=$bool1" },
+ )
+ }
+
companion object {
const val SB_LOGGING_TAG = "SbConnectivity"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
index caac8fa..7d2501ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
@@ -53,7 +53,7 @@
private fun Bundle.activeWifiEvent(): FakeWifiEventModel.Wifi {
val level = getString("level")?.toInt()
- val activity = getString("activity")?.toActivity()
+ val activity = getString("activity").toActivity()
val ssid = getString("ssid")
val validated = getString("fully").toBoolean()
@@ -69,11 +69,12 @@
val subId = getString("slot")?.toInt() ?: DEFAULT_CARRIER_MERGED_SUB_ID
val level = getString("level")?.toInt() ?: 0
val numberOfLevels = getString("numlevels")?.toInt() ?: DEFAULT_NUM_LEVELS
+ val activity = getString("activity").toActivity()
- return FakeWifiEventModel.CarrierMerged(subId, level, numberOfLevels)
+ return FakeWifiEventModel.CarrierMerged(subId, level, numberOfLevels, activity)
}
- private fun String.toActivity(): Int =
+ private fun String?.toActivity(): Int =
when (this) {
"inout" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT
"in" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index e161b3e..a19c3c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -80,17 +80,14 @@
private fun processEnabledWifiState(event: FakeWifiEventModel.Wifi) {
_isWifiEnabled.value = true
_isWifiDefault.value = true
- _wifiActivity.value =
- event.activity?.toWifiDataActivityModel()
- ?: DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ _wifiActivity.value = event.activity.toWifiDataActivityModel()
_wifiNetwork.value = event.toWifiNetworkModel()
}
private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) {
_isWifiEnabled.value = true
_isWifiDefault.value = true
- // TODO(b/238425913): Support activity in demo mode.
- _wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ _wifiActivity.value = event.activity.toWifiDataActivityModel()
_wifiNetwork.value = event.toCarrierMergedModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
index 518f8ce..f5035cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model
+import android.telephony.Annotation
+
/**
* Model for demo wifi commands, ported from [NetworkControllerImpl]
*
@@ -24,7 +26,7 @@
sealed interface FakeWifiEventModel {
data class Wifi(
val level: Int?,
- val activity: Int?,
+ @Annotation.DataActivityType val activity: Int,
val ssid: String?,
val validated: Boolean?,
) : FakeWifiEventModel
@@ -33,6 +35,7 @@
val subscriptionId: Int,
val level: Int,
val numberOfLevels: Int,
+ @Annotation.DataActivityType val activity: Int,
) : FakeWifiEventModel
object WifiDisabled : FakeWifiEventModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 8669047..c45b420 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -95,7 +95,7 @@
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
- columnName = "isWifiEnabled",
+ columnName = "isEnabled",
initialValue = wifiManager.isWifiEnabled,
)
.stateIn(
@@ -141,7 +141,7 @@
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
- columnName = "isWifiDefault",
+ columnName = "isDefault",
initialValue = false,
)
.stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
@@ -212,7 +212,7 @@
.distinctUntilChanged()
.logDiffsForTable(
wifiTableLogBuffer,
- columnPrefix = "wifiNetwork",
+ columnPrefix = "",
initialValue = WIFI_NETWORK_DEFAULT,
)
// There will be multiple wifi icons in different places that will frequently
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
index e491d2b..094bcf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -53,4 +53,4 @@
}
}
-private const val COL_ICON = "wifiIcon"
+private const val COL_ICON = "icon"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
index e2bebbe..f0949ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
@@ -25,6 +25,8 @@
* If controls become available, initiate this callback with the desired position
*/
fun onControlsUpdate(position: Int?)
+
+ fun removeControlsAutoTracker()
}
/** Add callback, supporting only a single callback at once */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index 341eb3b..4950482 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -21,16 +21,15 @@
import android.content.SharedPreferences
import android.provider.Settings
import android.util.Log
-
import com.android.systemui.R
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.statusbar.phone.AutoTileManager
import com.android.systemui.statusbar.policy.DeviceControlsController.Callback
import com.android.systemui.util.settings.SecureSettings
-
import javax.inject.Inject
/**
@@ -87,6 +86,10 @@
* incorrect.
*/
override fun setCallback(callback: Callback) {
+ if (!controlsComponent.isEnabled()) {
+ callback.removeControlsAutoTracker()
+ return
+ }
// Treat any additional call as a reset before recalculating
removeCallback()
this.callback = callback
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt
new file mode 100644
index 0000000..5e36750
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Returns a [Flow] that emits events whenever a [NotificationEntry] enters or exists the "heads up"
+ * state.
+ */
+val HeadsUpManager.headsUpEvents: Flow<Pair<NotificationEntry, Boolean>>
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : OnHeadsUpChangedListener {
+ override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
+ trySend(entry to isHeadsUp)
+ }
+ }
+ addListener(listener)
+ awaitClose { removeListener(listener) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index cc6fdcc..9ad36fd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -438,6 +438,11 @@
}
@Override
+ public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) {
+ update(false /* updateAlways */);
+ }
+
+ @Override
public void onKeyguardVisibilityChanged(boolean visible) {
update(false /* updateAlways */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 3e3a891..2b7ea2a 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -374,7 +374,7 @@
@Main Resources resources,
WakefulnessLifecycle wakefulnessLifecycle) {
mContext = context;
- mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEMES);
+ mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME);
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
mDeviceProvisionedController = deviceProvisionedController;
mBroadcastDispatcher = broadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index e5ab473..ad1e5fe 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -47,12 +47,14 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
@@ -120,9 +122,25 @@
featureFlags: FeatureFlags,
) : UserRepository {
- private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
- override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
- _userSwitcherSettings.asStateFlow().filterNotNull()
+ private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> =
+ globalSettings
+ .observerFlow(
+ names =
+ arrayOf(
+ SETTING_SIMPLE_USER_SWITCHER,
+ Settings.Global.ADD_USERS_WHEN_LOCKED,
+ Settings.Global.USER_SWITCHER_ENABLED,
+ ),
+ userId = UserHandle.USER_SYSTEM,
+ )
+ .onStart { emit(Unit) } // Forces an initial update.
+ .map { getSettings() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = runBlocking { getSettings() },
+ )
+ override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> = _userSwitcherSettings
private val _userInfos = MutableStateFlow<List<UserInfo>?>(null)
override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull()
@@ -154,7 +172,6 @@
init {
observeSelectedUser()
- observeUserSettings()
if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
observeUserSwitching()
}
@@ -237,23 +254,6 @@
.launchIn(applicationScope)
}
- private fun observeUserSettings() {
- globalSettings
- .observerFlow(
- names =
- arrayOf(
- SETTING_SIMPLE_USER_SWITCHER,
- Settings.Global.ADD_USERS_WHEN_LOCKED,
- Settings.Global.USER_SWITCHER_ENABLED,
- ),
- userId = UserHandle.USER_SYSTEM,
- )
- .onStart { emit(Unit) } // Forces an initial update.
- .map { getSettings() }
- .onEach { _userSwitcherSettings.value = it }
- .launchIn(applicationScope)
- }
-
private suspend fun getSettings(): UserSwitcherSettingsModel {
return withContext(backgroundDispatcher) {
val isSimpleUserSwitcher =
diff --git a/packages/SystemUI/src/com/android/systemui/util/BackupManagerProxy.kt b/packages/SystemUI/src/com/android/systemui/util/BackupManagerProxy.kt
new file mode 100644
index 0000000..f542434
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/BackupManagerProxy.kt
@@ -0,0 +1,17 @@
+package com.android.systemui.util
+
+import android.app.backup.BackupManager
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Wrapper around [BackupManager] useful for testing. */
+@SysUISingleton
+class BackupManagerProxy @Inject constructor() {
+
+ /** Wrapped version of [BackupManager.dataChanged] */
+ fun dataChanged(packageName: String) = BackupManager.dataChanged(packageName)
+
+ /** Wrapped version of [BackupManager.dataChangedForUser] */
+ fun dataChangedForUser(userId: Int, packageName: String) =
+ BackupManager.dataChangedForUser(userId, packageName)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java b/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java
new file mode 100644
index 0000000..b41bca0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.condition;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
+
+import java.util.Set;
+
+/**
+ * {@link ConditionalCoreStartable} is a {@link com.android.systemui.CoreStartable} abstract
+ * implementation where conditions must be met before routines are executed.
+ */
+public abstract class ConditionalCoreStartable implements CoreStartable {
+ private final Monitor mMonitor;
+ private final Set<Condition> mConditionSet;
+ private Monitor.Subscription.Token mStartToken;
+ private Monitor.Subscription.Token mBootCompletedToken;
+
+ public ConditionalCoreStartable(Monitor monitor) {
+ this(monitor, null);
+ }
+
+ public ConditionalCoreStartable(Monitor monitor, Set<Condition> conditionSet) {
+ mMonitor = monitor;
+ mConditionSet = conditionSet;
+ }
+
+ @Override
+ public final void start() {
+ if (mConditionSet == null || mConditionSet.isEmpty()) {
+ onStart();
+ return;
+ }
+
+ mStartToken = mMonitor.addSubscription(
+ new Monitor.Subscription.Builder(allConditionsMet -> {
+ if (allConditionsMet) {
+ mMonitor.removeSubscription(mStartToken);
+ mStartToken = null;
+ onStart();
+ }
+ }).addConditions(mConditionSet)
+ .build());
+ }
+
+ protected abstract void onStart();
+
+ @Override
+ public final void onBootCompleted() {
+ if (mConditionSet == null || mConditionSet.isEmpty()) {
+ bootCompleted();
+ return;
+ }
+
+ mBootCompletedToken = mMonitor.addSubscription(
+ new Monitor.Subscription.Builder(allConditionsMet -> {
+ if (allConditionsMet) {
+ mMonitor.removeSubscription(mBootCompletedToken);
+ mBootCompletedToken = null;
+ bootCompleted();
+ }
+ }).addConditions(mConditionSet)
+ .build());
+ }
+
+ protected void bootCompleted() {
+ }
+}
diff --git a/packages/SystemUI/tests/res/layout/invalid_notification_height.xml b/packages/SystemUI/tests/res/layout/invalid_notification_height.xml
new file mode 100644
index 0000000..aac43bf
--- /dev/null
+++ b/packages/SystemUI/tests/res/layout/invalid_notification_height.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="5dp"/>
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 39cc34b..e8d50ca 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -21,6 +21,8 @@
import android.hardware.biometrics.BiometricFaceConstants
import android.net.Uri
import android.os.Handler
+import android.os.PowerManager
+import android.os.PowerManager.WAKE_REASON_BIOMETRIC
import android.os.UserHandle
import android.provider.Settings
import androidx.test.filters.SmallTest
@@ -48,6 +50,8 @@
private val fakeFaceErrorsUri = Uri.Builder().appendPath("face-errors").build()
private val fakeFaceAcquiredUri = Uri.Builder().appendPath("face-acquired").build()
private val fakeUnlockIntentBioEnroll = Uri.Builder().appendPath("unlock-intent-bio").build()
+ private val fakeWakeupsConsideredUnlockIntents =
+ Uri.Builder().appendPath("wakeups-considered-unlock-intent").build()
@Mock
private lateinit var secureSettings: SecureSettings
@@ -82,6 +86,9 @@
`when`(secureSettings.getUriFor(
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED))
.thenReturn(fakeUnlockIntentBioEnroll)
+ `when`(secureSettings.getUriFor(
+ Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
+ .thenReturn(fakeWakeupsConsideredUnlockIntents)
activeUnlockConfig = ActiveUnlockConfig(
handler,
@@ -92,18 +99,18 @@
}
@Test
- fun testRegsitersForSettingsChanges() {
+ fun registersForSettingsChanges() {
verifyRegisterSettingObserver()
}
@Test
- fun testOnWakeupSettingChanged() {
+ fun onWakeupSettingChanged() {
verifyRegisterSettingObserver()
// GIVEN no active unlock settings enabled
assertFalse(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE)
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)
)
// WHEN unlock on wake is allowed
@@ -114,26 +121,26 @@
// THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure
assertTrue(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE)
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)
)
assertTrue(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)
)
assertTrue(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL)
)
}
@Test
- fun testOnUnlockIntentSettingChanged() {
+ fun onUnlockIntentSettingChanged() {
verifyRegisterSettingObserver()
// GIVEN no active unlock settings enabled
assertFalse(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT)
)
// WHEN unlock on biometric failed is allowed
@@ -143,15 +150,15 @@
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
}
@Test
- fun testOnBioFailSettingChanged() {
+ fun onBioFailSettingChanged() {
verifyRegisterSettingObserver()
// GIVEN no active unlock settings enabled and triggering unlock intent on biometric
@@ -161,7 +168,7 @@
0)).thenReturn("")
updateSetting(fakeUnlockIntentBioEnroll)
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
// WHEN unlock on biometric failed is allowed
`when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
@@ -170,15 +177,15 @@
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE))
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
}
@Test
- fun testFaceErrorSettingsChanged() {
+ fun faceErrorSettingsChanged() {
verifyRegisterSettingObserver()
// GIVEN unlock on biometric fail
@@ -200,7 +207,7 @@
}
@Test
- fun testFaceAcquiredSettingsChanged() {
+ fun faceAcquiredSettingsChanged() {
verifyRegisterSettingObserver()
// GIVEN unlock on biometric fail
@@ -228,7 +235,7 @@
}
@Test
- fun testTriggerOnUnlockIntentWhenBiometricEnrolledNone() {
+ fun triggerOnUnlockIntentWhenBiometricEnrolledNone() {
verifyRegisterSettingObserver()
// GIVEN unlock on biometric fail
@@ -244,16 +251,16 @@
// WHEN unlock intent is allowed when NO biometrics are enrolled (0)
`when`(secureSettings.getStringForUser(
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
- 0)).thenReturn("${ActiveUnlockConfig.BIOMETRIC_TYPE_NONE}")
+ 0)).thenReturn("${ActiveUnlockConfig.BiometricType.NONE.intValue}")
updateSetting(fakeUnlockIntentBioEnroll)
// THEN active unlock triggers allowed on unlock intent
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
}
@Test
- fun testTriggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() {
+ fun triggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() {
verifyRegisterSettingObserver()
// GIVEN unlock on biometric fail
@@ -263,7 +270,7 @@
// GIVEN fingerprint and face are both enrolled
activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
- `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true)
+ `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
`when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
// WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs
@@ -271,29 +278,99 @@
`when`(secureSettings.getStringForUser(
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
0)).thenReturn(
- "${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FACE}" +
- "|${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FINGERPRINT}")
+ "${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" +
+ "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}")
updateSetting(fakeUnlockIntentBioEnroll)
// THEN active unlock triggers NOT allowed on unlock intent
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
// WHEN fingerprint ONLY enrolled
- `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
`when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true)
// THEN active unlock triggers allowed on unlock intent
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
// WHEN face ONLY enrolled
- `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true)
+ `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
`when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
// THEN active unlock triggers allowed on unlock intent
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT))
+ }
+
+ @Test
+ fun isWakeupConsideredUnlockIntent_singleValue() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN lift is considered an unlock intent
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ 0)).thenReturn(PowerManager.WAKE_REASON_LIFT.toString())
+ updateSetting(fakeWakeupsConsideredUnlockIntents)
+
+ // THEN only WAKE_REASON_LIFT is considered an unlock intent
+ for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
+ if (wakeReason == PowerManager.WAKE_REASON_LIFT) {
+ assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
+ } else {
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
+ }
+ }
+ }
+
+ @Test
+ fun isWakeupConsideredUnlockIntent_multiValue() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN lift and tap are considered an unlock intent
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ 0)).thenReturn(
+ PowerManager.WAKE_REASON_LIFT.toString() +
+ "|" +
+ PowerManager.WAKE_REASON_TAP.toString()
+ )
+ updateSetting(fakeWakeupsConsideredUnlockIntents)
+
+ // THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent
+ for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
+ if (wakeReason == PowerManager.WAKE_REASON_LIFT ||
+ wakeReason == PowerManager.WAKE_REASON_TAP) {
+ assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
+ } else {
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
+ }
+ }
+ assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_LIFT))
+ assertTrue(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP))
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
+ PowerManager.WAKE_REASON_UNFOLD_DEVICE))
+ }
+
+ @Test
+ fun isWakeupConsideredUnlockIntent_emptyValues() {
+ verifyRegisterSettingObserver()
+
+ // GIVEN lift and tap are considered an unlock intent
+ `when`(secureSettings.getStringForUser(
+ Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ 0)).thenReturn(" ")
+ updateSetting(fakeWakeupsConsideredUnlockIntents)
+
+ // THEN no wake up gestures are considered an unlock intent
+ for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason))
+ }
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
+ PowerManager.WAKE_REASON_LIFT))
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(PowerManager.WAKE_REASON_TAP))
+ assertFalse(activeUnlockConfig.isWakeupConsideredUnlockIntent(
+ PowerManager.WAKE_REASON_UNFOLD_DEVICE))
}
private fun updateSetting(uri: Uri) {
@@ -312,6 +389,7 @@
verifyRegisterSettingObserver(fakeFaceErrorsUri)
verifyRegisterSettingObserver(fakeFaceAcquiredUri)
verifyRegisterSettingObserver(fakeUnlockIntentBioEnroll)
+ verifyRegisterSettingObserver(fakeWakeupsConsideredUnlockIntents)
}
private fun verifyRegisterSettingObserver(uri: Uri) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 43a2017..f7fec80 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -58,7 +58,7 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
-import java.util.*
+import java.util.TimeZone
import java.util.concurrent.Executor
import org.mockito.Mockito.`when` as whenever
@@ -105,7 +105,9 @@
repository = FakeKeyguardRepository()
underTest = ClockEventController(
- KeyguardInteractor(repository = repository, commandQueue = commandQueue),
+ KeyguardInteractor(repository = repository,
+ commandQueue = commandQueue,
+ featureFlags = featureFlags),
KeyguardTransitionInteractor(repository = transitionRepository),
broadcastDispatcher,
batteryController,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 36b3f89..ccc4e4a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -300,8 +300,9 @@
ArgumentCaptor<ContentObserver> observerCaptor =
ArgumentCaptor.forClass(ContentObserver.class);
mController.init();
- verify(mSecureSettings).registerContentObserverForUser(any(String.class),
- anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
+ verify(mSecureSettings).registerContentObserverForUser(
+ eq(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
+ anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL));
ContentObserver observer = observerCaptor.getValue();
mExecutor.runAllReady();
@@ -347,6 +348,22 @@
assertEquals(0, mController.getClockBottom(10));
}
+ @Test
+ public void testChangeLockscreenWeatherEnabledSetsWeatherViewVisible() {
+ when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
+ ArgumentCaptor<ContentObserver> observerCaptor =
+ ArgumentCaptor.forClass(ContentObserver.class);
+ mController.init();
+ verify(mSecureSettings).registerContentObserverForUser(
+ eq(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED), anyBoolean(),
+ observerCaptor.capture(), eq(UserHandle.USER_ALL));
+ ContentObserver observer = observerCaptor.getValue();
+ mExecutor.runAllReady();
+ // When a settings change has occurred, check that view is visible.
+ observer.onChange(true);
+ mExecutor.runAllReady();
+ assertEquals(View.VISIBLE, mFakeWeatherView.getVisibility());
+ }
private void verifyAttachment(VerificationMode times) {
verify(mClockRegistry, times).registerClockChangeListener(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 8dc1e8f..254f953 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -190,7 +189,6 @@
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
- assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
@@ -200,7 +198,6 @@
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
- assertThat(mSmallClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
@@ -215,7 +212,6 @@
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
- assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
@@ -227,7 +223,6 @@
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
- assertThat(mLargeClockFrame.getVisibility()).isEqualTo(INVISIBLE);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d1650b7..853d8ed 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -27,6 +27,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
@@ -240,7 +241,7 @@
@Mock
private FingerprintInteractiveToAuthProvider mInteractiveToAuthProvider;
-
+ private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -280,9 +281,7 @@
when(mFaceSensorProperties.get(anyInt())).thenReturn(
createFaceSensorProperties(/* supportsFaceDetection = */ false));
- when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
- when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
- when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of(
+ mFingerprintSensorProperties = List.of(
new FingerprintSensorPropertiesInternal(1 /* sensorId */,
FingerprintSensorProperties.STRENGTH_STRONG,
1 /* maxEnrollmentsPerUser */,
@@ -291,7 +290,11 @@
"1.01" /* firmwareVersion */,
"00000001" /* serialNumber */, "" /* softwareVersion */)),
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- false /* resetLockoutRequiresHAT */)));
+ false /* resetLockoutRequiresHAT */));
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(
+ mFingerprintSensorProperties);
when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
when(mUserManager.isPrimaryUser()).thenReturn(true);
when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
@@ -685,12 +688,36 @@
// WHEN fingerprint is locked out
fingerprintErrorLockedOut();
- // THEN unlocking with fingeprint is not allowed
+ // THEN unlocking with fingerprint is not allowed
Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
BiometricSourceType.FINGERPRINT));
}
@Test
+ public void trustAgentHasTrust() {
+ // WHEN user has trust
+ mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
+
+ // THEN user is considered as "having trust" and bouncer can be skipped
+ Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
+ Assert.assertTrue(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()));
+ }
+
+ @Test
+ public void trustAgentHasTrust_fingerprintLockout() {
+ // GIVEN user has trust
+ mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
+ Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
+
+ // WHEN fingerprint is locked out
+ fingerprintErrorLockedOut();
+
+ // THEN user is NOT considered as "having trust" and bouncer cannot be skipped
+ Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
+ Assert.assertFalse(mKeyguardUpdateMonitor.getUserCanSkipBouncer(getCurrentUser()));
+ }
+
+ @Test
public void testTriesToAuthenticate_whenBouncer() {
setKeyguardBouncerVisibility(true);
@@ -746,12 +773,43 @@
}
@Test
+ public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning() {
+ // GIVEN mocked keyguardUpdateMonitorCallback
+ KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback =
+ mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback);
+
+ // GIVEN bypass is enabled, face detection is supported
+ lockscreenBypassIsAllowed();
+ supportsFaceDetection();
+ keyguardIsVisible();
+
+ // GIVEN udfps is supported and strong auth required for weak biometrics (face) only
+ givenUdfpsSupported();
+ strongAuthRequiredForWeakBiometricOnly(); // this allows fingerprint to run but not face
+
+ // WHEN the device wakes up
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+
+ // THEN face detect and authenticate are NOT triggered
+ verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
+ verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+ anyBoolean());
+
+ // THEN biometric help message sent to callback
+ verify(keyguardUpdateMonitorCallback).onBiometricHelp(
+ eq(BIOMETRIC_HELP_FACE_NOT_AVAILABLE), anyString(), eq(BiometricSourceType.FACE));
+ }
+
+ @Test
public void faceDetect_whenStrongAuthRequiredAndBypass() {
// GIVEN bypass is enabled, face detection is supported and strong auth is required
lockscreenBypassIsAllowed();
supportsFaceDetection();
strongAuthRequiredEncrypted();
keyguardIsVisible();
+ // fingerprint is NOT running, UDFPS is NOT supported
// WHEN the device wakes up
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -1709,7 +1767,7 @@
}
@Test
- public void testShouldListenForFace_whenOccludingAppRequestsFaceAuth_returnsTrue()
+ public void shouldListenForFace_secureCameraLaunchedButAlternateBouncerIsLaunched_returnsTrue()
throws RemoteException {
// Face auth should run when the following is true.
keyguardNotGoingAway();
@@ -1725,7 +1783,7 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
- occludingAppRequestsFaceAuth();
+ alternateBouncerVisible();
mTestableLooper.processAllMessages();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
@@ -1815,7 +1873,7 @@
}
@Test
- public void testShouldListenForFace_whenUdfpsBouncerIsShowing_returnsTrue()
+ public void testShouldListenForFace_whenAlternateBouncerIsShowing_returnsTrue()
throws RemoteException {
// Preconditions for face auth to run
keyguardNotGoingAway();
@@ -1827,13 +1885,13 @@
mTestableLooper.processAllMessages();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
- mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+ mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
}
@Test
- public void testShouldListenForFace_udfpsBouncerIsShowingButDeviceGoingToSleep_returnsFalse()
+ public void testShouldListenForFace_alternateBouncerShowingButDeviceGoingToSleep_returnsFalse()
throws RemoteException {
// Preconditions for face auth to run
keyguardNotGoingAway();
@@ -1843,7 +1901,7 @@
biometricsEnabledForCurrentUser();
userNotCurrentlySwitching();
deviceNotGoingToSleep();
- mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+ alternateBouncerVisible();
mTestableLooper.processAllMessages();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
@@ -1853,6 +1911,10 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
}
+ private void alternateBouncerVisible() {
+ mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+ }
+
@Test
public void testShouldListenForFace_whenFaceIsLockedOut_returnsTrue()
throws RemoteException {
@@ -1863,7 +1925,7 @@
biometricsNotDisabledThroughDevicePolicyManager();
biometricsEnabledForCurrentUser();
userNotCurrentlySwitching();
- mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+ mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
mTestableLooper.processAllMessages();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
@@ -2183,7 +2245,7 @@
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
.thenReturn(true);
// WHEN fingerprint fails
@@ -2206,7 +2268,7 @@
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
.thenReturn(true);
// WHEN face fails & bypass is not allowed
@@ -2230,7 +2292,7 @@
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
.thenReturn(true);
// WHEN face fails & bypass is not allowed
@@ -2252,7 +2314,7 @@
// GIVEN active unlock triggers on biometric failures
when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin(
- ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL))
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
.thenReturn(true);
// WHEN face fails & on the bouncer
@@ -2442,6 +2504,11 @@
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
}
+ private void strongAuthRequiredForWeakBiometricOnly() {
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(true))).thenReturn(true);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(false))).thenReturn(false);
+ }
+
private void strongAuthNotRequired() {
when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
.thenReturn(0);
@@ -2496,6 +2563,12 @@
mTestableLooper.processAllMessages();
}
+ private void givenUdfpsSupported() {
+ Assert.assertFalse(mFingerprintSensorProperties.isEmpty());
+ when(mAuthController.getUdfpsProps()).thenReturn(mFingerprintSensorProperties);
+ Assert.assertTrue(mKeyguardUpdateMonitor.isUdfpsSupported());
+ }
+
private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) {
BroadcastReceiver.PendingResult pendingResult =
new BroadcastReceiver.PendingResult(Activity.RESULT_OK,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 05bd1e4..3d0d036 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -159,7 +159,9 @@
mAuthRippleController,
mResources,
new KeyguardTransitionInteractor(mTransitionRepository),
- new KeyguardInteractor(new FakeKeyguardRepository(), mCommandQueue),
+ new KeyguardInteractor(new FakeKeyguardRepository(),
+ mCommandQueue,
+ mFeatureFlags),
mFeatureFlags
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
index 32edf8f..babbe45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
@@ -11,6 +11,7 @@
import com.android.systemui.flags.Flag
import com.android.systemui.flags.FlagListenable
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.ReleasedFlag
import com.android.systemui.flags.UnreleasedFlag
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
@@ -102,7 +103,7 @@
@Test
fun initialize_enablesUnbundledChooser_whenFlagEnabled() {
// Arrange
- whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+ setFlagMock(true)
// Act
chooserSelector.start()
@@ -118,7 +119,7 @@
@Test
fun initialize_disablesUnbundledChooser_whenFlagDisabled() {
// Arrange
- whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ setFlagMock(false)
// Act
chooserSelector.start()
@@ -134,7 +135,7 @@
@Test
fun enablesUnbundledChooser_whenFlagBecomesEnabled() {
// Arrange
- whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ setFlagMock(false)
chooserSelector.start()
verify(mockFeatureFlags).addListener(
eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
@@ -147,7 +148,7 @@
)
// Act
- whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+ setFlagMock(true)
flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name))
// Assert
@@ -161,7 +162,7 @@
@Test
fun disablesUnbundledChooser_whenFlagBecomesDisabled() {
// Arrange
- whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
+ setFlagMock(true)
chooserSelector.start()
verify(mockFeatureFlags).addListener(
eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
@@ -174,7 +175,7 @@
)
// Act
- whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ setFlagMock(false)
flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name))
// Assert
@@ -188,7 +189,7 @@
@Test
fun doesNothing_whenAnotherFlagChanges() {
// Arrange
- whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
+ setFlagMock(false)
chooserSelector.start()
verify(mockFeatureFlags).addListener(
eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
@@ -197,13 +198,17 @@
clearInvocations(mockPackageManager)
// Act
- whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
flagListener.value.onFlagChanged(TestFlagEvent("other flag"))
// Assert
verifyZeroInteractions(mockPackageManager)
}
+ private fun setFlagMock(enabled: Boolean) {
+ whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(enabled)
+ whenever(mockFeatureFlags.isEnabled(any<ReleasedFlag>())).thenReturn(enabled)
+ }
+
private class TestFlagEvent(override val flagName: String) : FlagListenable.FlagEvent {
override fun requestNoRestart() {}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index fc11148..4cf5a4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -34,8 +34,10 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -104,8 +106,12 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.List;
@@ -119,7 +125,7 @@
private WindowManager mWindowManager;
private DisplayManager mDisplayManager;
private SecureSettings mSecureSettings;
- private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private FakeExecutor mExecutor;
private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
private FakeThreadFactory mThreadFactory;
private ArrayList<DecorProvider> mPrivacyDecorProviders;
@@ -161,6 +167,8 @@
private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener;
@Mock
private CutoutDecorProviderFactory mCutoutFactory;
+ @Captor
+ private ArgumentCaptor<AuthController.Callback> mAuthControllerCallback;
private List<DecorProvider> mMockCutoutList;
@Before
@@ -169,6 +177,7 @@
Handler mainHandler = new Handler(TestableLooper.get(this).getLooper());
mSecureSettings = new FakeSettings();
+ mExecutor = new FakeExecutor(new FakeSystemClock());
mThreadFactory = new FakeThreadFactory(mExecutor);
mThreadFactory.setHandler(mainHandler);
@@ -1166,6 +1175,44 @@
}
@Test
+ public void faceSensorLocationChangesReloadsFaceScanningOverlay() {
+ mFaceScanningProviders = new ArrayList<>();
+ mFaceScanningProviders.add(mFaceScanningDecorProvider);
+ when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders);
+ when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true);
+ ScreenDecorations screenDecorations = new ScreenDecorations(mContext, mExecutor,
+ mSecureSettings, mTunerService, mUserTracker, mDisplayTracker, mDotViewController,
+ mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
+ new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController);
+ screenDecorations.start();
+ verify(mAuthController).addCallback(mAuthControllerCallback.capture());
+ when(mContext.getDisplay()).thenReturn(mDisplay);
+ when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ DisplayInfo displayInfo = invocation.getArgument(0);
+ int modeId = 1;
+ displayInfo.modeId = modeId;
+ displayInfo.supportedModes = new Display.Mode[]{new Display.Mode(modeId, 1024, 1024,
+ 90)};
+ return false;
+ }
+ });
+ mExecutor.runAllReady();
+ clearInvocations(mFaceScanningDecorProvider);
+
+ AuthController.Callback callback = mAuthControllerCallback.getValue();
+ callback.onFaceSensorLocationChanged();
+ mExecutor.runAllReady();
+
+ verify(mFaceScanningDecorProvider).onReloadResAndMeasure(any(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ any());
+ }
+
+ @Test
public void testPrivacyDotShowingListenerWorkWellWithNullParameter() {
mPrivacyDotShowingListener.onPrivacyDotShown(null);
mPrivacyDotShowingListener.onPrivacyDotHidden(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 1e62fd23..316de59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -268,6 +268,12 @@
}
}
+ @Test
+ fun showFromDialogDoesNotCrashWhenShownFromRandomDialog() {
+ val dialog = createDialogAndShowFromDialog(animateFrom = TestDialog(context))
+ dialog.dismiss()
+ }
+
private fun createAndShowDialog(
animator: DialogLaunchAnimator = dialogLaunchAnimator,
): TestDialog {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt
new file mode 100644
index 0000000..d552c9d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/coroutine/CoroutineResultTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.common.coroutine
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** atest SystemUITests:CoroutineResultTest */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CoroutineResultTest : SysuiTestCase() {
+
+ @Test
+ fun suspendRunCatching_shouldReturnSuccess() = runTest {
+ val actual = suspendRunCatching { "Placeholder" }
+ assertThat(actual.isSuccess).isTrue()
+ assertThat(actual.getOrNull()).isEqualTo("Placeholder")
+ }
+
+ @Test
+ fun suspendRunCatching_whenExceptionThrow_shouldResumeWithException() = runTest {
+ val actual = suspendRunCatching { throw Exception() }
+ assertThat(actual.isFailure).isTrue()
+ assertThat(actual.exceptionOrNull()).isInstanceOf(Exception::class.java)
+ }
+
+ @Test(expected = CancellationException::class)
+ fun suspendRunCatching_whenCancelled_shouldResumeWithException() = runTest {
+ suspendRunCatching { cancel() }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt
new file mode 100644
index 0000000..1e4753e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/coroutines/FlowTest.kt
@@ -0,0 +1,24 @@
+package com.android.systemui.coroutines
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FlowTest : SysuiTestCase() {
+
+ @Test
+ fun collectLastValue() = runTest {
+ val flow = flowOf(0, 1, 2)
+ val lastValue by collectLastValue(flow)
+ assertThat(lastValue).isEqualTo(2)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
index ed16721..686782f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -20,6 +20,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
@@ -48,22 +49,22 @@
@Test
fun testRestart_ImmediateWhenAsleep() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- restarter.restartSystemUI()
- verify(systemExitRestarter).restartSystemUI()
+ restarter.restartSystemUI("Restart for test")
+ verify(systemExitRestarter).restartSystemUI(any())
}
@Test
fun testRestart_WaitsForSceenOff() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- restarter.restartSystemUI()
- verify(systemExitRestarter, never()).restartSystemUI()
+ restarter.restartSystemUI("Restart for test")
+ verify(systemExitRestarter, never()).restartSystemUI(any())
val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
verify(wakefulnessLifecycle).addObserver(captor.capture())
captor.value.onFinishedGoingToSleep()
- verify(systemExitRestarter).restartSystemUI()
+ verify(systemExitRestarter).restartSystemUI(any())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index d8bbd04..2bcd75b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import org.junit.Assert
import org.junit.Before
@@ -63,8 +62,6 @@
@Mock
private lateinit var globalSettings: GlobalSettings
@Mock
- private lateinit var secureSettings: SecureSettings
- @Mock
private lateinit var systemProperties: SystemPropertiesHelper
@Mock
private lateinit var resources: Resources
@@ -92,7 +89,6 @@
flagManager,
mockContext,
globalSettings,
- secureSettings,
systemProperties,
resources,
serverFlagReader,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
index 7d807e2..6060afe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -63,7 +64,7 @@
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI()
+ restarter.restartSystemUI("Restart for test")
assertThat(executor.numPending()).isEqualTo(1)
}
@@ -72,11 +73,11 @@
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
whenever(batteryController.isPluggedIn).thenReturn(true)
- restarter.restartSystemUI()
- verify(systemExitRestarter, never()).restartSystemUI()
+ restarter.restartSystemUI("Restart for test")
+ verify(systemExitRestarter, never()).restartSystemUI("Restart for test")
executor.advanceClockToLast()
executor.runAllReady()
- verify(systemExitRestarter).restartSystemUI()
+ verify(systemExitRestarter).restartSystemUI(any())
}
@Test
@@ -85,7 +86,7 @@
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI()
+ restarter.restartSystemUI("Restart for test")
assertThat(executor.numPending()).isEqualTo(0)
}
@@ -95,7 +96,7 @@
whenever(batteryController.isPluggedIn).thenReturn(false)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI()
+ restarter.restartSystemUI("Restart for test")
assertThat(executor.numPending()).isEqualTo(0)
}
@@ -105,8 +106,8 @@
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI()
- restarter.restartSystemUI()
+ restarter.restartSystemUI("Restart for test")
+ restarter.restartSystemUI("Restart for test")
assertThat(executor.numPending()).isEqualTo(1)
}
@@ -115,7 +116,7 @@
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
whenever(batteryController.isPluggedIn).thenReturn(true)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI()
+ restarter.restartSystemUI("Restart for test")
val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
verify(wakefulnessLifecycle).addObserver(captor.capture())
@@ -131,7 +132,7 @@
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
whenever(batteryController.isPluggedIn).thenReturn(false)
assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI()
+ restarter.restartSystemUI("Restart for test")
val captor =
ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 1633912..4ebf974 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -45,7 +45,7 @@
fun setup() {
MockitoAnnotations.initMocks(this)
- serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor)
+ serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor, false)
}
@Test
@@ -56,6 +56,6 @@
deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
executor.runAllReady()
- verify(changeListener).onChange()
+ verify(changeListener).onChange(flag)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index fb54d6d..4415033 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -157,25 +157,28 @@
dumpManager = mock(),
userHandle = UserHandle.SYSTEM,
)
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
+ set(Flags.REVAMPED_WALLPAPER_UI, true)
+ set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
underTest.interactor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor =
KeyguardInteractor(
repository = FakeKeyguardRepository(),
commandQueue = commandQueue,
+ featureFlags = featureFlags,
),
registry = mock(),
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
- set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
- set(Flags.REVAMPED_WALLPAPER_UI, true)
- set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
- },
+ featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
index 2290676..c3b0e5226 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
@@ -38,6 +38,7 @@
import androidx.test.filters.SmallTest;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
@@ -66,6 +67,8 @@
private KeyguardIndicationTextView mView;
@Mock
private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private KeyguardLogger mLogger;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor;
@@ -77,7 +80,7 @@
MockitoAnnotations.initMocks(this);
when(mView.getTextColors()).thenReturn(ColorStateList.valueOf(Color.WHITE));
mController = new KeyguardIndicationRotateTextViewController(mView, mExecutor,
- mStatusBarStateController);
+ mStatusBarStateController, mLogger);
mController.onViewAttached();
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index 58cdec4..db18ba6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -20,6 +20,7 @@
import android.app.StatusBarManager
import android.content.Context
import android.content.pm.PackageManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.camera.CameraGestureHelper
@@ -31,14 +32,13 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class CameraQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var cameraGestureHelper: CameraGestureHelper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
index 15b85de..64839e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt
@@ -22,6 +22,7 @@
import android.provider.Settings.Global.ZEN_MODE_OFF
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.EnableZenModeDialog
import com.android.systemui.R
@@ -51,7 +52,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
@@ -60,7 +60,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class DoNotDisturbQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var zenModeController: ZenModeController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
index 9fa7db1..31391ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.common.shared.model.Icon
@@ -35,13 +36,12 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
@Mock private lateinit var context: Context
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index 659c1e5..2c1c04c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -34,13 +35,12 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var component: ControlsComponent
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 3b0169d..3bae7f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.content.res.Resources
import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -40,7 +41,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
@@ -48,7 +48,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() {
@Mock private lateinit var sharedPrefs: FakeSharedPreferences
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index 3d65713..1259b47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -20,6 +20,7 @@
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -40,7 +41,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
@@ -51,7 +51,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() {
@Mock private lateinit var userFileManager: UserFileManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
index b21cec9..c08ef42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
@@ -19,6 +19,7 @@
import android.content.pm.UserInfo
import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.FakeUserTracker
@@ -37,13 +38,12 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() {
@Mock private lateinit var userHandle: UserHandle
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
index 34f3ed8..facc747 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -21,6 +21,7 @@
import android.media.AudioManager
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
@@ -47,7 +48,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
@@ -55,8 +55,8 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
-class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() {
+@RunWith(AndroidJUnit4::class)
+class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() {
@Mock
private lateinit var featureFlags: FeatureFlags
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 9d2ddff..1adf808 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
@@ -33,13 +34,12 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var controller: QRCodeScannerController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 8f56b95..752963f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -20,6 +20,7 @@
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsResponse
import android.service.quickaccesswallet.QuickAccessWalletClient
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -41,14 +42,13 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var walletController: QuickAccessWalletController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
index 805dcec..5bd86bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.SysuiTestCase
@@ -32,7 +33,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -40,7 +40,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var activityIntentHelper: ActivityIntentHelper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index ddd1049..21ad5e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -18,8 +18,12 @@
package com.android.systemui.keyguard.data.repository
import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE
+import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT
import android.content.Intent
import android.content.pm.UserInfo
+import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
@@ -29,8 +33,14 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.repository.BiometricType.FACE
+import com.android.systemui.keyguard.data.repository.BiometricType.REAR_FINGERPRINT
+import com.android.systemui.keyguard.data.repository.BiometricType.SIDE_FINGERPRINT
+import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -41,9 +51,14 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -56,6 +71,12 @@
@Mock private lateinit var authController: AuthController
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var biometricManager: BiometricManager
+ @Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback>
+ @Captor
+ private lateinit var biometricManagerCallback:
+ ArgumentCaptor<IBiometricEnabledOnKeyguardCallback.Stub>
private lateinit var userRepository: FakeUserRepository
private lateinit var testDispatcher: TestDispatcher
@@ -72,7 +93,7 @@
}
private suspend fun createBiometricSettingsRepository() {
- userRepository.setUserInfos(listOf(PRIMARY_USER))
+ userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
userRepository.setSelectedUserInfo(PRIMARY_USER)
underTest =
BiometricSettingsRepositoryImpl(
@@ -85,33 +106,30 @@
scope = testScope.backgroundScope,
backgroundDispatcher = testDispatcher,
looper = testableLooper!!.looper,
+ dumpManager = dumpManager,
+ biometricManager = biometricManager,
)
+ testScope.runCurrent()
}
@Test
fun fingerprintEnrollmentChange() =
testScope.runTest {
createBiometricSettingsRepository()
- val fingerprintEnabledByDevicePolicy = collectLastValue(underTest.isFingerprintEnrolled)
+ val fingerprintEnrolled = collectLastValue(underTest.isFingerprintEnrolled)
runCurrent()
- val captor = argumentCaptor<AuthController.Callback>()
- verify(authController).addCallback(captor.capture())
+ verify(authController).addCallback(authControllerCallback.capture())
whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(true)
- captor.value.onEnrollmentsChanged(
- BiometricType.UNDER_DISPLAY_FINGERPRINT,
- PRIMARY_USER_ID,
- true
- )
- assertThat(fingerprintEnabledByDevicePolicy()).isTrue()
+ enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
+ assertThat(fingerprintEnrolled()).isTrue()
whenever(authController.isFingerprintEnrolled(anyInt())).thenReturn(false)
- captor.value.onEnrollmentsChanged(
- BiometricType.UNDER_DISPLAY_FINGERPRINT,
- PRIMARY_USER_ID,
- false
- )
- assertThat(fingerprintEnabledByDevicePolicy()).isFalse()
+ enrollmentChange(UNDER_DISPLAY_FINGERPRINT, ANOTHER_USER_ID, false)
+ assertThat(fingerprintEnrolled()).isTrue()
+
+ enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, false)
+ assertThat(fingerprintEnrolled()).isFalse()
}
@Test
@@ -124,15 +142,14 @@
val captor = argumentCaptor<LockPatternUtils.StrongAuthTracker>()
verify(lockPatternUtils).registerStrongAuthTracker(captor.capture())
- captor.value
- .getStub()
- .onStrongAuthRequiredChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
+ captor.value.stub.onStrongAuthRequiredChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
assertThat(strongBiometricAllowed()).isTrue()
- captor.value
- .getStub()
- .onStrongAuthRequiredChanged(STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID)
+ captor.value.stub.onStrongAuthRequiredChanged(
+ STRONG_AUTH_REQUIRED_AFTER_BOOT,
+ PRIMARY_USER_ID
+ )
testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
assertThat(strongBiometricAllowed()).isFalse()
}
@@ -146,7 +163,7 @@
runCurrent()
whenever(devicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
- .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT)
+ .thenReturn(KEYGUARD_DISABLE_FINGERPRINT)
broadcastDPMStateChange()
assertThat(fingerprintEnabledByDevicePolicy()).isFalse()
@@ -155,6 +172,137 @@
assertThat(fingerprintEnabledByDevicePolicy()).isTrue()
}
+ @Test
+ fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ runCurrent()
+ clearInvocations(authController)
+
+ whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
+ val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
+
+ assertThat(faceEnrolled()).isFalse()
+ verify(authController).addCallback(authControllerCallback.capture())
+ enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true)
+
+ assertThat(faceEnrolled()).isFalse()
+
+ enrollmentChange(SIDE_FINGERPRINT, PRIMARY_USER_ID, true)
+
+ assertThat(faceEnrolled()).isFalse()
+
+ enrollmentChange(UNDER_DISPLAY_FINGERPRINT, PRIMARY_USER_ID, true)
+
+ assertThat(faceEnrolled()).isFalse()
+
+ enrollmentChange(FACE, ANOTHER_USER_ID, true)
+
+ assertThat(faceEnrolled()).isFalse()
+
+ enrollmentChange(FACE, PRIMARY_USER_ID, true)
+
+ assertThat(faceEnrolled()).isTrue()
+ }
+
+ @Test
+ fun faceEnrollmentStatusOfNewUserUponUserSwitch() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ runCurrent()
+ clearInvocations(authController)
+
+ whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false)
+ whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)
+ val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
+
+ assertThat(faceEnrolled()).isFalse()
+ }
+
+ @Test
+ fun faceEnrollmentChangesArePropagatedAfterUserSwitch() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+
+ userRepository.setSelectedUserInfo(ANOTHER_USER)
+ runCurrent()
+ clearInvocations(authController)
+
+ val faceEnrolled = collectLastValue(underTest.isFaceEnrolled)
+ runCurrent()
+
+ verify(authController).addCallback(authControllerCallback.capture())
+
+ enrollmentChange(FACE, ANOTHER_USER_ID, true)
+
+ assertThat(faceEnrolled()).isTrue()
+ }
+
+ @Test
+ fun devicePolicyControlsFaceAuthenticationEnabledState() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ verify(biometricManager)
+ .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
+
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
+ .thenReturn(KEYGUARD_DISABLE_FINGERPRINT or KEYGUARD_DISABLE_FACE)
+
+ val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
+ runCurrent()
+
+ broadcastDPMStateChange()
+
+ assertThat(isFaceAuthEnabled()).isFalse()
+
+ biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
+ runCurrent()
+ assertThat(isFaceAuthEnabled()).isFalse()
+
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
+ .thenReturn(KEYGUARD_DISABLE_FINGERPRINT)
+ broadcastDPMStateChange()
+
+ assertThat(isFaceAuthEnabled()).isTrue()
+ }
+
+ @Test
+ fun biometricManagerControlsFaceAuthenticationEnabledStatus() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ verify(biometricManager)
+ .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
+
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
+ .thenReturn(0)
+ broadcastDPMStateChange()
+
+ biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
+ val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
+
+ assertThat(isFaceAuthEnabled()).isTrue()
+
+ biometricManagerCallback.value.onChanged(false, PRIMARY_USER_ID)
+
+ assertThat(isFaceAuthEnabled()).isFalse()
+ }
+
+ @Test
+ fun biometricManagerCallbackIsRegisteredOnlyOnce() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+
+ collectLastValue(underTest.isFaceAuthenticationEnabled)()
+ collectLastValue(underTest.isFaceAuthenticationEnabled)()
+ collectLastValue(underTest.isFaceAuthenticationEnabled)()
+
+ verify(biometricManager, times(1)).registerEnabledOnKeyguardCallback(any())
+ }
+
+ private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
+ authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
+ }
+
private fun broadcastDPMStateChange() {
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
receiver.onReceive(
@@ -172,5 +320,13 @@
/* name= */ "primary user",
/* flags= */ UserInfo.FLAG_PRIMARY
)
+
+ private const val ANOTHER_USER_ID = 1
+ private val ANOTHER_USER =
+ UserInfo(
+ /* id= */ ANOTHER_USER_ID,
+ /* name= */ "another user",
+ /* flags= */ UserInfo.FLAG_PRIMARY
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 9203f05..0519a44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -22,6 +22,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.DumpManager
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,6 +45,7 @@
@RunWith(JUnit4::class)
class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var dumpManager: DumpManager
@Captor private lateinit var callbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
private lateinit var testScope: TestScope
@@ -59,6 +61,7 @@
DeviceEntryFingerprintAuthRepositoryImpl(
keyguardUpdateMonitor,
testScope.backgroundScope,
+ dumpManager,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
index 444a2a7..ff22f1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.data.repository
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.SysuiTestCase
@@ -26,13 +27,12 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardBouncerRepositoryTest : SysuiTestCase() {
@Mock private lateinit var systemClock: SystemClock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 6099f01..86e8c9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -19,9 +19,11 @@
import android.content.pm.UserInfo
import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
@@ -39,23 +41,19 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
private lateinit var underTest: KeyguardQuickAffordanceRepository
@@ -65,12 +63,14 @@
private lateinit var userTracker: FakeUserTracker
private lateinit var client1: FakeCustomizationProviderClient
private lateinit var client2: FakeCustomizationProviderClient
+ private lateinit var testScope: TestScope
@Before
fun setUp() {
config1 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_1)
config2 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_2)
- val scope = CoroutineScope(IMMEDIATE)
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
userTracker = FakeUserTracker()
val localUserSelectionManager =
KeyguardQuickAffordanceLocalUserSelectionManager(
@@ -93,7 +93,7 @@
client2 = FakeCustomizationProviderClient()
val remoteUserSelectionManager =
KeyguardQuickAffordanceRemoteUserSelectionManager(
- scope = scope,
+ scope = testScope.backgroundScope,
userTracker = userTracker,
clientFactory =
FakeKeyguardQuickAffordanceProviderClientFactory(
@@ -116,14 +116,14 @@
underTest =
KeyguardQuickAffordanceRepository(
appContext = context,
- scope = scope,
+ scope = testScope.backgroundScope,
localUserSelectionManager = localUserSelectionManager,
remoteUserSelectionManager = remoteUserSelectionManager,
userTracker = userTracker,
legacySettingSyncer =
KeyguardQuickAffordanceLegacySettingSyncer(
- scope = scope,
- backgroundDispatcher = IMMEDIATE,
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
secureSettings = FakeSettings(),
selectionsManager = localUserSelectionManager,
),
@@ -135,15 +135,14 @@
@Test
fun setSelections() =
- runBlocking(IMMEDIATE) {
- var configsBySlotId: Map<String, List<KeyguardQuickAffordanceConfig>>? = null
- val job = underTest.selections.onEach { configsBySlotId = it }.launchIn(this)
+ testScope.runTest {
+ val configsBySlotId = collectLastValue(underTest.selections)
val slotId1 = "slot1"
val slotId2 = "slot2"
underTest.setSelections(slotId1, listOf(config1.key))
assertSelections(
- configsBySlotId,
+ configsBySlotId(),
mapOf(
slotId1 to listOf(config1),
),
@@ -151,7 +150,7 @@
underTest.setSelections(slotId2, listOf(config2.key))
assertSelections(
- configsBySlotId,
+ configsBySlotId(),
mapOf(
slotId1 to listOf(config1),
slotId2 to listOf(config2),
@@ -161,19 +160,17 @@
underTest.setSelections(slotId1, emptyList())
underTest.setSelections(slotId2, listOf(config1.key))
assertSelections(
- configsBySlotId,
+ configsBySlotId(),
mapOf(
slotId1 to emptyList(),
slotId2 to listOf(config1),
),
)
-
- job.cancel()
}
@Test
fun getAffordancePickerRepresentations() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
assertThat(underTest.getAffordancePickerRepresentations())
.isEqualTo(
listOf(
@@ -226,7 +223,7 @@
@Test
fun `selections for secondary user`() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
userTracker.set(
userInfos =
listOf(
@@ -252,12 +249,10 @@
slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
affordanceId = FakeCustomizationProviderClient.AFFORDANCE_2,
)
- val observed = mutableListOf<Map<String, List<KeyguardQuickAffordanceConfig>>>()
- val job = underTest.selections.onEach { observed.add(it) }.launchIn(this)
- yield()
+ val observed = collectLastValue(underTest.selections)
assertSelections(
- observed = observed.last(),
+ observed = observed(),
expected =
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
@@ -266,8 +261,6 @@
),
)
)
-
- job.cancel()
}
private fun assertSelections(
@@ -283,7 +276,6 @@
}
companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
private const val SECONDARY_USER_1 = UserHandle.MIN_SECONDARY_USER_ID + 1
private const val SECONDARY_USER_2 = UserHandle.MIN_SECONDARY_USER_ID + 2
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index f997d18..8bb6a85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -18,6 +18,7 @@
import android.graphics.Point
import android.hardware.biometrics.BiometricSourceType
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -54,13 +55,12 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var statusBarStateController: StatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 32cec09..ae227b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -20,6 +20,7 @@
import android.util.Log
import android.util.Log.TerribleFailure
import android.util.Log.TerribleFailureHandler
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
@@ -43,10 +44,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardTransitionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: KeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
index 4b06905..a181137 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -18,6 +18,7 @@
import android.app.trust.TrustManager
import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.logging.TrustRepositoryLogger
import com.android.systemui.SysuiTestCase
@@ -33,7 +34,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
@@ -43,7 +43,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class TrustRepositoryTest : SysuiTestCase() {
@Mock private lateinit var trustManager: TrustManager
@Captor private lateinit var listenerCaptor: ArgumentCaptor<TrustManager.TrustListener>
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 8caf60f..7ded354 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.ViewMediatorCallback
@@ -36,14 +37,13 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class AlternateBouncerInteractorTest : SysuiTestCase() {
private lateinit var underTest: AlternateBouncerInteractor
private lateinit var bouncerRepository: KeyguardBouncerRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 68d13d3..7d4861b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -18,30 +18,35 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.settings.DisplayTracker
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.CommandQueue.Callbacks
-import com.android.systemui.util.mockito.argumentCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.onCompletion
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
+import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardInteractorTest : SysuiTestCase() {
- @Mock private lateinit var commandQueue: CommandQueue
+ private lateinit var commandQueue: FakeCommandQueue
+ private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var testScope: TestScope
private lateinit var underTest: KeyguardInteractor
private lateinit var repository: FakeKeyguardRepository
@@ -49,38 +54,134 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
+ commandQueue = FakeCommandQueue(mock(Context::class.java), mock(DisplayTracker::class.java))
+ testScope = TestScope()
repository = FakeKeyguardRepository()
- underTest = KeyguardInteractor(repository, commandQueue)
+ underTest = KeyguardInteractor(repository, commandQueue, featureFlags)
}
@Test
- fun onCameraLaunchDetected() = runTest {
- val flow = underTest.onCameraLaunchDetected
- var cameraLaunchSource = collectLastValue(flow)
- runCurrent()
+ fun onCameraLaunchDetected() =
+ testScope.runTest {
+ val flow = underTest.onCameraLaunchDetected
+ var cameraLaunchSource = collectLastValue(flow)
+ runCurrent()
- val captor = argumentCaptor<CommandQueue.Callbacks>()
- verify(commandQueue).addCallback(captor.capture())
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)
+ }
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE)
- captor.value.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE)
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
+ )
+ }
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP)
- captor.value.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
- )
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP)
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)
+ }
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER)
- captor.value.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER
- )
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER)
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE
+ )
+ }
+ assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE)
- captor.value.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE
- )
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE)
+ flow.onCompletion { assertThat(commandQueue.callbackCount()).isEqualTo(0) }
+ }
- flow.onCompletion { verify(commandQueue).removeCallback(captor.value) }
+ @Test
+ fun testKeyguardGuardVisibilityStopsSecureCamera() =
+ testScope.runTest {
+ val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
+ runCurrent()
+
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
+ )
+ }
+
+ assertThat(secureCameraActive()).isTrue()
+
+ // Keyguard is showing but occluded
+ repository.setKeyguardShowing(true)
+ repository.setKeyguardOccluded(true)
+ assertThat(secureCameraActive()).isTrue()
+
+ // Keyguard is showing and not occluded
+ repository.setKeyguardOccluded(false)
+ assertThat(secureCameraActive()).isFalse()
+ }
+
+ @Test
+ fun testBouncerShowingResetsSecureCameraState() =
+ testScope.runTest {
+ val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
+ runCurrent()
+
+ commandQueue.doForEachCallback {
+ it.onCameraLaunchGestureDetected(
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
+ )
+ }
+ assertThat(secureCameraActive()).isTrue()
+
+ // Keyguard is showing and not occluded
+ repository.setKeyguardShowing(true)
+ repository.setKeyguardOccluded(true)
+ assertThat(secureCameraActive()).isTrue()
+
+ repository.setBouncerShowing(true)
+ assertThat(secureCameraActive()).isFalse()
+ }
+
+ @Test
+ fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest {
+ var isVisible = collectLastValue(underTest.isKeyguardVisible)
+ repository.setKeyguardShowing(true)
+ repository.setKeyguardOccluded(false)
+
+ assertThat(isVisible()).isTrue()
+
+ repository.setKeyguardOccluded(true)
+ assertThat(isVisible()).isFalse()
+
+ repository.setKeyguardShowing(false)
+ repository.setKeyguardOccluded(true)
+ assertThat(isVisible()).isFalse()
}
+
+ @Test
+ fun secureCameraIsNotActiveWhenNoCameraLaunchEventHasBeenFiredYet() =
+ testScope.runTest {
+ val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
+ runCurrent()
+
+ assertThat(secureCameraActive()).isFalse()
+ }
+}
+
+class FakeCommandQueue(val context: Context, val displayTracker: DisplayTracker) :
+ CommandQueue(context, displayTracker) {
+ private val callbacks = mutableListOf<Callbacks>()
+
+ override fun addCallback(callback: Callbacks) {
+ callbacks.add(callback)
+ }
+
+ override fun removeCallback(callback: Callbacks) {
+ callbacks.remove(callback)
+ }
+
+ fun doForEachCallback(func: (callback: Callbacks) -> Unit) {
+ callbacks.forEach { func(it) }
+ }
+
+ fun callbackCount(): Int = callbacks.size
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index 9d60b16..51988ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
@@ -39,7 +40,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.Mockito.never
@@ -48,7 +48,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardLongPressInteractorTest : SysuiTestCase() {
@Mock private lateinit var activityStarter: ActivityStarter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 43287b0..240af7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -286,12 +286,18 @@
dumpManager = mock(),
userHandle = UserHandle.SYSTEM,
)
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor =
KeyguardInteractor(
repository = FakeKeyguardRepository(),
- commandQueue = commandQueue
+ commandQueue = commandQueue,
+ featureFlags = featureFlags,
),
registry =
FakeKeyguardQuickAffordanceRegistry(
@@ -311,10 +317,7 @@
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
- },
+ featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index b75a15d..ec70857 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
@@ -60,7 +61,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
@@ -68,7 +68,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -150,12 +150,17 @@
featureFlags =
FakeFeatureFlags().apply {
set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
}
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor =
- KeyguardInteractor(repository = repository, commandQueue = commandQueue),
+ KeyguardInteractor(
+ repository = repository,
+ commandQueue = commandQueue,
+ featureFlags = featureFlags
+ ),
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 6333b24..3d13d80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -29,17 +30,17 @@
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyguardTransitionInteractorTest : SysuiTestCase() {
private lateinit var underTest: KeyguardTransitionInteractor
@@ -53,7 +54,7 @@
@Test
fun `transition collectors receives only appropriate events`() =
- runBlocking(IMMEDIATE) {
+ runTest(UnconfinedTestDispatcher()) {
var lockscreenToAodSteps = mutableListOf<TransitionStep>()
val job1 =
underTest.lockscreenToAodTransition
@@ -87,10 +88,9 @@
@Test
fun dozeAmountTransitionTest() =
- runBlocking(IMMEDIATE) {
+ runTest(UnconfinedTestDispatcher()) {
var dozeAmountSteps = mutableListOf<TransitionStep>()
- val job =
- underTest.dozeAmountTransition.onEach { dozeAmountSteps.add(it) }.launchIn(this)
+ val job = underTest.dozeAmountTransition.onEach { dozeAmountSteps.add(it) }.launchIn(this)
val steps = mutableListOf<TransitionStep>()
@@ -119,10 +119,9 @@
@Test
fun keyguardStateTests() =
- runBlocking(IMMEDIATE) {
+ runTest(UnconfinedTestDispatcher()) {
var finishedSteps = mutableListOf<KeyguardState>()
- val job =
- underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
+ val job = underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
val steps = mutableListOf<TransitionStep>()
@@ -143,12 +142,10 @@
@Test
fun finishedKeyguardTransitionStepTests() =
- runBlocking(IMMEDIATE) {
+ runTest(UnconfinedTestDispatcher()) {
var finishedSteps = mutableListOf<TransitionStep>()
val job =
- underTest.finishedKeyguardTransitionStep
- .onEach { finishedSteps.add(it) }
- .launchIn(this)
+ underTest.finishedKeyguardTransitionStep.onEach { finishedSteps.add(it) }.launchIn(this)
val steps = mutableListOf<TransitionStep>()
@@ -169,12 +166,10 @@
@Test
fun startedKeyguardTransitionStepTests() =
- runBlocking(IMMEDIATE) {
+ runTest(UnconfinedTestDispatcher()) {
var startedSteps = mutableListOf<TransitionStep>()
val job =
- underTest.startedKeyguardTransitionStep
- .onEach { startedSteps.add(it) }
- .launchIn(this)
+ underTest.startedKeyguardTransitionStep.onEach { startedSteps.add(it) }.launchIn(this)
val steps = mutableListOf<TransitionStep>()
@@ -192,8 +187,4 @@
job.cancel()
}
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 702f3763..46e4679 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -21,6 +21,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
@@ -92,10 +94,12 @@
transitionRepository = KeyguardTransitionRepositoryImpl()
runner = KeyguardTransitionRunner(transitionRepository)
+ val featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
shadeRepository = shadeRepository,
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
@@ -105,7 +109,8 @@
fromDreamingTransitionInteractor =
FromDreamingTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
@@ -114,7 +119,8 @@
fromAodTransitionInteractor =
FromAodTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
@@ -123,7 +129,8 @@
fromGoneTransitionInteractor =
FromGoneTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
@@ -132,7 +139,8 @@
fromDozingTransitionInteractor =
FromDozingTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
@@ -141,7 +149,8 @@
fromOccludedTransitionInteractor =
FromOccludedTransitionInteractor(
scope = testScope,
- keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue),
+ keyguardInteractor =
+ KeyguardInteractor(keyguardRepository, commandQueue, featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
@@ -149,7 +158,7 @@
}
@Test
- fun `DREAMING to LOCKSCREEN`() =
+ fun `DREAMING to LOCKSCREEN - dreaming state changes first`() =
testScope.runTest {
// GIVEN a device is dreaming and occluded
keyguardRepository.setDreamingWithOverlay(true)
@@ -179,9 +188,59 @@
)
// AND dreaming has stopped
keyguardRepository.setDreamingWithOverlay(false)
+ advanceUntilIdle()
+ // AND then occluded has stopped
+ keyguardRepository.setKeyguardOccluded(false)
+ advanceUntilIdle()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture())
+ }
+ // THEN a transition to BOUNCER should occur
+ assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+ assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `DREAMING to LOCKSCREEN - occluded state changes first`() =
+ testScope.runTest {
+ // GIVEN a device is dreaming and occluded
+ keyguardRepository.setDreamingWithOverlay(true)
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to DREAMING
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN doze is complete
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
// AND occluded has stopped
keyguardRepository.setKeyguardOccluded(false)
advanceUntilIdle()
+ // AND then dreaming has stopped
+ keyguardRepository.setDreamingWithOverlay(false)
+ advanceUntilIdle()
val info =
withArgCaptor<TransitionInfo> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 3166214..6236616 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -33,11 +34,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class LightRevealScrimInteractorTest : SysuiTestCase() {
private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
index fbfeca9..f86ac79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt
@@ -17,18 +17,18 @@
package com.android.systemui.keyguard.domain.interactor
import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class PrimaryBouncerCallbackInteractorTest : SysuiTestCase() {
private val mPrimaryBouncerCallbackInteractor = PrimaryBouncerCallbackInteractor()
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index ea7bc91..75b74b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.os.Looper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -35,12 +36,11 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
private lateinit var repository: FakeKeyguardBouncerRepository
@Mock private lateinit var bouncerView: BouncerView
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 06e397d..706154e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -33,10 +34,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: DreamingToLockscreenTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 14c3b50..b15ce10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -32,10 +33,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class GoneToDreamingTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: GoneToDreamingTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 4b04b7b..03a347e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -125,9 +125,18 @@
),
)
repository = FakeKeyguardRepository()
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
val keyguardInteractor =
- KeyguardInteractor(repository = repository, commandQueue = commandQueue)
+ KeyguardInteractor(
+ repository = repository,
+ commandQueue = commandQueue,
+ featureFlags = featureFlags,
+ )
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
@@ -191,10 +200,7 @@
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
- },
+ featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 3727134..586af62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -16,28 +16,29 @@
package com.android.systemui.keyguard.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyguardBouncerViewModelTest : SysuiTestCase() {
lateinit var underTest: KeyguardBouncerViewModel
@Mock lateinit var bouncerView: BouncerView
@@ -51,7 +52,7 @@
@Test
fun setMessage() =
- runBlocking(Dispatchers.Main.immediate) {
+ runTest {
val flow = MutableStateFlow<BouncerShowMessageModel?>(null)
var message: BouncerShowMessageModel? = null
Mockito.`when`(bouncerInteractor.showMessage)
@@ -62,6 +63,8 @@
flow.value = BouncerShowMessageModel(message = "abc", colorStateList = null)
val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
+ // Run the tasks that are pending at this point of virtual time.
+ runCurrent()
assertThat(message?.message).isEqualTo("abc")
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index ed31dc3..d94c108 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -32,10 +33,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: LockscreenToDreamingTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 458b315..12ec24d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -32,10 +33,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: LockscreenToOccludedTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index a36214e..0c4e845 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -32,10 +33,9 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: OccludedToLockscreenTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
index 3b5e6b9..d1744c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/LogDiffsForTableTest.kt
@@ -276,6 +276,52 @@
}
@Test
+ fun intNullable_logsNull() =
+ testScope.runTest {
+ systemClock.setCurrentTimeMillis(100L)
+ val flow = flow {
+ for (int in listOf(null, 6, null, 8)) {
+ systemClock.advanceTime(100L)
+ emit(int)
+ }
+ }
+
+ val flowWithLogging =
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = 1234,
+ )
+
+ val job = launch { flowWithLogging.collect() }
+
+ val logs = dumpLog()
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + FULL_NAME + SEPARATOR + "1234"
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + FULL_NAME + SEPARATOR + "null"
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(300L) + SEPARATOR + FULL_NAME + SEPARATOR + "6"
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(400L) + SEPARATOR + FULL_NAME + SEPARATOR + "null"
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(500L) + SEPARATOR + FULL_NAME + SEPARATOR + "8"
+ )
+
+ job.cancel()
+ }
+
+ @Test
fun int_logsUpdates() =
testScope.runTest {
systemClock.setCurrentTimeMillis(100L)
@@ -1030,6 +1076,246 @@
job.cancel()
}
+ // ---- Flow<List<T>> tests ----
+
+ @Test
+ fun list_doesNotLogWhenNotCollected() {
+ val flow = flowOf(listOf(5), listOf(6), listOf(7))
+
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = listOf(1234),
+ )
+
+ val logs = dumpLog()
+ assertThat(logs).doesNotContain(COLUMN_PREFIX)
+ assertThat(logs).doesNotContain(COLUMN_NAME)
+ assertThat(logs).doesNotContain("1234")
+ }
+
+ @Test
+ fun list_logsInitialWhenCollected() =
+ testScope.runTest {
+ val flow = flowOf(listOf(5), listOf(6), listOf(7))
+
+ val flowWithLogging =
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = listOf(1234),
+ )
+
+ systemClock.setCurrentTimeMillis(3000L)
+ val job = launch { flowWithLogging.collect() }
+
+ val logs = dumpLog()
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(3000L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf(1234).toString()
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun list_logsUpdates() =
+ testScope.runTest {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val listItems =
+ listOf(listOf("val1", "val2"), listOf("val3"), listOf("val4", "val5", "val6"))
+ val flow = flow {
+ for (list in listItems) {
+ systemClock.advanceTime(100L)
+ emit(list)
+ }
+ }
+
+ val flowWithLogging =
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = listOf("val0", "val00"),
+ )
+
+ val job = launch { flowWithLogging.collect() }
+
+ val logs = dumpLog()
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val0", "val00").toString()
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(200L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val1", "val2").toString()
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(300L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val3").toString()
+ )
+ assertThat(logs)
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(400L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val4", "val5", "val6").toString()
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun list_doesNotLogIfSameValue() =
+ testScope.runTest {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val listItems =
+ listOf(
+ listOf("val0", "val00"),
+ listOf("val1"),
+ listOf("val1"),
+ listOf("val1", "val2"),
+ )
+ val flow = flow {
+ for (bool in listItems) {
+ systemClock.advanceTime(100L)
+ emit(bool)
+ }
+ }
+
+ val flowWithLogging =
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = listOf("val0", "val00"),
+ )
+
+ val job = launch { flowWithLogging.collect() }
+
+ val logs = dumpLog()
+
+ val expected1 =
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val0", "val00").toString()
+ val expected3 =
+ TABLE_LOG_DATE_FORMAT.format(300L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val1").toString()
+ val expected5 =
+ TABLE_LOG_DATE_FORMAT.format(500L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val1", "val2").toString()
+ assertThat(logs).contains(expected1)
+ assertThat(logs).contains(expected3)
+ assertThat(logs).contains(expected5)
+
+ val unexpected2 =
+ TABLE_LOG_DATE_FORMAT.format(200L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val0", "val00")
+ val unexpected4 =
+ TABLE_LOG_DATE_FORMAT.format(400L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf("val1")
+ assertThat(logs).doesNotContain(unexpected2)
+ assertThat(logs).doesNotContain(unexpected4)
+ job.cancel()
+ }
+
+ @Test
+ fun list_worksForStateFlows() =
+ testScope.runTest {
+ val flow = MutableStateFlow(listOf(1111))
+
+ val flowWithLogging =
+ flow.logDiffsForTable(
+ tableLogBuffer,
+ COLUMN_PREFIX,
+ COLUMN_NAME,
+ initialValue = listOf(1111),
+ )
+
+ systemClock.setCurrentTimeMillis(50L)
+ val job = launch { flowWithLogging.collect() }
+ assertThat(dumpLog())
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(50L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf(1111).toString()
+ )
+
+ systemClock.setCurrentTimeMillis(100L)
+ flow.emit(listOf(2222, 3333))
+ assertThat(dumpLog())
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf(2222, 3333).toString()
+ )
+
+ systemClock.setCurrentTimeMillis(200L)
+ flow.emit(listOf(3333, 4444))
+ assertThat(dumpLog())
+ .contains(
+ TABLE_LOG_DATE_FORMAT.format(200L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf(3333, 4444).toString()
+ )
+
+ // Doesn't log duplicates
+ systemClock.setCurrentTimeMillis(300L)
+ flow.emit(listOf(3333, 4444))
+ assertThat(dumpLog())
+ .doesNotContain(
+ TABLE_LOG_DATE_FORMAT.format(300L) +
+ SEPARATOR +
+ FULL_NAME +
+ SEPARATOR +
+ listOf(3333, 4444).toString()
+ )
+
+ job.cancel()
+ }
+
private fun dumpLog(): String {
val outputWriter = StringWriter()
tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
index 432764a..c7f3fa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
@@ -36,6 +36,17 @@
}
@Test
+ fun setString_null() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(null as String?)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("null")
+ }
+
+ @Test
fun setBoolean_isBoolean() {
val underTest = TableChange()
@@ -58,6 +69,17 @@
}
@Test
+ fun setInt_null() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(null as Int?)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("null")
+ }
+
+ @Test
fun setThenReset_isEmpty() {
val underTest = TableChange()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 44e2fbd..9f12329 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -40,6 +40,7 @@
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -51,6 +52,7 @@
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.resume.MediaResumeListener
+import com.android.systemui.media.controls.resume.ResumeMediaBrowser
import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
@@ -125,6 +127,7 @@
@Mock lateinit var pendingIntent: PendingIntent
@Mock lateinit var activityStarter: ActivityStarter
@Mock lateinit var smartspaceManager: SmartspaceManager
+ @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -186,6 +189,7 @@
mediaFlags = mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
+ keyguardUpdateMonitor = keyguardUpdateMonitor
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -241,6 +245,7 @@
whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(false)
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
whenever(logger.getNewInstanceId()).thenReturn(instanceIdSequence.newInstanceId())
+ whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(false)
}
@After
@@ -642,6 +647,59 @@
}
@Test
+ fun testOnNotificationRemoved_withResumption_tooManyPlayers() {
+ // Given the maximum number of resume controls already
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ for (i in 0..ResumeMediaBrowser.MAX_RESUMPTION_CONTROLS) {
+ addResumeControlAndLoad(desc, "$i:$PACKAGE_NAME")
+ clock.advanceTime(1000)
+ }
+
+ // And an active, resumable notification
+ whenever(controller.metadata).thenReturn(metadataBuilder.build())
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
+ assertThat(data.resumption).isFalse()
+ mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+
+ // When the notification is removed
+ mediaDataManager.onNotificationRemoved(KEY)
+
+ // Then it is converted to resumption
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.resumption).isTrue()
+ assertThat(mediaDataCaptor.value.isPlaying).isFalse()
+
+ // And the oldest resume control was removed
+ verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"))
+ }
+
+ fun testOnNotificationRemoved_lockDownMode() {
+ whenever(keyguardUpdateMonitor.isUserInLockdown(any())).thenReturn(true)
+
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
+ mediaDataManager.onNotificationRemoved(KEY)
+
+ verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ verify(logger, never())
+ .logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+ }
+
+ @Test
fun testAddResumptionControls() {
// WHEN resumption controls are added
val desc =
@@ -769,6 +827,24 @@
}
@Test
+ fun testAddResumptionControls_hasNoExtras() {
+ whenever(mediaFlags.isResumeProgressEnabled()).thenReturn(true)
+
+ // WHEN resumption controls are added that do not have any extras
+ val desc =
+ MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ addResumeControlAndLoad(desc)
+
+ // Resume progress is null
+ val data = mediaDataCaptor.value
+ assertThat(data.resumption).isTrue()
+ assertThat(data.resumeProgress).isEqualTo(null)
+ }
+
+ @Test
fun testResumptionDisabled_dismissesResumeControls() {
// WHEN there are resume controls and resumption is switched off
val desc =
@@ -1846,7 +1922,10 @@
}
/** Helper function to add a resumption control and capture the resulting MediaData */
- private fun addResumeControlAndLoad(desc: MediaDescription) {
+ private fun addResumeControlAndLoad(
+ desc: MediaDescription,
+ packageName: String = PACKAGE_NAME
+ ) {
mediaDataManager.addResumptionControls(
USER_ID,
desc,
@@ -1854,14 +1933,14 @@
session.sessionToken,
APP_NAME,
pendingIntent,
- PACKAGE_NAME
+ packageName
)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
verify(listener)
.onMediaDataLoaded(
- eq(PACKAGE_NAME),
+ eq(packageName),
eq(null),
capture(mediaDataCaptor),
eq(true),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index e201b6b..997198e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -21,12 +21,20 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.util.MathUtils.abs
+import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
@@ -49,6 +57,9 @@
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
@@ -90,11 +101,15 @@
@Mock lateinit var mediaCarousel: MediaScrollView
@Mock lateinit var pageIndicator: PageIndicator
@Mock lateinit var mediaFlags: MediaFlags
+ @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
@Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
@Captor
lateinit var configListener: ArgumentCaptor<ConfigurationController.ConfigurationListener>
@Captor lateinit var newConfig: ArgumentCaptor<Configuration>
@Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
+ @Captor lateinit var keyguardCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
private val clock = FakeSystemClock()
private lateinit var mediaCarouselController: MediaCarouselController
@@ -102,6 +117,7 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ transitionRepository = FakeKeyguardTransitionRepository()
mediaCarouselController =
MediaCarouselController(
context,
@@ -119,11 +135,14 @@
logger,
debugLogger,
mediaFlags,
+ keyguardUpdateMonitor,
+ KeyguardTransitionInteractor(repository = transitionRepository),
)
verify(configurationController).addCallback(capture(configListener))
verify(mediaDataManager).addListener(capture(listener))
verify(visualStabilityProvider)
.addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCallback))
whenever(mediaControlPanelFactory.get()).thenReturn(panel)
whenever(panel.mediaViewController).thenReturn(mediaViewController)
whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
@@ -740,4 +759,41 @@
assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(0).isSsMediaRec)
assertFalse(MediaPlayerData.visiblePlayerKeys().elementAt(0).data.active)
}
+
+ @Test
+ fun testOnLockDownMode_hideMediaCarousel() {
+ whenever(keyguardUpdateMonitor.isUserInLockdown(context.userId)).thenReturn(true)
+ mediaCarouselController.mediaCarousel = mediaCarousel
+
+ keyguardCallback.value.onStrongAuthStateChanged(context.userId)
+
+ verify(mediaCarousel).visibility = View.GONE
+ }
+
+ @Test
+ fun testLockDownModeOff_showMediaCarousel() {
+ whenever(keyguardUpdateMonitor.isUserInLockdown(context.userId)).thenReturn(false)
+ whenever(keyguardUpdateMonitor.isUserUnlocked(context.userId)).thenReturn(true)
+ mediaCarouselController.mediaCarousel = mediaCarousel
+
+ keyguardCallback.value.onStrongAuthStateChanged(context.userId)
+
+ verify(mediaCarousel).visibility = View.VISIBLE
+ }
+
+ @ExperimentalCoroutinesApi
+ @Test
+ fun testKeyguardGone_showMediaCarousel() =
+ runTest(UnconfinedTestDispatcher()) {
+ mediaCarouselController.mediaCarousel = mediaCarousel
+
+ val job = mediaCarouselController.listenForAnyStateToGoneKeyguardTransition(this)
+ transitionRepository.sendTransitionStep(
+ TransitionStep(to = KeyguardState.GONE, transitionState = TransitionState.FINISHED)
+ )
+
+ verify(mediaCarousel).visibility = View.VISIBLE
+
+ job.cancel()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index 2f7eac2..af91cdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -33,6 +33,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.floatThat
import org.mockito.Mock
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -139,14 +140,12 @@
whenever(controlWidgetState.y).thenReturn(150F)
whenever(controlWidgetState.height).thenReturn(20)
// in current beizer, when the progress reach 0.38, the result will be 0.5
- mediaViewController.squishViewState(mockViewState, 119F / 200F)
- verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
- mediaViewController.squishViewState(mockViewState, 150F / 200F)
- verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
mediaViewController.squishViewState(mockViewState, 181.4F / 200F)
verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 0.5F) < delta }
+ verify(detailWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
mediaViewController.squishViewState(mockViewState, 200F / 200F)
verify(controlWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
+ verify(detailWidgetState, times(2)).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 8055b98..4fc9ca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -65,7 +65,13 @@
@Test
fun getIconInfoFromPackageName_nullPackageName_returnsDefault() {
- val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null) {}
+ val iconInfo =
+ MediaTttUtils.getIconInfoFromPackageName(
+ context,
+ appPackageName = null,
+ isReceiver = false,
+ ) {
+ }
assertThat(iconInfo.isAppIcon).isFalse()
assertThat(iconInfo.contentDescription.loadContentDescription(context))
@@ -74,10 +80,32 @@
}
@Test
+ fun getIconInfoFromPackageName_nullPackageName_isReceiver_returnsDefault() {
+ val iconInfo =
+ MediaTttUtils.getIconInfoFromPackageName(
+ context,
+ appPackageName = null,
+ isReceiver = true,
+ ) {
+ }
+
+ assertThat(iconInfo.isAppIcon).isFalse()
+ assertThat(iconInfo.contentDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(R.string.media_transfer_receiver_content_description_unknown_app)
+ )
+ assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast))
+ }
+
+ @Test
fun getIconInfoFromPackageName_nullPackageName_exceptionFnNotTriggered() {
var exceptionTriggered = false
- MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null) {
+ MediaTttUtils.getIconInfoFromPackageName(
+ context,
+ appPackageName = null,
+ isReceiver = false,
+ ) {
exceptionTriggered = true
}
@@ -86,7 +114,13 @@
@Test
fun getIconInfoFromPackageName_invalidPackageName_returnsDefault() {
- val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName") {}
+ val iconInfo =
+ MediaTttUtils.getIconInfoFromPackageName(
+ context,
+ appPackageName = "fakePackageName",
+ isReceiver = false,
+ ) {
+ }
assertThat(iconInfo.isAppIcon).isFalse()
assertThat(iconInfo.contentDescription.loadContentDescription(context))
@@ -95,19 +129,58 @@
}
@Test
+ fun getIconInfoFromPackageName_invalidPackageName_isReceiver_returnsDefault() {
+ val iconInfo =
+ MediaTttUtils.getIconInfoFromPackageName(
+ context,
+ appPackageName = "fakePackageName",
+ isReceiver = true,
+ ) {
+ }
+
+ assertThat(iconInfo.isAppIcon).isFalse()
+ assertThat(iconInfo.contentDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(R.string.media_transfer_receiver_content_description_unknown_app)
+ )
+ assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast))
+ }
+
+ @Test
fun getIconInfoFromPackageName_invalidPackageName_exceptionFnTriggered() {
var exceptionTriggered = false
- MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = "fakePackageName") {
- exceptionTriggered = true
- }
+ MediaTttUtils.getIconInfoFromPackageName(
+ context,
+ appPackageName = "fakePackageName",
+ isReceiver = false
+ ) { exceptionTriggered = true }
+
+ assertThat(exceptionTriggered).isTrue()
+ }
+
+ @Test
+ fun getIconInfoFromPackageName_invalidPackageName_isReceiver_exceptionFnTriggered() {
+ var exceptionTriggered = false
+
+ MediaTttUtils.getIconInfoFromPackageName(
+ context,
+ appPackageName = "fakePackageName",
+ isReceiver = true
+ ) { exceptionTriggered = true }
assertThat(exceptionTriggered).isTrue()
}
@Test
fun getIconInfoFromPackageName_validPackageName_returnsAppInfo() {
- val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME) {}
+ val iconInfo =
+ MediaTttUtils.getIconInfoFromPackageName(
+ context,
+ PACKAGE_NAME,
+ isReceiver = false,
+ ) {
+ }
assertThat(iconInfo.isAppIcon).isTrue()
assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Loaded(appIconFromPackageName))
@@ -115,10 +188,42 @@
}
@Test
+ fun getIconInfoFromPackageName_validPackageName_isReceiver_returnsAppInfo() {
+ val iconInfo =
+ MediaTttUtils.getIconInfoFromPackageName(
+ context,
+ PACKAGE_NAME,
+ isReceiver = true,
+ ) {
+ }
+
+ assertThat(iconInfo.isAppIcon).isTrue()
+ assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Loaded(appIconFromPackageName))
+ assertThat(iconInfo.contentDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(
+ R.string.media_transfer_receiver_content_description_with_app_name,
+ APP_NAME
+ )
+ )
+ }
+
+ @Test
fun getIconInfoFromPackageName_validPackageName_exceptionFnNotTriggered() {
var exceptionTriggered = false
- MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME) {
+ MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, isReceiver = false) {
+ exceptionTriggered = true
+ }
+
+ assertThat(exceptionTriggered).isFalse()
+ }
+
+ @Test
+ fun getIconInfoFromPackageName_validPackageName_isReceiver_exceptionFnNotTriggered() {
+ var exceptionTriggered = false
+
+ MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, isReceiver = true) {
exceptionTriggered = true
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index dba2da7..19dd2f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -354,7 +354,11 @@
val view = getChipView()
assertThat(view.getAppIconView().drawable).isEqualTo(fakeAppIconDrawable)
- assertThat(view.getAppIconView().contentDescription).isEqualTo(APP_NAME)
+ assertThat(view.getAppIconView().contentDescription)
+ .isEqualTo(context.getString(
+ R.string.media_transfer_receiver_content_description_with_app_name,
+ APP_NAME,
+ ))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 1042ea7..4977775 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -24,6 +24,8 @@
private val taskListProvider = TestRecentTaskListProvider()
private val scope = CoroutineScope(Dispatchers.Unconfined)
private val appSelectorComponentName = ComponentName("com.test", "AppSelector")
+ private val callerPackageName = "com.test.caller"
+ private val callerComponentName = ComponentName(callerPackageName, "Caller")
private val hostUserHandle = UserHandle.of(123)
private val otherUserHandle = UserHandle.of(456)
@@ -31,14 +33,16 @@
private val view: MediaProjectionAppSelectorView = mock()
private val featureFlags: FeatureFlags = mock()
- private val controller = MediaProjectionAppSelectorController(
- taskListProvider,
- view,
- featureFlags,
- hostUserHandle,
- scope,
- appSelectorComponentName
- )
+ private val controller =
+ MediaProjectionAppSelectorController(
+ taskListProvider,
+ view,
+ featureFlags,
+ hostUserHandle,
+ scope,
+ appSelectorComponentName,
+ callerPackageName
+ )
@Test
fun initNoRecentTasks_bindsEmptyList() {
@@ -51,104 +55,87 @@
@Test
fun initOneRecentTask_bindsList() {
- taskListProvider.tasks = listOf(
- createRecentTask(taskId = 1)
- )
+ taskListProvider.tasks = listOf(createRecentTask(taskId = 1))
controller.init()
- verify(view).bind(
- listOf(
- createRecentTask(taskId = 1)
- )
- )
+ verify(view).bind(listOf(createRecentTask(taskId = 1)))
}
@Test
fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() {
- val tasks = listOf(
- createRecentTask(taskId = 1),
- createRecentTask(taskId = 2),
- createRecentTask(taskId = 3),
- )
- taskListProvider.tasks = tasks
-
- controller.init()
-
- verify(view).bind(
+ val tasks =
listOf(
createRecentTask(taskId = 1),
createRecentTask(taskId = 2),
createRecentTask(taskId = 3),
)
- )
- }
-
- @Test
- fun initRecentTasksWithAppSelectorTasks_bindsAppSelectorTasksAtTheEnd() {
- val tasks = listOf(
- createRecentTask(taskId = 1),
- createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
- createRecentTask(taskId = 3),
- createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
- createRecentTask(taskId = 5),
- )
taskListProvider.tasks = tasks
controller.init()
- verify(view).bind(
+ verify(view)
+ .bind(
+ listOf(
+ createRecentTask(taskId = 1),
+ createRecentTask(taskId = 2),
+ createRecentTask(taskId = 3),
+ )
+ )
+ }
+
+ @Test
+ fun initRecentTasksWithAppSelectorTasks_removeAppSelector() {
+ val tasks =
listOf(
createRecentTask(taskId = 1),
- createRecentTask(taskId = 3),
- createRecentTask(taskId = 5),
createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
- createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
+ createRecentTask(taskId = 3),
+ createRecentTask(taskId = 4),
)
- )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ verify(view)
+ .bind(
+ listOf(
+ createRecentTask(taskId = 1),
+ createRecentTask(taskId = 3),
+ createRecentTask(taskId = 4),
+ )
+ )
+ }
+
+ @Test
+ fun initRecentTasksWithAppSelectorTasks_bindsCallerTasksAtTheEnd() {
+ val tasks =
+ listOf(
+ createRecentTask(taskId = 1),
+ createRecentTask(taskId = 2, topActivityComponent = callerComponentName),
+ createRecentTask(taskId = 3),
+ createRecentTask(taskId = 4),
+ )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ verify(view)
+ .bind(
+ listOf(
+ createRecentTask(taskId = 1),
+ createRecentTask(taskId = 3),
+ createRecentTask(taskId = 4),
+ createRecentTask(taskId = 2, topActivityComponent = callerComponentName),
+ )
+ )
}
@Test
fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesDisabled_bindsOnlyTasksWithHostProfile() {
givenEnterprisePoliciesFeatureFlag(enabled = false)
- val tasks = listOf(
- createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
- createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
- createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
- )
- taskListProvider.tasks = tasks
-
- controller.init()
-
- verify(view).bind(
- listOf(
- createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
- )
- )
- }
-
- @Test
- fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesEnabled_bindsAllTasks() {
- givenEnterprisePoliciesFeatureFlag(enabled = true)
-
- val tasks = listOf(
- createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
- createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
- createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
- createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
- )
- taskListProvider.tasks = tasks
-
- controller.init()
-
- // TODO(b/233348916) should filter depending on the policies
- verify(view).bind(
+ val tasks =
listOf(
createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
@@ -156,7 +143,47 @@
createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
)
- )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ verify(view)
+ .bind(
+ listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ )
+ }
+
+ @Test
+ fun initRecentTasksWithAppSelectorTasks_enterprisePoliciesEnabled_bindsAllTasks() {
+ givenEnterprisePoliciesFeatureFlag(enabled = true)
+
+ val tasks =
+ listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ taskListProvider.tasks = tasks
+
+ controller.init()
+
+ // TODO(b/233348916) should filter depending on the policies
+ verify(view)
+ .bind(
+ listOf(
+ createRecentTask(taskId = 1, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 2, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 3, userId = hostUserHandle.identifier),
+ createRecentTask(taskId = 4, userId = otherUserHandle.identifier),
+ createRecentTask(taskId = 5, userId = hostUserHandle.identifier),
+ )
+ )
}
private fun givenEnterprisePoliciesFeatureFlag(enabled: Boolean) {
@@ -183,6 +210,5 @@
var tasks: List<RecentTask> = emptyList()
override suspend fun loadRecentTasks(): List<RecentTask> = tasks
-
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
new file mode 100644
index 0000000..bc31a0e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar.gestural
+
+import android.os.Handler
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import android.view.ViewConfiguration
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.NavigationEdgeBackPlugin
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class BackPanelControllerTest : SysuiTestCase() {
+ companion object {
+ private const val START_X: Float = 0f
+ }
+ private lateinit var mBackPanelController: BackPanelController
+ private lateinit var testableLooper: TestableLooper
+ private var triggerThreshold: Float = 0.0f
+ private val touchSlop = ViewConfiguration.get(context).scaledEdgeSlop
+ @Mock private lateinit var vibratorHelper: VibratorHelper
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var latencyTracker: LatencyTracker
+ @Mock private lateinit var layoutParams: WindowManager.LayoutParams
+ @Mock private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mBackPanelController =
+ BackPanelController(
+ context,
+ windowManager,
+ ViewConfiguration.get(context),
+ Handler.createAsync(Looper.myLooper()),
+ vibratorHelper,
+ configurationController,
+ latencyTracker
+ )
+ mBackPanelController.setLayoutParams(layoutParams)
+ mBackPanelController.setBackCallback(backCallback)
+ mBackPanelController.setIsLeftPanel(true)
+ testableLooper = TestableLooper.get(this)
+ triggerThreshold = mBackPanelController.params.staticTriggerThreshold
+ }
+
+ @Test
+ fun handlesActionDown() {
+ startTouch()
+
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.GONE)
+ }
+
+ @Test
+ fun staysHiddenBeforeSlopCrossed() {
+ startTouch()
+ // Move just enough to not cross the touch slop
+ continueTouch(START_X + touchSlop - 1)
+
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.GONE)
+ }
+
+ @Test
+ fun handlesBackCommitted() {
+ startTouch()
+ // Move once to cross the touch slop
+ continueTouch(START_X + touchSlop.toFloat() + 1)
+ // Move again to cross the back trigger threshold
+ continueTouch(START_X + touchSlop + triggerThreshold + 1)
+
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.ACTIVE)
+ verify(backCallback).setTriggerBack(true)
+ testableLooper.moveTimeForward(100)
+ testableLooper.processAllMessages()
+ verify(vibratorHelper).vibrate(VIBRATE_ACTIVATED_EFFECT)
+
+ finishTouchActionUp(START_X + touchSlop + triggerThreshold + 1)
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.FLUNG)
+ verify(backCallback).triggerBack()
+ }
+
+ @Test
+ fun handlesBackCancelled() {
+ startTouch()
+ continueTouch(START_X + touchSlop.toFloat() + 1)
+ continueTouch(
+ START_X + touchSlop + triggerThreshold -
+ mBackPanelController.params.deactivationSwipeTriggerThreshold
+ )
+ clearInvocations(backCallback)
+ Thread.sleep(MIN_DURATION_ACTIVE_ANIMATION)
+ // Move in the opposite direction to cross the deactivation threshold and cancel back
+ continueTouch(START_X)
+
+ assertThat(mBackPanelController.currentState)
+ .isEqualTo(BackPanelController.GestureState.INACTIVE)
+ verify(backCallback).setTriggerBack(false)
+ verify(vibratorHelper).vibrate(VIBRATE_DEACTIVATED_EFFECT)
+
+ finishTouchActionUp(START_X)
+ verify(backCallback).cancelBack()
+ }
+
+ private fun startTouch() {
+ mBackPanelController.onMotionEvent(createMotionEvent(ACTION_DOWN, START_X, 0f))
+ }
+
+ private fun continueTouch(x: Float) {
+ mBackPanelController.onMotionEvent(createMotionEvent(ACTION_MOVE, x, 0f))
+ }
+
+ private fun finishTouchActionUp(x: Float) {
+ mBackPanelController.onMotionEvent(createMotionEvent(ACTION_UP, x, 0f))
+ }
+
+ private fun createMotionEvent(action: Int, x: Float, y: Float): MotionEvent {
+ return MotionEvent.obtain(0L, 0L, action, x, y, 0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 8440455..39c4e06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -23,10 +23,14 @@
import android.os.UserManager
import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
-import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
+import com.android.systemui.notetask.NoteTaskController.Companion.INTENT_EXTRA_USE_STYLUS_MODE
+import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
+import com.android.systemui.notetask.NoteTaskInfoResolver.NoteTaskInfo
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.bubbles.Bubbles
@@ -36,8 +40,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
/**
@@ -50,24 +54,23 @@
@RunWith(AndroidJUnit4::class)
internal class NoteTaskControllerTest : SysuiTestCase() {
- private val notesIntent = Intent(ACTION_CREATE_NOTE)
-
@Mock lateinit var context: Context
@Mock lateinit var packageManager: PackageManager
- @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
+ @Mock lateinit var resolver: NoteTaskInfoResolver
@Mock lateinit var bubbles: Bubbles
@Mock lateinit var optionalBubbles: Optional<Bubbles>
@Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
@Mock lateinit var optionalUserManager: Optional<UserManager>
@Mock lateinit var userManager: UserManager
+ @Mock lateinit var uiEventLogger: UiEventLogger
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(context.packageManager).thenReturn(packageManager)
- whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
+ whenever(resolver.resolveInfo()).thenReturn(NoteTaskInfo(NOTES_PACKAGE_NAME, NOTES_UID))
whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
@@ -77,101 +80,182 @@
private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
return NoteTaskController(
context = context,
- intentResolver = noteTaskIntentResolver,
+ resolver = resolver,
optionalBubbles = optionalBubbles,
optionalKeyguardManager = optionalKeyguardManager,
optionalUserManager = optionalUserManager,
isEnabled = isEnabled,
+ uiEventLogger = uiEventLogger,
)
}
// region showNoteTask
@Test
- fun showNoteTask_keyguardIsLocked_shouldStartActivity() {
+ fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
- createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = false,
+ uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE,
+ )
- verify(context).startActivity(notesIntent)
- verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(context).startActivity(capture(intentCaptor))
+ intentCaptor.value.let { intent ->
+ assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+ assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+ assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+ }
+ verifyZeroInteractions(bubbles)
+ verify(uiEventLogger)
+ .log(
+ ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE,
+ NOTES_UID,
+ NOTES_PACKAGE_NAME
+ )
}
@Test
- fun showNoteTask_keyguardIsUnlocked_shouldStartBubbles() {
+ fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesAndLogUiEvent() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
- createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = false,
+ uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+ )
- verify(bubbles).showOrHideAppBubble(notesIntent)
- verify(context, never()).startActivity(notesIntent)
+ verifyZeroInteractions(context)
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+ intentCaptor.value.let { intent ->
+ assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+ assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+ assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+ }
+ verify(uiEventLogger)
+ .log(
+ ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+ NOTES_UID,
+ NOTES_PACKAGE_NAME
+ )
}
@Test
- fun showNoteTask_isInMultiWindowMode_shouldStartActivity() {
+ fun showNoteTask_keyguardIsUnlocked_uiEventIsNull_shouldStartBubblesWithoutLoggingUiEvent() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
- createNoteTaskController().showNoteTask(isInMultiWindowMode = true)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false, uiEvent = null)
- verify(context).startActivity(notesIntent)
- verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+ verifyZeroInteractions(context)
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
+ intentCaptor.value.let { intent ->
+ assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+ assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+ assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+ }
+ verifyZeroInteractions(uiEventLogger)
+ }
+
+ @Test
+ fun showNoteTask_isInMultiWindowMode_shouldStartActivityAndLogUiEvent() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = true,
+ uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT,
+ )
+
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(context).startActivity(capture(intentCaptor))
+ intentCaptor.value.let { intent ->
+ assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+ assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
+ assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.getBooleanExtra(INTENT_EXTRA_USE_STYLUS_MODE, false)).isTrue()
+ }
+ verifyZeroInteractions(bubbles)
+ verify(uiEventLogger)
+ .log(ShowNoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT, NOTES_UID, NOTES_PACKAGE_NAME)
}
@Test
fun showNoteTask_bubblesIsNull_shouldDoNothing() {
whenever(optionalBubbles.orElse(null)).thenReturn(null)
- createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = false,
+ uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+ )
- verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+ verifyZeroInteractions(context, bubbles, uiEventLogger)
}
@Test
fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
- createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = false,
+ uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+ )
- verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+ verifyZeroInteractions(context, bubbles, uiEventLogger)
}
@Test
fun showNoteTask_userManagerIsNull_shouldDoNothing() {
whenever(optionalUserManager.orElse(null)).thenReturn(null)
- createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = false,
+ uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+ )
- verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+ verifyZeroInteractions(context, bubbles, uiEventLogger)
}
@Test
fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
- whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
+ whenever(resolver.resolveInfo()).thenReturn(null)
- createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = false,
+ uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+ )
- verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+ verifyZeroInteractions(context, bubbles, uiEventLogger)
}
@Test
fun showNoteTask_flagDisabled_shouldDoNothing() {
- createNoteTaskController(isEnabled = false).showNoteTask()
+ createNoteTaskController(isEnabled = false)
+ .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
- verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+ verifyZeroInteractions(context, bubbles, uiEventLogger)
}
@Test
fun showNoteTask_userIsLocked_shouldDoNothing() {
whenever(userManager.isUserUnlocked).thenReturn(false)
- createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
+ createNoteTaskController()
+ .showNoteTask(
+ isInMultiWindowMode = false,
+ uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON,
+ )
- verify(context, never()).startActivity(notesIntent)
- verify(bubbles, never()).showOrHideAppBubble(notesIntent)
+ verifyZeroInteractions(context, bubbles, uiEventLogger)
}
// endregion
@@ -206,4 +290,9 @@
assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
}
// endregion
+
+ private companion object {
+ const val NOTES_PACKAGE_NAME = "com.android.note.app"
+ const val NOTES_UID = 123456
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
new file mode 100644
index 0000000..d6495d8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.app.role.RoleManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskInfoResolver].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskInfoResolverTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskInfoResolverTest : SysuiTestCase() {
+
+ @Mock lateinit var packageManager: PackageManager
+ @Mock lateinit var roleManager: RoleManager
+
+ private lateinit var underTest: NoteTaskInfoResolver
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest = NoteTaskInfoResolver(context, roleManager, packageManager)
+ }
+
+ @Test
+ fun resolveInfo_shouldReturnInfo() {
+ val packageName = "com.android.note.app"
+ val uid = 123456
+ whenever(roleManager.getRoleHoldersAsUser(NoteTaskInfoResolver.ROLE_NOTES, context.user))
+ .then { listOf(packageName) }
+ whenever(
+ packageManager.getApplicationInfoAsUser(
+ eq(packageName),
+ any<PackageManager.ApplicationInfoFlags>(),
+ eq(context.user)
+ )
+ )
+ .thenReturn(ApplicationInfo().apply { this.uid = uid })
+
+ val actual = underTest.resolveInfo()
+
+ requireNotNull(actual) { "Note task info must not be null" }
+ assertThat(actual.packageName).isEqualTo(packageName)
+ assertThat(actual.uid).isEqualTo(uid)
+ }
+
+ @Test
+ fun resolveInfo_packageManagerThrowsException_shouldReturnInfoWithZeroUid() {
+ val packageName = "com.android.note.app"
+ whenever(roleManager.getRoleHoldersAsUser(NoteTaskInfoResolver.ROLE_NOTES, context.user))
+ .then { listOf(packageName) }
+ whenever(
+ packageManager.getApplicationInfoAsUser(
+ eq(packageName),
+ any<PackageManager.ApplicationInfoFlags>(),
+ eq(context.user)
+ )
+ )
+ .thenThrow(PackageManager.NameNotFoundException(packageName))
+
+ val actual = underTest.resolveInfo()
+
+ requireNotNull(actual) { "Note task info must not be null" }
+ assertThat(actual.packageName).isEqualTo(packageName)
+ assertThat(actual.uid).isEqualTo(0)
+ }
+
+ @Test
+ fun resolveInfo_noRoleHolderIsSet_shouldReturnNull() {
+ whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskInfoResolver.ROLE_NOTES), any()))
+ .then { listOf<String>() }
+
+ val actual = underTest.resolveInfo()
+
+ assertThat(actual).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 010ac5b..53720ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -15,11 +15,14 @@
*/
package com.android.systemui.notetask
+import android.app.KeyguardManager
import android.test.suitebuilder.annotation.SmallTest
import android.view.KeyEvent
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -30,6 +33,7 @@
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
/**
@@ -55,12 +59,16 @@
whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
}
- private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
+ private fun createNoteTaskInitializer(
+ isEnabled: Boolean = true,
+ optionalKeyguardManager: Optional<KeyguardManager> = Optional.empty(),
+ ): NoteTaskInitializer {
return NoteTaskInitializer(
optionalBubbles = optionalBubbles,
noteTaskController = noteTaskController,
commandQueue = commandQueue,
isEnabled = isEnabled,
+ optionalKeyguardManager = optionalKeyguardManager,
)
}
@@ -105,19 +113,44 @@
// region handleSystemKey
@Test
- fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
- createNoteTaskInitializer()
+ fun handleSystemKey_receiveValidSystemKey_keyguardNotLocked_shouldShowNoteTaskWithUnlocked() {
+ val keyguardManager =
+ mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(false) }
+ createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager))
.callbacks
.handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
- verify(noteTaskController).showNoteTask()
+ verify(noteTaskController)
+ .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
+ }
+
+ @Test
+ fun handleSystemKey_receiveValidSystemKey_keyguardLocked_shouldShowNoteTaskWithLocked() {
+ val keyguardManager =
+ mock<KeyguardManager>() { whenever(isKeyguardLocked).thenReturn(true) }
+ createNoteTaskInitializer(optionalKeyguardManager = Optional.of(keyguardManager))
+ .callbacks
+ .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
+
+ verify(noteTaskController)
+ .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED)
+ }
+
+ @Test
+ fun handleSystemKey_receiveValidSystemKey_nullKeyguardManager_shouldShowNoteTaskWithUnlocked() {
+ createNoteTaskInitializer(optionalKeyguardManager = Optional.empty())
+ .callbacks
+ .handleSystemKey(NoteTaskController.NOTE_TASK_KEY_EVENT)
+
+ verify(noteTaskController)
+ .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON)
}
@Test
fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
- verify(noteTaskController, never()).showNoteTask()
+ verifyZeroInteractions(noteTaskController)
}
// endregion
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
deleted file mode 100644
index 18be92b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.notetask
-
-import android.app.role.RoleManager
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.test.suitebuilder.annotation.SmallTest
-import androidx.test.runner.AndroidJUnit4
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.ACTION_CREATE_NOTE
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.MockitoAnnotations
-
-/**
- * Tests for [NoteTaskIntentResolver].
- *
- * Build/Install/Run:
- * - atest SystemUITests:NoteTaskIntentResolverTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-internal class NoteTaskIntentResolverTest : SysuiTestCase() {
-
- @Mock lateinit var packageManager: PackageManager
- @Mock lateinit var roleManager: RoleManager
-
- private lateinit var underTest: NoteTaskIntentResolver
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- underTest = NoteTaskIntentResolver(context, roleManager)
- }
-
- @Test
- fun resolveIntent_shouldReturnIntentInStylusMode() {
- val packageName = "com.android.note.app"
- whenever(roleManager.getRoleHoldersAsUser(NoteTaskIntentResolver.ROLE_NOTES, context.user))
- .then { listOf(packageName) }
-
- val actual = underTest.resolveIntent()
-
- requireNotNull(actual) { "Intent must not be null" }
- assertThat(actual.action).isEqualTo(ACTION_CREATE_NOTE)
- assertThat(actual.`package`).isEqualTo(packageName)
- val expectedExtra = actual.getExtra(NoteTaskIntentResolver.INTENT_EXTRA_USE_STYLUS_MODE)
- assertThat(expectedExtra).isEqualTo(true)
- val expectedFlag = actual.flags and Intent.FLAG_ACTIVITY_NEW_TASK
- assertThat(expectedFlag).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
-
- @Test
- fun resolveIntent_noRoleHolderIsSet_shouldReturnNull() {
- whenever(roleManager.getRoleHoldersAsUser(eq(NoteTaskIntentResolver.ROLE_NOTES), any()))
- .then { listOf<String>() }
-
- val actual = underTest.resolveIntent()
-
- assertThat(actual).isNull()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index a1d42a0..cdc683f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -27,7 +27,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.LockScreenState
import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.notetask.NoteTaskController.ShowNoteTaskUiEvent
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -53,7 +53,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(noteTaskController.showNoteTask()).then {}
}
private fun createUnderTest(isEnabled: Boolean) =
@@ -96,6 +95,7 @@
underTest.onTriggered(expandable = null)
- verify(noteTaskController).showNoteTask()
+ verify(noteTaskController)
+ .showNoteTask(uiEvent = ShowNoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java
new file mode 100644
index 0000000..2293fc5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.process.condition;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.process.ProcessWrapper;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class UserProcessConditionTest extends SysuiTestCase {
+ @Mock
+ UserTracker mUserTracker;
+
+ @Mock
+ ProcessWrapper mProcessWrapper;
+
+ @Mock
+ Monitor.Callback mCallback;
+
+ private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Verifies condition reports false when tracker reports a different user id than the
+ * identifier from the process handle.
+ */
+ @Test
+ public void testConditionFailsWithDifferentIds() {
+
+ final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker);
+ when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0);
+ when(mUserTracker.getUserId()).thenReturn(1);
+
+ final Monitor monitor = new Monitor(mExecutor);
+
+ monitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
+ .addCondition(condition)
+ .build());
+
+ mExecutor.runAllReady();
+
+ verify(mCallback).onConditionsChanged(false);
+ }
+
+ /**
+ * Verifies condition reports false when tracker reports a different user id than the
+ * identifier from the process handle.
+ */
+ @Test
+ public void testConditionSucceedsWithSameIds() {
+
+ final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker);
+ when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0);
+ when(mUserTracker.getUserId()).thenReturn(0);
+
+ final Monitor monitor = new Monitor(mExecutor);
+
+ monitor.addSubscription(new Monitor.Subscription.Builder(mCallback)
+ .addCondition(condition)
+ .build());
+
+ mExecutor.runAllReady();
+
+ verify(mCallback).onConditionsChanged(true);
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 6d2972d..508327f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -879,6 +879,26 @@
}
}
+ @Test
+ public void getMobileNetworkSummary_withCarrierNetworkChange() {
+ Resources res = mock(Resources.class);
+ doReturn("Carrier network changing").when(res).getString(anyInt());
+ when(SubscriptionManager.getResourcesForSubId(any(), eq(SUB_ID))).thenReturn(res);
+ InternetDialogController spyController = spy(mInternetDialogController);
+ Map<Integer, TelephonyDisplayInfo> mSubIdTelephonyDisplayInfoMap =
+ spyController.mSubIdTelephonyDisplayInfoMap;
+ TelephonyDisplayInfo info = new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+
+ mSubIdTelephonyDisplayInfoMap.put(SUB_ID, info);
+ doReturn(true).when(spyController).isMobileDataEnabled();
+ doReturn(true).when(spyController).activeNetworkIsCellular();
+ spyController.mCarrierNetworkChangeMode = true;
+ String dds = spyController.getMobileNetworkSummary(SUB_ID);
+
+ assertThat(dds).contains(mContext.getString(R.string.carrier_network_change_mode));
+ }
+
private String getResourcesString(String name) {
return mContext.getResources().getString(getResourcesId(name));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 69f3e987..33aaa3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -16,13 +16,15 @@
package com.android.systemui.screenrecord;
+import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.Dialog;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Looper;
@@ -31,7 +33,13 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -61,8 +69,15 @@
@Mock
private UserContextProvider mUserContextProvider;
@Mock
+ private ScreenCaptureDevicePolicyResolver mDevicePolicyResolver;
+ @Mock
+ private DialogLaunchAnimator mDialogLaunchAnimator;
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
private UserTracker mUserTracker;
+ private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
private static final int USER_ID = 10;
@@ -70,8 +85,9 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = new RecordingController(mMainExecutor, mBroadcastDispatcher,
- mUserContextProvider, mUserTracker);
+ mFeatureFlags = new FakeFeatureFlags();
+ mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext,
+ mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker);
mController.addCallback(mCallback);
}
@@ -190,4 +206,67 @@
verify(mCallback).onRecordingEnd();
assertFalse(mController.isRecording());
}
+
+ @Test
+ public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
+ when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
+
+ Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+ mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+ assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
+ }
+
+ @Test
+ public void testPartialScreenSharingDisabled_returnsLegacyDialog() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false);
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
+
+ Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+ mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+ assertThat(dialog).isInstanceOf(ScreenRecordDialog.class);
+ }
+
+ @Test
+ public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+ when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
+
+ Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+ mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+ assertThat(dialog).isInstanceOf(ScreenCaptureDisabledDialog.class);
+ }
+
+ @Test
+ public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+ mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+ when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
+
+ Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
+ mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+ assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 0aa3621..5b094c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenrecord
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
@@ -59,6 +60,7 @@
dialog =
ScreenRecordPermissionDialog(
context,
+ UserHandle.of(0),
controller,
starter,
dialogLaunchAnimator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 78bebb9..d01edcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -18,8 +18,6 @@
import android.content.ContentResolver
import android.content.Context
import android.graphics.drawable.Drawable
-import android.os.Handler
-import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -34,6 +32,9 @@
import com.android.systemui.util.mockito.eq
import junit.framework.Assert.assertEquals
import junit.framework.Assert.fail
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
import org.json.JSONException
import org.junit.Before
import org.junit.Rule
@@ -49,19 +50,19 @@
class ClockRegistryTest : SysuiTestCase() {
@JvmField @Rule val mockito = MockitoJUnit.rule()
+ private lateinit var dispatcher: CoroutineDispatcher
+ private lateinit var scope: TestScope
+
@Mock private lateinit var mockContext: Context
@Mock private lateinit var mockPluginManager: PluginManager
@Mock private lateinit var mockClock: ClockController
@Mock private lateinit var mockDefaultClock: ClockController
@Mock private lateinit var mockThumbnail: Drawable
- @Mock private lateinit var mockHandler: Handler
@Mock private lateinit var mockContentResolver: ContentResolver
private lateinit var fakeDefaultProvider: FakeClockPlugin
private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
private lateinit var registry: ClockRegistry
- private var settingValue: ClockSettings? = null
-
companion object {
private fun failFactory(): ClockController {
fail("Unexpected call to createClock")
@@ -99,6 +100,9 @@
@Before
fun setUp() {
+ dispatcher = StandardTestDispatcher()
+ scope = TestScope(dispatcher)
+
fakeDefaultProvider = FakeClockPlugin()
.addClock(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME, { mockDefaultClock }, { mockThumbnail })
whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
@@ -107,15 +111,22 @@
registry = object : ClockRegistry(
mockContext,
mockPluginManager,
- mockHandler,
+ scope = scope.backgroundScope,
+ mainDispatcher = dispatcher,
+ bgDispatcher = dispatcher,
isEnabled = true,
- userHandle = UserHandle.USER_ALL,
- defaultClockProvider = fakeDefaultProvider
+ handleAllUsers = true,
+ defaultClockProvider = fakeDefaultProvider,
) {
- override var settings: ClockSettings?
- get() = settingValue
- set(value) { settingValue = value }
+ override fun querySettings() { }
+ override fun applySettings(value: ClockSettings?) {
+ settings = value
+ }
+ // Unit Test does not validate threading
+ override fun assertMainThread() {}
+ override fun assertNotMainThread() {}
}
+ registry.registerListeners()
verify(mockPluginManager)
.addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java), eq(true))
@@ -187,16 +198,16 @@
.addClock("clock_1", "clock 1")
.addClock("clock_2", "clock 2")
- settingValue = ClockSettings("clock_3", null, null)
val plugin2 = FakeClockPlugin()
.addClock("clock_3", "clock 3", { mockClock })
.addClock("clock_4", "clock 4")
+ registry.applySettings(ClockSettings("clock_3", null))
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
val clock = registry.createCurrentClock()
- assertEquals(clock, mockClock)
+ assertEquals(mockClock, clock)
}
@Test
@@ -205,11 +216,11 @@
.addClock("clock_1", "clock 1")
.addClock("clock_2", "clock 2")
- settingValue = ClockSettings("clock_3", null, null)
val plugin2 = FakeClockPlugin()
.addClock("clock_3", "clock 3")
.addClock("clock_4", "clock 4")
+ registry.applySettings(ClockSettings("clock_3", null))
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
pluginListener.onPluginDisconnected(plugin2)
@@ -224,11 +235,11 @@
.addClock("clock_1", "clock 1")
.addClock("clock_2", "clock 2")
- settingValue = ClockSettings("clock_3", null, null)
val plugin2 = FakeClockPlugin()
.addClock("clock_3", "clock 3", { mockClock })
.addClock("clock_4", "clock 4")
+ registry.applySettings(ClockSettings("clock_3", null))
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
@@ -244,7 +255,7 @@
@Test
fun jsonDeserialization_gotExpectedObject() {
- val expected = ClockSettings("ID", null, 500)
+ val expected = ClockSettings("ID", null).apply { _applied_timestamp = 500 }
val actual = ClockSettings.deserialize("""{
"clockId":"ID",
"_applied_timestamp":500
@@ -254,14 +265,14 @@
@Test
fun jsonDeserialization_noTimestamp_gotExpectedObject() {
- val expected = ClockSettings("ID", null, null)
+ val expected = ClockSettings("ID", null)
val actual = ClockSettings.deserialize("{\"clockId\":\"ID\"}")
assertEquals(expected, actual)
}
@Test
fun jsonDeserialization_nullTimestamp_gotExpectedObject() {
- val expected = ClockSettings("ID", null, null)
+ val expected = ClockSettings("ID", null)
val actual = ClockSettings.deserialize("""{
"clockId":"ID",
"_applied_timestamp":null
@@ -271,7 +282,7 @@
@Test(expected = JSONException::class)
fun jsonDeserialization_noId_threwException() {
- val expected = ClockSettings("ID", null, 500)
+ val expected = ClockSettings(null, null).apply { _applied_timestamp = 500 }
val actual = ClockSettings.deserialize("{\"_applied_timestamp\":500}")
assertEquals(expected, actual)
}
@@ -279,14 +290,15 @@
@Test
fun jsonSerialization_gotExpectedString() {
val expected = "{\"clockId\":\"ID\",\"_applied_timestamp\":500}"
- val actual = ClockSettings.serialize(ClockSettings("ID", null, 500))
+ val actual = ClockSettings.serialize(ClockSettings("ID", null)
+ .apply { _applied_timestamp = 500 })
assertEquals(expected, actual)
}
@Test
fun jsonSerialization_noTimestamp_gotExpectedString() {
val expected = "{\"clockId\":\"ID\"}"
- val actual = ClockSettings.serialize(ClockSettings("ID", null, null))
+ val actual = ClockSettings.serialize(ClockSettings("ID", null))
assertEquals(expected, actual)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
index 7693fee..9eccbb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
@@ -471,4 +471,142 @@
mExecutor.runAllReady();
verify(callback).onConditionsChanged(true);
}
+
+ /**
+ * Ensures that the result of a condition being true leads to its nested condition being
+ * activated.
+ */
+ @Test
+ public void testNestedCondition() {
+ mCondition1.fakeUpdateCondition(false);
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+
+ mCondition2.fakeUpdateCondition(false);
+
+ // Create a nested condition
+ mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(
+ new Monitor.Subscription.Builder(callback)
+ .addCondition(mCondition2)
+ .build())
+ .addCondition(mCondition1)
+ .build());
+
+ mExecutor.runAllReady();
+
+ // Ensure the nested condition callback is not called at all.
+ verify(callback, never()).onActiveChanged(anyBoolean());
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+
+ // Update the inner condition to true and ensure that the nested condition is not triggered.
+ mCondition2.fakeUpdateCondition(true);
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+ mCondition2.fakeUpdateCondition(false);
+
+ // Set outer condition and make sure the inner condition becomes active and reports that
+ // conditions aren't met
+ mCondition1.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onActiveChanged(eq(true));
+ verify(callback).onConditionsChanged(eq(false));
+
+ Mockito.clearInvocations(callback);
+
+ // Update the inner condition and make sure the callback is updated.
+ mCondition2.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onConditionsChanged(true);
+
+ Mockito.clearInvocations(callback);
+ // Invalidate outer condition and make sure callback is informed, but the last state is
+ // not affected.
+ mCondition1.fakeUpdateCondition(false);
+ mExecutor.runAllReady();
+
+ verify(callback).onActiveChanged(eq(false));
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+ }
+
+ /**
+ * Ensures a subscription is predicated on its precondition.
+ */
+ @Test
+ public void testPrecondition() {
+ mCondition1.fakeUpdateCondition(false);
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+
+ mCondition2.fakeUpdateCondition(false);
+
+ // Create a nested condition
+ mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(callback)
+ .addPrecondition(mCondition1)
+ .addCondition(mCondition2)
+ .build());
+
+ mExecutor.runAllReady();
+
+ // Ensure the nested condition callback is not called at all.
+ verify(callback, never()).onActiveChanged(anyBoolean());
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+
+ // Update the condition to true and ensure that the nested condition is not triggered.
+ mCondition2.fakeUpdateCondition(true);
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+ mCondition2.fakeUpdateCondition(false);
+
+ // Set precondition and make sure the inner condition becomes active and reports that
+ // conditions aren't met
+ mCondition1.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onActiveChanged(eq(true));
+ verify(callback).onConditionsChanged(eq(false));
+
+ Mockito.clearInvocations(callback);
+
+ // Update the condition and make sure the callback is updated.
+ mCondition2.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onConditionsChanged(true);
+
+ Mockito.clearInvocations(callback);
+ // Invalidate precondition and make sure callback is informed, but the last state is
+ // not affected.
+ mCondition1.fakeUpdateCondition(false);
+ mExecutor.runAllReady();
+
+ verify(callback).onActiveChanged(eq(false));
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+ }
+
+ /**
+ * Ensure preconditions are applied to every subscription added to a monitor.
+ */
+ @Test
+ public void testPreconditionMonitor() {
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+
+ mCondition2.fakeUpdateCondition(true);
+ final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(mCondition1)));
+
+ monitor.addSubscription(new Monitor.Subscription.Builder(callback)
+ .addCondition(mCondition2)
+ .build());
+
+ mExecutor.runAllReady();
+
+ verify(callback, never()).onActiveChanged(anyBoolean());
+ verify(callback, never()).onConditionsChanged(anyBoolean());
+
+ mCondition1.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+
+ verify(callback).onActiveChanged(eq(true));
+ verify(callback).onConditionsChanged(eq(true));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index dffa566..406826b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -23,6 +23,7 @@
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
@@ -620,6 +621,109 @@
}
@Test
+ public void onBiometricHelp_coEx_faceUnavailable() {
+ createController();
+
+ // GIVEN unlocking with fingerprint is possible
+ when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
+ .thenReturn(true);
+
+ String message = "A message";
+ mController.setVisible(true);
+
+ // WHEN there's a face unavailable message
+ mController.getKeyguardCallback().onBiometricHelp(
+ BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+ message,
+ BiometricSourceType.FACE);
+
+ // THEN show sequential messages such as: 'face unlock unavailable' and
+ // 'try fingerprint instead'
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ message);
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_suggest_fingerprint));
+ }
+
+ @Test
+ public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
+ createController();
+
+ // GIVEN face has already unlocked the device
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithFace(anyInt())).thenReturn(true);
+
+ String message = "A message";
+ mController.setVisible(true);
+
+ // WHEN there's a fingerprint not recognized message
+ mController.getKeyguardCallback().onBiometricHelp(
+ BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+ message,
+ BiometricSourceType.FINGERPRINT);
+
+ // THEN show sequential messages such as: 'Unlocked by face' and
+ // 'Swipe up to open'
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ mContext.getString(R.string.keyguard_face_successful_unlock));
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricHelp_coEx_fpFailure_trustAgentAlreadyUnlocked() {
+ createController();
+
+ // GIVEN trust agent has already unlocked the device
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+
+ String message = "A message";
+ mController.setVisible(true);
+
+ // WHEN there's a fingerprint not recognized message
+ mController.getKeyguardCallback().onBiometricHelp(
+ BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+ message,
+ BiometricSourceType.FINGERPRINT);
+
+ // THEN show sequential messages such as: 'Kept unlocked by TrustAgent' and
+ // 'Swipe up to open'
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ mContext.getString(R.string.keyguard_indication_trust_unlocked));
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricHelp_coEx_fpFailure_trustAgentUnlocked_emptyTrustGrantedMessage() {
+ createController();
+
+ // GIVEN trust agent has already unlocked the device & trust granted message is empty
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+ mController.showTrustGrantedMessage(false, "");
+
+ String message = "A message";
+ mController.setVisible(true);
+
+ // WHEN there's a fingerprint not recognized message
+ mController.getKeyguardCallback().onBiometricHelp(
+ BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED,
+ message,
+ BiometricSourceType.FINGERPRINT);
+
+ // THEN show action to unlock (ie: 'Swipe up to open')
+ verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
public void transientIndication_visibleWhenDozing_unlessSwipeUp_fromError() {
createController();
String message = mContext.getString(R.string.keyguard_unlock);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 0a576de..d6225c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -32,7 +32,9 @@
import android.view.View
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
@@ -104,6 +106,9 @@
private lateinit var keyguardBypassController: KeyguardBypassController
@Mock
+ private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Mock
private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock
@@ -125,6 +130,9 @@
private lateinit var configPlugin: BcSmartspaceConfigPlugin
@Mock
+ private lateinit var dumpManager: DumpManager
+
+ @Mock
private lateinit var controllerListener: SmartspaceTargetListener
@Captor
@@ -223,6 +231,8 @@
statusBarStateController,
deviceProvisionedController,
keyguardBypassController,
+ keyguardUpdateMonitor,
+ dumpManager,
execution,
executor,
bgExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 2686238..49da848 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -38,6 +38,8 @@
import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
@@ -63,6 +65,7 @@
@RunWith(AndroidTestingRunner::class)
class KeyguardCoordinatorTest : SysuiTestCase() {
+ private val headsUpManager: HeadsUpManager = mock()
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
private val keyguardRepository = FakeKeyguardRepository()
private val notifPipelineFlags: NotifPipelineFlags = mock()
@@ -90,8 +93,9 @@
fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
- // GIVEN: Keyguard is not showing, and a notification is present
+ // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
keyguardRepository.setKeyguardShowing(false)
+ whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
@@ -113,11 +117,44 @@
}
@Test
+ fun unseenFilter_headsUpMarkedAsSeen() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing, shade is not expanded
+ keyguardRepository.setKeyguardShowing(false)
+ whenever(statusBarStateController.isExpanded).thenReturn(false)
+ runKeyguardCoordinatorTest {
+ // WHEN: A notification is posted
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: That notification is heads up
+ onHeadsUpChangedListener.onHeadsUpStateChanged(fakeEntry, /* isHeadsUp= */ true)
+ testScheduler.runCurrent()
+
+ // WHEN: The keyguard is now showing
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+ // WHEN: The keyguard goes away
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is shown regardless
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
fun unseenFilterDoesNotSuppressSeenOngoingNotifWhileKeyguardShowing() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
- // GIVEN: Keyguard is not showing, and an ongoing notification is present
+ // GIVEN: Keyguard is not showing, shade is expanded, and an ongoing notification is present
keyguardRepository.setKeyguardShowing(false)
+ whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
val fakeEntry = NotificationEntryBuilder()
.setNotification(Notification.Builder(mContext).setOngoing(true).build())
@@ -137,8 +174,9 @@
fun unseenFilterDoesNotSuppressSeenMediaNotifWhileKeyguardShowing() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
- // GIVEN: Keyguard is not showing, and a media notification is present
+ // GIVEN: Keyguard is not showing, shade is expanded, and a media notification is present
keyguardRepository.setKeyguardShowing(false)
+ whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
val fakeEntry = NotificationEntryBuilder().build().apply {
row = mock<ExpandableNotificationRow>().apply {
@@ -160,8 +198,9 @@
fun unseenFilterUpdatesSeenProviderWhenSuppressing() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
- // GIVEN: Keyguard is not showing, and a notification is present
+ // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
keyguardRepository.setKeyguardShowing(false)
+ whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
@@ -185,8 +224,9 @@
fun unseenFilterInvalidatesWhenSettingChanges() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
- // GIVEN: Keyguard is not showing
+ // GIVEN: Keyguard is not showing, and shade is expanded
keyguardRepository.setKeyguardShowing(false)
+ whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
// GIVEN: A notification is present
val fakeEntry = NotificationEntryBuilder().build()
@@ -237,8 +277,9 @@
fun unseenFilterSeenGroupSummaryWithUnseenChild() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
- // GIVEN: Keyguard is not showing, and a notification is present
+ // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present
keyguardRepository.setKeyguardShowing(false)
+ whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
// WHEN: A new notification is posted
val fakeSummary = NotificationEntryBuilder().build()
@@ -276,11 +317,11 @@
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
- // WHEN: Keyguard is no longer showing for 5 seconds
+ // WHEN: Keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
- testScheduler.runCurrent()
- testScheduler.advanceTimeBy(5.seconds.inWholeMilliseconds)
- testScheduler.runCurrent()
+
+ // When: Shade is expanded
+ statusBarStateListener.onExpandedChanged(true)
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
@@ -292,7 +333,7 @@
}
@Test
- fun unseenNotificationIsNotMarkedAsSeenIfTimeThresholdNotMet() {
+ fun unseenNotificationIsNotMarkedAsSeenIfShadeNotExpanded() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
// GIVEN: Keyguard is showing, unseen notification is present
@@ -301,10 +342,8 @@
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
- // WHEN: Keyguard is no longer showing for <5 seconds
+ // WHEN: Keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
- testScheduler.runCurrent()
- testScheduler.advanceTimeBy(1.seconds.inWholeMilliseconds)
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
@@ -327,6 +366,7 @@
val keyguardCoordinator =
KeyguardCoordinator(
testDispatcher,
+ headsUpManager,
keyguardNotifVisibilityProvider,
keyguardRepository,
notifPipelineFlags,
@@ -364,12 +404,21 @@
val unseenFilter: NotifFilter
get() = keyguardCoordinator.unseenNotifFilter
- // TODO(254647461): Remove lazy once Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD is enabled and
- // removed
+ // TODO(254647461): Remove lazy from these properties once
+ // Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD is enabled and removed
+
val collectionListener: NotifCollectionListener by lazy {
withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) }
}
+ val onHeadsUpChangedListener: OnHeadsUpChangedListener by lazy {
+ withArgCaptor { verify(headsUpManager).addListener(capture()) }
+ }
+
+ val statusBarStateListener: StatusBarStateController.StateListener by lazy {
+ withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
+ }
+
var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
get() =
fakeSettings.getIntForUser(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
index 33b94e3..bd03903 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.lang.RuntimeException
import kotlinx.coroutines.Dispatchers
import org.junit.Before
import org.junit.Test
@@ -113,6 +114,24 @@
assertThat(data).hasSize(2)
}
+ @Test
+ fun onPullAtom_throwsInterruptedException_failsGracefully() {
+ val pipeline: NotifPipeline = mock()
+ whenever(pipeline.allNotifs).thenAnswer { throw InterruptedException("Timeout") }
+ val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
+ .isEqualTo(StatsManager.PULL_SKIP)
+ }
+
+ @Test
+ fun onPullAtom_throwsRuntimeException_failsGracefully() {
+ val pipeline: NotifPipeline = mock()
+ whenever(pipeline.allNotifs).thenThrow(RuntimeException("Something broke!"))
+ val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
+ .isEqualTo(StatsManager.PULL_SKIP)
+ }
+
private fun createLoggerWithNotifications(
notifications: List<Notification>
): NotificationMemoryLogger {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 5394d88..3face35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -42,6 +43,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RemoteViews;
@@ -332,6 +334,38 @@
eq(FLAG_CONTENT_VIEW_HEADS_UP));
}
+ @Test
+ public void testNotificationViewHeightTooSmallFailsValidation() {
+ View view = mock(View.class);
+ when(view.getHeight())
+ .thenReturn((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10,
+ mContext.getResources().getDisplayMetrics()));
+ String result = NotificationContentInflater.isValidView(view, mRow.getEntry(),
+ mContext.getResources());
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testNotificationViewPassesValidation() {
+ View view = mock(View.class);
+ when(view.getHeight())
+ .thenReturn((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 17,
+ mContext.getResources().getDisplayMetrics()));
+ String result = NotificationContentInflater.isValidView(view, mRow.getEntry(),
+ mContext.getResources());
+ assertNull(result);
+ }
+
+ @Test
+ public void testInvalidNotificationDoesNotInvokeCallback() throws Exception {
+ mRow.getPrivateLayout().removeAllViews();
+ mRow.getEntry().getSbn().getNotification().contentView =
+ new RemoteViews(mContext.getPackageName(), R.layout.invalid_notification_height);
+ inflateAndWait(true, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+ assertEquals(0, mRow.getPrivateLayout().getChildCount());
+ verify(mRow, times(0)).onNotificationUpdated();
+ }
+
private static void inflateAndWait(NotificationContentInflater inflater,
@InflationFlag int contentToInflate,
ExpandableNotificationRow row)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 8cfcc07..f568547 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
+import static com.android.systemui.statusbar.phone.AutoTileManager.DEVICE_CONTROLS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -50,6 +51,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.ReduceBrightColorsController;
@@ -70,6 +72,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
@@ -77,6 +80,7 @@
import org.mockito.Spy;
import org.mockito.stubbing.Answer;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -543,6 +547,61 @@
}
@Test
+ public void testAddControlsTileIfNotPresent() {
+ String spec = DEVICE_CONTROLS;
+ when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
+ when(mQsTileHost.getTiles()).thenReturn(new ArrayList<>());
+
+ mAutoTileManager.init();
+ ArgumentCaptor<DeviceControlsController.Callback> captor =
+ ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
+
+ verify(mDeviceControlsController).setCallback(captor.capture());
+
+ captor.getValue().onControlsUpdate(3);
+ verify(mQsTileHost).addTile(spec, 3);
+ verify(mAutoAddTracker).setTileAdded(spec);
+ }
+
+ @Test
+ public void testDontAddControlsTileIfPresent() {
+ String spec = DEVICE_CONTROLS;
+ when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
+ when(mQsTileHost.getTiles()).thenReturn(new ArrayList<>());
+
+ mAutoTileManager.init();
+ ArgumentCaptor<DeviceControlsController.Callback> captor =
+ ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
+
+ verify(mDeviceControlsController).setCallback(captor.capture());
+
+ captor.getValue().removeControlsAutoTracker();
+ verify(mQsTileHost, never()).addTile(spec, 3);
+ verify(mAutoAddTracker, never()).setTileAdded(spec);
+ verify(mAutoAddTracker).setTileRemoved(spec);
+ }
+
+ @Test
+ public void testRemoveControlsTileFromTrackerWhenRequested() {
+ String spec = "controls";
+ when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(true);
+ QSTile mockTile = mock(QSTile.class);
+ when(mockTile.getTileSpec()).thenReturn(spec);
+ when(mQsTileHost.getTiles()).thenReturn(List.of(mockTile));
+
+ mAutoTileManager.init();
+ ArgumentCaptor<DeviceControlsController.Callback> captor =
+ ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
+
+ verify(mDeviceControlsController).setCallback(captor.capture());
+
+ captor.getValue().onControlsUpdate(3);
+ verify(mQsTileHost, never()).addTile(spec, 3);
+ verify(mAutoAddTracker, never()).setTileAdded(spec);
+ }
+
+
+ @Test
public void testEmptyArray_doesNotCrash() {
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsAutoAdd, new String[0]);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index ed3f710..7e69efa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -264,6 +264,19 @@
}
@Test
+ public void notifPositionAlignedWithClockAndBurnInOffsetInSplitShadeMode() {
+ setSplitShadeTopMargin(100); // this makes clock to be at 100
+ givenAOD();
+ mIsSplitShade = true;
+ givenMaxBurnInOffset(100);
+ givenHighestBurnInOffset(); // this makes clock to be at 200
+ // WHEN the position algorithm is run
+ positionClock();
+ // THEN the notif padding adjusts for burn-in offset: clock position - burn-in offset
+ assertThat(mClockPosition.stackScrollerPadding).isEqualTo(100);
+ }
+
+ @Test
public void clockPositionedDependingOnMarginInSplitShade() {
setSplitShadeTopMargin(400);
givenLockScreen();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 6fb6893..8aaa57f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -23,6 +23,8 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -33,7 +35,10 @@
import androidx.test.filters.SmallTest;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarMobileView;
import com.android.systemui.statusbar.StatusBarWifiView;
@@ -46,6 +51,8 @@
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
@@ -87,6 +94,61 @@
testCallOnAdd_forManager(manager);
}
+ @Test
+ public void testRemoveIcon_ignoredForNewPipeline() {
+ IconManager manager = mock(IconManager.class);
+
+ // GIVEN the new pipeline is on
+ StatusBarPipelineFlags flags = mock(StatusBarPipelineFlags.class);
+ when(flags.isIconControlledByFlags("test_icon")).thenReturn(true);
+
+ StatusBarIconController iconController = new StatusBarIconControllerImpl(
+ mContext,
+ mock(CommandQueue.class),
+ mock(DemoModeController.class),
+ mock(ConfigurationController.class),
+ mock(TunerService.class),
+ mock(DumpManager.class),
+ mock(StatusBarIconList.class),
+ flags
+ );
+
+ iconController.addIconGroup(manager);
+
+ // WHEN a request to remove a new icon is sent
+ iconController.removeIcon("test_icon", 0);
+
+ // THEN it is not removed for those icons
+ verify(manager, never()).onRemoveIcon(anyInt());
+ }
+
+ @Test
+ public void testRemoveAllIconsForSlot_ignoredForNewPipeline() {
+ IconManager manager = mock(IconManager.class);
+
+ // GIVEN the new pipeline is on
+ StatusBarPipelineFlags flags = mock(StatusBarPipelineFlags.class);
+ when(flags.isIconControlledByFlags("test_icon")).thenReturn(true);
+
+ StatusBarIconController iconController = new StatusBarIconControllerImpl(
+ mContext,
+ mock(CommandQueue.class),
+ mock(DemoModeController.class),
+ mock(ConfigurationController.class),
+ mock(TunerService.class),
+ mock(DumpManager.class),
+ mock(StatusBarIconList.class),
+ flags
+ );
+
+ iconController.addIconGroup(manager);
+
+ // WHEN a request to remove a new icon is sent
+ iconController.removeAllIconsForSlot("test_icon");
+
+ // THEN it is not removed for those icons
+ verify(manager, never()).onRemoveIcon(anyInt());
+ }
private <T extends IconManager & TestableIconManager> void testCallOnAdd_forManager(T manager) {
StatusBarIconHolder holder = holderForType(TYPE_ICON);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
index f822ba0..45189cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
@@ -19,7 +19,8 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableRowLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_IN
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_OUT
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CARRIER_NETWORK_CHANGE
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CDMA_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CONNECTION_STATE
@@ -54,7 +55,19 @@
assertThat(logger.changes)
.contains(Pair(COL_CONNECTION_STATE, connection.dataConnectionState.toString()))
assertThat(logger.changes)
- .contains(Pair(COL_ACTIVITY_DIRECTION, connection.dataActivityDirection.toString()))
+ .contains(
+ Pair(
+ COL_ACTIVITY_DIRECTION_IN,
+ connection.dataActivityDirection.hasActivityIn.toString(),
+ )
+ )
+ assertThat(logger.changes)
+ .contains(
+ Pair(
+ COL_ACTIVITY_DIRECTION_OUT,
+ connection.dataActivityDirection.hasActivityOut.toString(),
+ )
+ )
assertThat(logger.changes)
.contains(
Pair(COL_CARRIER_NETWORK_CHANGE, connection.carrierNetworkChangeActive.toString())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
new file mode 100644
index 0000000..63cb30c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.model
+
+import android.os.PersistableBundle
+import android.telephony.CarrierConfigManager
+import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
+import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.junit.Before
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class SystemUiCarrierConfigTest : SysuiTestCase() {
+
+ lateinit var underTest: SystemUiCarrierConfig
+
+ @Before
+ fun setUp() {
+ underTest = SystemUiCarrierConfig(SUB_1_ID, createTestConfig())
+ }
+
+ @Test
+ fun `process new config - reflected by isUsingDefault`() {
+ // Starts out using the defaults
+ assertThat(underTest.isUsingDefault).isTrue()
+
+ // ANY new config means we're no longer tracking defaults
+ underTest.processNewCarrierConfig(createTestConfig())
+
+ assertThat(underTest.isUsingDefault).isFalse()
+ }
+
+ @Test
+ fun `process new config - updates all flows`() {
+ assertThat(underTest.shouldInflateSignalStrength.value).isFalse()
+ assertThat(underTest.showOperatorNameInStatusBar.value).isFalse()
+
+ underTest.processNewCarrierConfig(
+ configWithOverrides(
+ KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
+ KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
+ )
+ )
+
+ assertThat(underTest.shouldInflateSignalStrength.value).isTrue()
+ assertThat(underTest.showOperatorNameInStatusBar.value).isTrue()
+ }
+
+ @Test
+ fun `process new config - defaults to false for config overrides`() {
+ // This case is only apparent when:
+ // 1. The default is true
+ // 2. The override config has no value for a given key
+ // In this case (per the old code) we would use the default value of false, despite there
+ // being no override key present in the override config
+
+ underTest =
+ SystemUiCarrierConfig(
+ SUB_1_ID,
+ configWithOverrides(
+ KEY_INFLATE_SIGNAL_STRENGTH_BOOL to true,
+ KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL to true,
+ )
+ )
+
+ assertThat(underTest.isUsingDefault).isTrue()
+ assertThat(underTest.shouldInflateSignalStrength.value).isTrue()
+ assertThat(underTest.showOperatorNameInStatusBar.value).isTrue()
+
+ // Process a new config with no keys
+ underTest.processNewCarrierConfig(PersistableBundle())
+
+ assertThat(underTest.isUsingDefault).isFalse()
+ assertThat(underTest.shouldInflateSignalStrength.value).isFalse()
+ assertThat(underTest.showOperatorNameInStatusBar.value).isFalse()
+ }
+
+ companion object {
+ private const val SUB_1_ID = 1
+
+ /**
+ * In order to keep us from having to update every place that might want to create a config,
+ * make sure to add new keys here
+ */
+ fun createTestConfig() =
+ PersistableBundle().also {
+ it.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ it.putBoolean(CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL, false)
+ }
+
+ /** Override the default config with the given (key, value) pair */
+ fun configWithOverride(key: String, override: Boolean): PersistableBundle =
+ createTestConfig().also { it.putBoolean(key, override) }
+
+ /** Override any number of configs from the default */
+ fun configWithOverrides(vararg overrides: Pair<String, Boolean>) =
+ createTestConfig().also { config ->
+ overrides.forEach { (key, value) -> config.putBoolean(key, value) }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
new file mode 100644
index 0000000..521c67f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository
+
+import android.content.Intent
+import android.os.PersistableBundle
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.MockitoSession
+import org.mockito.quality.Strictness
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class CarrierConfigRepositoryTest : SysuiTestCase() {
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private lateinit var underTest: CarrierConfigRepository
+ private lateinit var mockitoSession: MockitoSession
+ private lateinit var carrierConfigCoreStartable: CarrierConfigCoreStartable
+
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var carrierConfigManager: CarrierConfigManager
+ @Mock private lateinit var dumpManager: DumpManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mockitoSession =
+ mockitoSession()
+ .initMocks(this)
+ .mockStatic(CarrierConfigManager::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+
+ whenever(CarrierConfigManager.getDefaultConfig()).thenReturn(DEFAULT_CONFIG)
+
+ whenever(carrierConfigManager.getConfigForSubId(anyInt())).thenAnswer { invocation ->
+ when (invocation.getArgument(0) as Int) {
+ 1 -> CONFIG_1
+ 2 -> CONFIG_2
+ else -> null
+ }
+ }
+
+ underTest =
+ CarrierConfigRepository(
+ fakeBroadcastDispatcher,
+ carrierConfigManager,
+ dumpManager,
+ logger,
+ testScope.backgroundScope,
+ )
+
+ carrierConfigCoreStartable =
+ CarrierConfigCoreStartable(underTest, testScope.backgroundScope)
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun `carrier config stream produces int-bundle pairs`() =
+ testScope.runTest {
+ var latest: Pair<Int, PersistableBundle>? = null
+ val job = underTest.carrierConfigStream.onEach { latest = it }.launchIn(this)
+
+ sendConfig(SUB_ID_1)
+ assertThat(latest).isEqualTo(Pair(SUB_ID_1, CONFIG_1))
+
+ sendConfig(SUB_ID_2)
+ assertThat(latest).isEqualTo(Pair(SUB_ID_2, CONFIG_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun `carrier config stream ignores invalid subscriptions`() =
+ testScope.runTest {
+ var latest: Pair<Int, PersistableBundle>? = null
+ val job = underTest.carrierConfigStream.onEach { latest = it }.launchIn(this)
+
+ sendConfig(INVALID_SUBSCRIPTION_ID)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `getOrCreateConfig - uses default config if no override`() {
+ val config = underTest.getOrCreateConfigForSubId(123)
+ assertThat(config.isUsingDefault).isTrue()
+ }
+
+ @Test
+ fun `getOrCreateConfig - uses override if exists`() {
+ val config = underTest.getOrCreateConfigForSubId(SUB_ID_1)
+ assertThat(config.isUsingDefault).isFalse()
+ }
+
+ @Test
+ fun `config - updates while config stream is collected`() =
+ testScope.runTest {
+ CONFIG_1.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+
+ carrierConfigCoreStartable.start()
+
+ val config = underTest.getOrCreateConfigForSubId(SUB_ID_1)
+ assertThat(config.shouldInflateSignalStrength.value).isFalse()
+
+ CONFIG_1.putBoolean(CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
+ sendConfig(SUB_ID_1)
+
+ assertThat(config.shouldInflateSignalStrength.value).isTrue()
+ }
+
+ private fun sendConfig(subId: Int) {
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+ .putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, subId)
+ )
+ }
+ }
+
+ companion object {
+ private const val SUB_ID_1 = 1
+ private const val SUB_ID_2 = 2
+
+ private val DEFAULT_CONFIG = createTestConfig()
+ private val CONFIG_1 = createTestConfig()
+ private val CONFIG_2 = createTestConfig()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 0add905e..f483e42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -55,8 +55,12 @@
private val _subscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
override val subscriptions = _subscriptions
- private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+ private val _activeMobileDataSubscriptionId = MutableStateFlow<Int?>(null)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
+
+ private val _activeMobileRepository = MutableStateFlow<MobileConnectionRepository?>(null)
+ override val activeMobileDataRepository = _activeMobileRepository
+
override val activeSubChangedInGroupEvent: MutableSharedFlow<Unit> = MutableSharedFlow()
private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
@@ -66,14 +70,12 @@
override val defaultMobileNetworkConnectivity = _mobileConnectivity
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
+
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
return subIdRepos[subId]
?: FakeMobileConnectionRepository(subId, tableLogBuffer).also { subIdRepos[subId] = it }
}
- private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
- override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
-
override val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config())
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
@@ -94,12 +96,15 @@
_mobileConnectivity.value = model
}
- suspend fun triggerGlobalMobileDataSettingChangedEvent() {
- _globalMobileDataSettingChangedEvent.emit(Unit)
- }
-
fun setActiveMobileDataSubscriptionId(subId: Int) {
- _activeMobileDataSubscriptionId.value = subId
+ // Simulate the filtering that the repo does
+ if (subId == INVALID_SUBSCRIPTION_ID) {
+ _activeMobileDataSubscriptionId.value = null
+ _activeMobileRepository.value = null
+ } else {
+ _activeMobileDataSubscriptionId.value = subId
+ _activeMobileRepository.value = getRepoForSubId(subId)
+ }
}
fun setMobileConnectionRepositoryMap(connections: Map<Int, MobileConnectionRepository>) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 0859d14..4da2104 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
@@ -40,7 +41,6 @@
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -82,10 +82,10 @@
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var summaryLogger: TableLogBuffer
@Mock private lateinit var demoModeController: DemoModeController
@Mock private lateinit var dumpManager: DumpManager
- private val globalSettings = FakeSettings()
private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
private val mobileMappings = FakeMobileMappingsProxy()
@@ -116,9 +116,9 @@
subscriptionManager,
telephonyManager,
logger,
+ summaryLogger,
mobileMappings,
fakeBroadcastDispatcher,
- globalSettings,
context,
IMMEDIATE,
scope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index 6989b514..00ce412 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -138,7 +138,8 @@
assertThat(connectionInfo.carrierNetworkChangeActive)
.isEqualTo(model.carrierNetworkChange)
assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
- assertThat(conn.networkName.value).isEqualTo(NetworkNameModel.Derived(model.name))
+ assertThat(conn.networkName.value)
+ .isEqualTo(NetworkNameModel.IntentDerived(model.name))
// TODO(b/261029387): check these once we start handling them
assertThat(connectionInfo.isEmergencyOnly).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index f12d113..f60d92b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -539,7 +539,8 @@
assertThat(connectionInfo.carrierNetworkChangeActive)
.isEqualTo(model.carrierNetworkChange)
assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
- assertThat(conn.networkName.value).isEqualTo(NetworkNameModel.Derived(model.name))
+ assertThat(conn.networkName.value)
+ .isEqualTo(NetworkNameModel.IntentDerived(model.name))
// TODO(b/261029387) check these once we start handling them
assertThat(connectionInfo.isEmergencyOnly).isFalse()
@@ -594,9 +595,11 @@
subId: Int = 1,
level: Int = 1,
numberOfLevels: Int = 4,
+ activity: Int = DATA_ACTIVITY_NONE,
): FakeWifiEventModel.CarrierMerged =
FakeWifiEventModel.CarrierMerged(
subscriptionId = subId,
level = level,
numberOfLevels = numberOfLevels,
+ activity = activity,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
index ea90150..abb4561 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.telephony.TelephonyManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -27,6 +28,7 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -49,6 +51,7 @@
private lateinit var wifiRepository: FakeWifiRepository
@Mock private lateinit var logger: TableLogBuffer
+ @Mock private lateinit var telephonyManager: TelephonyManager
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -56,13 +59,16 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID)
+ whenever(telephonyManager.simOperatorName).thenReturn("")
+
wifiRepository = FakeWifiRepository()
underTest =
CarrierMergedConnectionRepository(
SUB_ID,
logger,
- NetworkNameModel.Default("name"),
+ telephonyManager,
testScope.backgroundScope,
wifiRepository,
)
@@ -135,6 +141,44 @@
}
@Test
+ fun connectionInfo_activity_comesFromWifiActivity() =
+ testScope.runTest {
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+ wifiRepository.setIsWifiEnabled(true)
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId = NET_ID,
+ subscriptionId = SUB_ID,
+ level = 3,
+ )
+ )
+ wifiRepository.setWifiActivity(
+ DataActivityModel(
+ hasActivityIn = true,
+ hasActivityOut = false,
+ )
+ )
+
+ assertThat(latest!!.dataActivityDirection.hasActivityIn).isTrue()
+ assertThat(latest!!.dataActivityDirection.hasActivityOut).isFalse()
+
+ wifiRepository.setWifiActivity(
+ DataActivityModel(
+ hasActivityIn = false,
+ hasActivityOut = true,
+ )
+ )
+
+ assertThat(latest!!.dataActivityDirection.hasActivityIn).isFalse()
+ assertThat(latest!!.dataActivityDirection.hasActivityOut).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
fun connectionInfo_carrierMergedWifi_wrongSubId_isDefault() =
testScope.runTest {
var latest: MobileConnectionModel? = null
@@ -244,6 +288,43 @@
job.cancel()
}
+ @Test
+ fun networkName_usesSimOperatorNameAsInitial() =
+ testScope.runTest {
+ whenever(telephonyManager.simOperatorName).thenReturn("Test SIM name")
+
+ var latest: NetworkNameModel? = null
+ val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("Test SIM name"))
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkName_updatesOnNetworkUpdate() =
+ testScope.runTest {
+ whenever(telephonyManager.simOperatorName).thenReturn("Test SIM name")
+
+ var latest: NetworkNameModel? = null
+ val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("Test SIM name"))
+
+ whenever(telephonyManager.simOperatorName).thenReturn("New SIM name")
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId = NET_ID,
+ subscriptionId = SUB_ID,
+ level = 3,
+ )
+ )
+
+ assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("New SIM name"))
+
+ job.cancel()
+ }
+
private companion object {
const val SUB_ID = 123
const val NET_ID = 456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index c02a4df..c02ca01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -16,24 +16,33 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
@@ -55,24 +64,18 @@
class FullMobileConnectionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: FullMobileConnectionRepository
+ private val systemClock = FakeSystemClock()
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
- private val mobileMappings = FakeMobileMappingsProxy()
- private val tableLogBuffer = mock<TableLogBuffer>()
+ private val tableLogBuffer = TableLogBuffer(maxSize = 100, name = "TestName", systemClock)
private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()
private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>()
- private lateinit var connectionsRepo: FakeMobileConnectionsRepository
- private val globalMobileDataSettingChangedEvent: Flow<Unit>
- get() = connectionsRepo.globalMobileDataSettingChangedEvent
-
private lateinit var mobileRepo: FakeMobileConnectionRepository
private lateinit var carrierMergedRepo: FakeMobileConnectionRepository
@Before
fun setUp() {
- connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogBuffer)
-
mobileRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer)
carrierMergedRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer)
@@ -82,12 +85,10 @@
any(),
eq(DEFAULT_NAME),
eq(SEP),
- eq(globalMobileDataSettingChangedEvent),
)
)
.thenReturn(mobileRepo)
- whenever(carrierMergedFactory.build(eq(SUB_ID), any(), eq(DEFAULT_NAME)))
- .thenReturn(carrierMergedRepo)
+ whenever(carrierMergedFactory.build(eq(SUB_ID), any())).thenReturn(carrierMergedRepo)
}
@Test
@@ -109,7 +110,6 @@
tableLogBuffer,
DEFAULT_NAME,
SEP,
- globalMobileDataSettingChangedEvent
)
}
@@ -126,7 +126,7 @@
assertThat(underTest.activeRepo.value).isEqualTo(mobileRepo)
assertThat(underTest.connectionInfo.value).isEqualTo(mobileConnectionInfo)
- verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer, DEFAULT_NAME)
+ verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer)
}
@Test
@@ -310,7 +310,6 @@
startingIsCarrierMerged = false,
DEFAULT_NAME,
SEP,
- globalMobileDataSettingChangedEvent,
)
val connection1Repeat =
@@ -319,7 +318,6 @@
startingIsCarrierMerged = false,
DEFAULT_NAME,
SEP,
- globalMobileDataSettingChangedEvent,
)
assertThat(connection1.tableLogBuffer)
@@ -345,7 +343,6 @@
startingIsCarrierMerged = false,
DEFAULT_NAME,
SEP,
- globalMobileDataSettingChangedEvent,
)
// WHEN a connection with the same sub ID but carrierMerged = true is created
@@ -355,7 +352,6 @@
startingIsCarrierMerged = true,
DEFAULT_NAME,
SEP,
- globalMobileDataSettingChangedEvent,
)
// THEN the same table is re-used
@@ -363,8 +359,219 @@
.isSameInstanceAs(connection1Repeat.tableLogBuffer)
}
- // TODO(b/238425913): Verify that the logging switches correctly (once the carrier merged repo
- // implements logging).
+ @Test
+ fun connectionInfo_logging_notCarrierMerged_getsUpdates() =
+ testScope.runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager =
+ mock<TelephonyManager>().apply { whenever(this.simOperatorName).thenReturn("") }
+ createRealMobileRepo(telephonyManager)
+ createRealCarrierMergedRepo(telephonyManager, FakeWifiRepository())
+
+ initializeRepo(startingIsCarrierMerged = false)
+
+ val job = underTest.connectionInfo.launchIn(this)
+
+ // WHEN we set up some mobile connection info
+ val serviceState = ServiceState()
+ serviceState.setOperatorName("longName", "OpTypical", "1")
+ serviceState.isEmergencyOnly = false
+ getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
+ .onServiceStateChanged(serviceState)
+
+ // THEN it's logged to the buffer
+ assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpTypical")
+ assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}false")
+
+ // WHEN we update mobile connection info
+ val serviceState2 = ServiceState()
+ serviceState2.setOperatorName("longName", "OpDiff", "1")
+ serviceState2.isEmergencyOnly = true
+ getTelephonyCallbackForType<TelephonyCallback.ServiceStateListener>(telephonyManager)
+ .onServiceStateChanged(serviceState2)
+
+ // THEN the updates are logged
+ assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpDiff")
+ assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}true")
+
+ job.cancel()
+ }
+
+ @Test
+ fun connectionInfo_logging_carrierMerged_getsUpdates() =
+ testScope.runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager =
+ mock<TelephonyManager>().apply { whenever(this.simOperatorName).thenReturn("") }
+ createRealMobileRepo(telephonyManager)
+ val wifiRepository = FakeWifiRepository()
+ createRealCarrierMergedRepo(telephonyManager, wifiRepository)
+
+ initializeRepo(startingIsCarrierMerged = true)
+
+ val job = underTest.connectionInfo.launchIn(this)
+
+ // WHEN we set up carrier merged info
+ val networkId = 2
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 3,
+ )
+ )
+
+ // THEN the carrier merged info is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN we update the info
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 1,
+ )
+ )
+
+ // THEN the updates are logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+ job.cancel()
+ }
+
+ @Test
+ fun connectionInfo_logging_updatesWhenCarrierMergedUpdates() =
+ testScope.runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager =
+ mock<TelephonyManager>().apply { whenever(this.simOperatorName).thenReturn("") }
+ createRealMobileRepo(telephonyManager)
+
+ val wifiRepository = FakeWifiRepository()
+ createRealCarrierMergedRepo(telephonyManager, wifiRepository)
+
+ initializeRepo(startingIsCarrierMerged = false)
+
+ val job = underTest.connectionInfo.launchIn(this)
+
+ // WHEN we set up some mobile connection info
+ val signalStrength = mock<SignalStrength>()
+ whenever(signalStrength.level).thenReturn(1)
+
+ getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+ .onSignalStrengthsChanged(signalStrength)
+
+ // THEN it's logged to the buffer
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+ // WHEN isCarrierMerged is set to true
+ val networkId = 2
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 3,
+ )
+ )
+ underTest.setIsCarrierMerged(true)
+
+ // THEN the carrier merged info is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN the carrier merge network is updated
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 4,
+ )
+ )
+
+ // THEN the new level is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
+
+ // WHEN isCarrierMerged is set to false
+ underTest.setIsCarrierMerged(false)
+
+ // THEN the typical info is logged
+ // Note: Since our first logs also had the typical info, we need to search the log
+ // contents for after our carrier merged level log.
+ val fullBuffer = dumpBuffer()
+ val carrierMergedContentIndex = fullBuffer.indexOf("${BUFFER_SEPARATOR}4")
+ val bufferAfterCarrierMerged = fullBuffer.substring(carrierMergedContentIndex)
+ assertThat(bufferAfterCarrierMerged).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
+
+ // WHEN the normal network is updated
+ val newMobileInfo =
+ MobileConnectionModel(
+ operatorAlphaShort = "Mobile Operator 2",
+ primaryLevel = 0,
+ )
+ mobileRepo.setConnectionInfo(newMobileInfo)
+
+ // THEN the new level is logged
+ assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}0")
+
+ job.cancel()
+ }
+
+ @Test
+ fun connectionInfo_logging_doesNotLogUpdatesForNotActiveRepo() =
+ testScope.runTest {
+ // SETUP: Use real repositories to verify the diffing still works. (See b/267501739.)
+ val telephonyManager =
+ mock<TelephonyManager>().apply { whenever(this.simOperatorName).thenReturn("") }
+ createRealMobileRepo(telephonyManager)
+
+ val wifiRepository = FakeWifiRepository()
+ createRealCarrierMergedRepo(telephonyManager, wifiRepository)
+
+ // WHEN isCarrierMerged = false
+ initializeRepo(startingIsCarrierMerged = false)
+
+ val job = underTest.connectionInfo.launchIn(this)
+
+ val signalStrength = mock<SignalStrength>()
+ whenever(signalStrength.level).thenReturn(1)
+ getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+ .onSignalStrengthsChanged(signalStrength)
+
+ // THEN updates to the carrier merged level aren't logged
+ val networkId = 2
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 4,
+ )
+ )
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4")
+
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(
+ networkId,
+ SUB_ID,
+ level = 3,
+ )
+ )
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}3")
+
+ // WHEN isCarrierMerged is set to true
+ underTest.setIsCarrierMerged(true)
+
+ // THEN updates to the normal level aren't logged
+ whenever(signalStrength.level).thenReturn(5)
+ getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+ .onSignalStrengthsChanged(signalStrength)
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}5")
+
+ whenever(signalStrength.level).thenReturn(6)
+ getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>(telephonyManager)
+ .onSignalStrengthsChanged(signalStrength)
+ assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}6")
+
+ job.cancel()
+ }
private fun initializeRepo(startingIsCarrierMerged: Boolean) {
underTest =
@@ -374,16 +581,74 @@
tableLogBuffer,
DEFAULT_NAME,
SEP,
- globalMobileDataSettingChangedEvent,
testScope.backgroundScope,
mobileFactory,
carrierMergedFactory,
)
}
+ private fun createRealMobileRepo(
+ telephonyManager: TelephonyManager,
+ ): MobileConnectionRepositoryImpl {
+ whenever(telephonyManager.subscriptionId).thenReturn(SUB_ID)
+
+ val realRepo =
+ MobileConnectionRepositoryImpl(
+ context,
+ SUB_ID,
+ defaultNetworkName = NetworkNameModel.Default("default"),
+ networkNameSeparator = SEP,
+ telephonyManager,
+ systemUiCarrierConfig = mock(),
+ fakeBroadcastDispatcher,
+ mobileMappingsProxy = mock(),
+ testDispatcher,
+ logger = mock(),
+ tableLogBuffer,
+ testScope.backgroundScope,
+ )
+ whenever(
+ mobileFactory.build(
+ eq(SUB_ID),
+ any(),
+ eq(DEFAULT_NAME),
+ eq(SEP),
+ )
+ )
+ .thenReturn(realRepo)
+
+ return realRepo
+ }
+
+ private fun createRealCarrierMergedRepo(
+ telephonyManager: TelephonyManager,
+ wifiRepository: FakeWifiRepository,
+ ): CarrierMergedConnectionRepository {
+ wifiRepository.setIsWifiEnabled(true)
+ wifiRepository.setIsWifiDefault(true)
+ val realRepo =
+ CarrierMergedConnectionRepository(
+ SUB_ID,
+ tableLogBuffer,
+ telephonyManager,
+ testScope.backgroundScope,
+ wifiRepository,
+ )
+ whenever(carrierMergedFactory.build(eq(SUB_ID), any())).thenReturn(realRepo)
+
+ return realRepo
+ }
+
+ private fun dumpBuffer(): String {
+ val outputWriter = StringWriter()
+ tableLogBuffer.dump(PrintWriter(outputWriter), arrayOf())
+ return outputWriter.toString()
+ }
+
private companion object {
const val SUB_ID = 42
private val DEFAULT_NAME = NetworkNameModel.Default("default name")
private const val SEP = "-"
+ private const val BUFFER_SEPARATOR = "|"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index d6b8c0d..a294088a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -17,15 +17,13 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.content.Intent
-import android.os.UserHandle
-import android.provider.Settings
+import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
import android.telephony.CellSignalStrengthCdma
import android.telephony.NetworkRegistrationInfo
import android.telephony.ServiceState
import android.telephony.ServiceState.STATE_IN_SERVICE
import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
import android.telephony.SignalStrength
-import android.telephony.SubscriptionInfo
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.DataActivityListener
import android.telephony.TelephonyCallback.ServiceStateListener
@@ -41,6 +39,8 @@
import android.telephony.TelephonyManager.DATA_CONNECTING
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_HANDOVER_IN_PROGRESS
+import android.telephony.TelephonyManager.DATA_SUSPENDED
import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.ERI_OFF
import android.telephony.TelephonyManager.ERI_ON
@@ -59,6 +59,9 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.configWithOverride
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
@@ -67,10 +70,8 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -83,7 +84,6 @@
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -99,12 +99,15 @@
private val scope = CoroutineScope(IMMEDIATE)
private val mobileMappings = FakeMobileMappingsProxy()
- private val globalSettings = FakeSettings()
+ private val systemUiCarrierConfig =
+ SystemUiCarrierConfig(
+ SUB_1_ID,
+ createTestConfig(),
+ )
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- globalSettings.userId = UserHandle.USER_ALL
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger)
@@ -116,9 +119,8 @@
DEFAULT_NAME,
SEP,
telephonyManager,
- globalSettings,
+ systemUiCarrierConfig,
fakeBroadcastDispatcher,
- connectionsRepo.globalMobileDataSettingChangedEvent,
mobileMappings,
IMMEDIATE,
logger,
@@ -255,6 +257,37 @@
}
@Test
+ fun testFlowForSubId_dataConnectionState_suspended() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_SUSPENDED, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Suspended)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testFlowForSubId_dataConnectionState_handoverInProgress() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_HANDOVER_IN_PROGRESS, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState)
+ .isEqualTo(DataConnectionState.HandoverInProgress)
+
+ job.cancel()
+ }
+
+ @Test
fun testFlowForSubId_dataConnectionState_unknown() =
runBlocking(IMMEDIATE) {
var latest: MobileConnectionModel? = null
@@ -270,6 +303,21 @@
}
@Test
+ fun testFlowForSubId_dataConnectionState_invalid() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileConnectionModel? = null
+ val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(45, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Invalid)
+
+ job.cancel()
+ }
+
+ @Test
fun testFlowForSubId_dataActivity() =
runBlocking(IMMEDIATE) {
var latest: MobileConnectionModel? = null
@@ -352,52 +400,26 @@
@Test
fun dataEnabled_initial_false() =
runBlocking(IMMEDIATE) {
- whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
-
- assertThat(underTest.dataEnabled.value).isFalse()
- }
-
- @Test
- fun dataEnabled_isEnabled_true() =
- runBlocking(IMMEDIATE) {
- whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
- val job = underTest.dataEnabled.launchIn(this)
-
- assertThat(underTest.dataEnabled.value).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun dataEnabled_isDisabled() =
- runBlocking(IMMEDIATE) {
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
- val job = underTest.dataEnabled.launchIn(this)
assertThat(underTest.dataEnabled.value).isFalse()
-
- job.cancel()
}
@Test
- fun isDataConnectionAllowed_subIdSettingUpdate_valueUpdated() =
+ fun `is data enabled - tracks telephony callback`() =
runBlocking(IMMEDIATE) {
- val subIdSettingName = "${Settings.Global.MOBILE_DATA}$SUB_1_ID"
-
var latest: Boolean? = null
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
- // We don't read the setting directly, we query telephony when changes happen
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
- globalSettings.putInt(subIdSettingName, 0)
- assertThat(latest).isFalse()
+ assertThat(underTest.dataEnabled.value).isFalse()
- whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
- globalSettings.putInt(subIdSettingName, 1)
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataEnabledListener>()
+
+ callback.onDataEnabledChanged(true, 1)
assertThat(latest).isTrue()
- whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
- globalSettings.putInt(subIdSettingName, 0)
+ callback.onDataEnabledChanged(false, 1)
assertThat(latest).isFalse()
job.cancel()
@@ -418,8 +440,6 @@
fun `roaming - cdma - queries telephony manager`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
- // Start the telephony collection job so that cdmaRoaming starts updating
- val telephonyJob = underTest.connectionInfo.launchIn(this)
val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
val cb = getTelephonyCallbackForType<ServiceStateListener>()
@@ -439,7 +459,6 @@
assertThat(latest).isTrue()
- telephonyJob.cancel()
job.cancel()
}
@@ -536,16 +555,51 @@
}
@Test
- fun `network name - broadcast not for this sub id - returns default`() =
+ fun `network name - broadcast not for this sub id - keeps old value`() =
runBlocking(IMMEDIATE) {
var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
- val intent = spnIntent(subId = 101)
-
+ val intent = spnIntent()
fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
receiver.onReceive(context, intent)
}
+ assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+
+ // WHEN an intent with a different subId is sent
+ val wrongSubIntent = spnIntent(subId = 101)
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(context, wrongSubIntent)
+ }
+
+ // THEN the previous intent's name is still used
+ assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun `network name - broadcast has no data - updates to default`() =
+ runBlocking(IMMEDIATE) {
+ var latest: NetworkNameModel? = null
+ val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+ val intent = spnIntent()
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(context, intent)
+ }
+ assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+
+ val intentWithoutInfo =
+ spnIntent(
+ showSpn = false,
+ showPlmn = false,
+ )
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(context, intentWithoutInfo)
+ }
assertThat(latest).isEqualTo(DEFAULT_NAME)
@@ -553,7 +607,7 @@
}
@Test
- fun `network name - operatorAlphaShort - tracked`() =
+ fun `operatorAlphaShort - tracked`() =
runBlocking(IMMEDIATE) {
var latest: String? = null
@@ -625,16 +679,31 @@
job.cancel()
}
- private fun getTelephonyCallbacks(): List<TelephonyCallback> {
- val callbackCaptor = argumentCaptor<TelephonyCallback>()
- Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
- return callbackCaptor.allValues
- }
+ @Test
+ fun `number of levels - uses carrier config`() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, true)
+ )
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS + 1)
+
+ systemUiCarrierConfig.processNewCarrierConfig(
+ configWithOverride(KEY_INFLATE_SIGNAL_STRENGTH_BOOL, false)
+ )
+
+ assertThat(latest).isEqualTo(DEFAULT_NUM_LEVELS)
+
+ job.cancel()
+ }
private inline fun <reified T> getTelephonyCallbackForType(): T {
- val cbs = getTelephonyCallbacks().filterIsInstance<T>()
- assertThat(cbs.size).isEqualTo(1)
- return cbs[0]
+ return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
}
/** Convenience constructor for SignalStrength */
@@ -668,8 +737,6 @@
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1
- private val SUB_1 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
private val DEFAULT_NAME = NetworkNameModel.Default("default name")
private const val SEP = "-"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index db8172a..673e559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -23,10 +23,10 @@
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.os.ParcelUuid
-import android.provider.Settings
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
@@ -39,6 +39,8 @@
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
@@ -49,13 +51,13 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
@@ -80,20 +82,22 @@
private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
private lateinit var wifiRepository: FakeWifiRepository
+ private lateinit var carrierConfigRepository: CarrierConfigRepository
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var summaryLogger: TableLogBuffer
@Mock private lateinit var logBufferFactory: TableLogBufferFactory
private val mobileMappings = FakeMobileMappingsProxy()
private val scope = CoroutineScope(IMMEDIATE)
- private val globalSettings = FakeSettings()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(telephonyManager.simOperatorName).thenReturn("")
// Set up so the individual connection repositories
whenever(telephonyManager.createForSubscriptionId(anyInt())).thenAnswer { invocation ->
@@ -119,19 +123,29 @@
wifiRepository = FakeWifiRepository()
+ carrierConfigRepository =
+ CarrierConfigRepository(
+ fakeBroadcastDispatcher,
+ mock(),
+ mock(),
+ logger,
+ scope,
+ )
+
connectionFactory =
MobileConnectionRepositoryImpl.Factory(
fakeBroadcastDispatcher,
context = context,
telephonyManager = telephonyManager,
bgDispatcher = IMMEDIATE,
- globalSettings = globalSettings,
logger = logger,
mobileMappingsProxy = mobileMappings,
scope = scope,
+ carrierConfigRepository = carrierConfigRepository,
)
carrierMergedFactory =
CarrierMergedConnectionRepository.Factory(
+ telephonyManager,
scope,
wifiRepository,
)
@@ -149,9 +163,9 @@
subscriptionManager,
telephonyManager,
logger,
+ summaryLogger,
mobileMappings,
fakeBroadcastDispatcher,
- globalSettings,
context,
IMMEDIATE,
scope,
@@ -245,10 +259,9 @@
}
@Test
- fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+ fun testActiveDataSubscriptionId_initialValueIsNull() =
runBlocking(IMMEDIATE) {
- assertThat(underTest.activeMobileDataSubscriptionId.value)
- .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ assertThat(underTest.activeMobileDataSubscriptionId.value).isEqualTo(null)
}
@Test
@@ -267,6 +280,140 @@
}
@Test
+ fun activeSubId_nullIfInvalidSubIdIsReceived() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+
+ val job = underTest.activeMobileDataSubscriptionId.onEach { latest = it }.launchIn(this)
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(latest).isNotNull()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activeRepo_initiallyNull() {
+ assertThat(underTest.activeMobileDataRepository.value).isNull()
+ }
+
+ @Test
+ fun activeRepo_updatesWithActiveDataId() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileConnectionRepository? = null
+ val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this)
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(latest?.subId).isEqualTo(SUB_2_ID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun activeRepo_nullIfActiveDataSubIdBecomesInvalid() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileConnectionRepository? = null
+ val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this)
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(latest).isNotNull()
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ /** Regression test for b/268146648. */
+ fun activeSubIdIsSetBeforeSubscriptionsAreUpdated_doesNotThrow() =
+ runBlocking(IMMEDIATE) {
+ var activeRepo: MobileConnectionRepository? = null
+ var subscriptions: List<SubscriptionModel>? = null
+
+ val activeRepoJob =
+ underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this)
+ val subscriptionsJob =
+ underTest.subscriptions.onEach { subscriptions = it }.launchIn(this)
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(subscriptions).isEmpty()
+ assertThat(activeRepo).isNotNull()
+
+ activeRepoJob.cancel()
+ subscriptionsJob.cancel()
+ }
+
+ @Test
+ fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileConnectionRepository? = null
+ var subscriptions: List<SubscriptionModel>? = null
+ val activeSubIdJob =
+ underTest.activeMobileDataSubscriptionId
+ .filterNotNull()
+ .onEach { latest = underTest.getRepoForSubId(it) }
+ .launchIn(this)
+ val subscriptionsJob =
+ underTest.subscriptions.onEach { subscriptions = it }.launchIn(this)
+
+ // Active data subscription id is sent, but no subscription change has been posted yet
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ // Subscriptions list is empty
+ assertThat(subscriptions).isEmpty()
+ // getRepoForSubId does not throw
+ assertThat(latest).isNotNull()
+
+ activeSubIdJob.cancel()
+ subscriptionsJob.cancel()
+ }
+
+ @Test
+ fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() =
+ runBlocking(IMMEDIATE) {
+ var activeRepo: MobileConnectionRepository? = null
+ val job = underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this)
+ val subscriptionsJob = underTest.subscriptions.launchIn(this)
+
+ // GIVEN active repo is updated before the subscription list updates
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(activeRepo).isNotNull()
+
+ // GIVEN the subscription list is then updated which includes the active data sub id
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // WHEN requesting a connection repository for the subscription
+ val newRepo = underTest.getRepoForSubId(SUB_2_ID)
+
+ // THEN the newly request repo has been cached and reused
+ assertThat(activeRepo).isSameInstanceAs(newRepo)
+
+ job.cancel()
+ subscriptionsJob.cancel()
+ }
+
+ @Test
fun testConnectionRepository_validSubId_isCached() =
runBlocking(IMMEDIATE) {
val job = underTest.subscriptions.launchIn(this)
@@ -466,7 +613,7 @@
}
@Test
- fun `connection repository - log buffer contains sub id in its name`() =
+ fun connectionRepository_logBufferContainsSubIdInItsName() =
runBlocking(IMMEDIATE) {
val job = underTest.subscriptions.launchIn(this)
@@ -544,24 +691,6 @@
}
@Test
- fun globalMobileDataSettingsChangedEvent_producesOnSettingChange() =
- runBlocking(IMMEDIATE) {
- var produced = false
- val job =
- underTest.globalMobileDataSettingChangedEvent
- .onEach { produced = true }
- .launchIn(this)
-
- assertThat(produced).isFalse()
-
- globalSettings.putInt(Settings.Global.MOBILE_DATA, 0)
-
- assertThat(produced).isTrue()
-
- job.cancel()
- }
-
- @Test
fun mobileConnectivity_isConnected_isNotValidated() =
runBlocking(IMMEDIATE) {
val caps = createCapabilities(connected = true, validated = false)
@@ -627,9 +756,9 @@
subscriptionManager,
telephonyManager,
logger,
+ summaryLogger,
mobileMappings,
fakeBroadcastDispatcher,
- globalSettings,
context,
IMMEDIATE,
scope,
@@ -700,7 +829,7 @@
}
@Test
- fun `active data change - in same group - emits unit`() =
+ fun activeDataChange_inSameGroup_emitsUnit() =
runBlocking(IMMEDIATE) {
var latest: Unit? = null
val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
@@ -716,7 +845,7 @@
}
@Test
- fun `active data change - not in same group - does not emit`() =
+ fun activeDataChange_notInSameGroup_doesNotEmit() =
runBlocking(IMMEDIATE) {
var latest: Unit? = null
val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
new file mode 100644
index 0000000..621f793
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.google.common.truth.Truth.assertThat
+import org.mockito.Mockito.verify
+
+/** Helper methods for telephony-related callbacks for mobile tests. */
+object MobileTelephonyHelpers {
+ fun getTelephonyCallbacks(mockTelephonyManager: TelephonyManager): List<TelephonyCallback> {
+ val callbackCaptor = argumentCaptor<TelephonyCallback>()
+ verify(mockTelephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ inline fun <reified T> getTelephonyCallbackForType(mockTelephonyManager: TelephonyManager): T {
+ val cbs = getTelephonyCallbacks(mockTelephonyManager).filterIsInstance<T>()
+ assertThat(cbs.size).isEqualTo(1)
+ return cbs[0]
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 7aeaa48..b645e66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -45,7 +45,7 @@
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
override val networkTypeIconGroup = _iconGroup
- override val networkName = MutableStateFlow(NetworkNameModel.Derived("demo mode"))
+ override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode"))
private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly = _isEmergencyOnly
@@ -71,6 +71,8 @@
private val _numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
override val numberOfLevels = _numberOfLevels
+ override val isForceHidden = MutableStateFlow(false)
+
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
_iconGroup.value = group
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 172755c..2699316 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -73,6 +73,8 @@
private val _isUserSetup = MutableStateFlow(true)
override val isUserSetup = _isUserSetup
+ override val isForceHidden = MutableStateFlow(false)
+
/** Always returns a new fake interactor */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
return FakeMobileIconInteractor(tableLogBuffer)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index c42aba5..fa072fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -66,6 +66,7 @@
mobileIconsInteractor.defaultMobileIconGroup,
mobileIconsInteractor.defaultDataSubId,
mobileIconsInteractor.isDefaultConnectionFailed,
+ mobileIconsInteractor.isForceHidden,
connectionRepository,
)
}
@@ -530,7 +531,7 @@
)
yield()
- assertThat(latest).isEqualTo(NetworkNameModel.Derived(testOperatorName))
+ assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
// Default network name, operator name is null, uses the default
connectionRepository.setConnectionInfo(MobileConnectionModel(operatorAlphaShort = null))
@@ -550,6 +551,21 @@
job.cancel()
}
+ @Test
+ fun isForceHidden_matchesParent() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+ mobileIconsInteractor.isForceHidden.value = true
+ assertThat(latest).isTrue()
+
+ mobileIconsInteractor.isForceHidden.value = false
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
@@ -559,6 +575,6 @@
private const val SUB_1_ID = 1
private val DEFAULT_NAME = NetworkNameModel.Default("test default name")
- private val DERIVED_NAME = NetworkNameModel.Derived("test derived name")
+ private val DERIVED_NAME = NetworkNameModel.IntentDerived("test derived name")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index bd24922..f8a9783 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -50,6 +52,7 @@
@SmallTest
class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor
+ private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var connectionsRepository: FakeMobileConnectionsRepository
private val userSetupRepository = FakeUserSetupRepository()
private val mobileMappingsProxy = FakeMobileMappingsProxy()
@@ -63,6 +66,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ connectivityRepository = FakeConnectivityRepository()
+
connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
connectionsRepository.setMobileConnectionRepositoryMap(
mapOf(
@@ -79,6 +84,8 @@
connectionsRepository,
carrierConfigTracker,
logger = mock(),
+ tableLogger = mock(),
+ connectivityRepository,
userSetupRepository,
testScope.backgroundScope,
)
@@ -609,6 +616,32 @@
job.cancel()
}
+ @Test
+ fun isForceHidden_repoHasMobileHidden_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
+
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
companion object {
private val tableLogBuffer =
TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index a2c1209..e68a397 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -29,12 +29,14 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -58,31 +60,37 @@
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var tableLogBuffer: TableLogBuffer
- @Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
+ private lateinit var interactor: FakeMobileIconInteractor
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
+ private lateinit var viewModelCommon: MobileIconViewModel
private lateinit var viewModel: LocationBasedMobileViewModel
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ // This line was necessary to make the onDarkChanged and setStaticDrawableColor tests pass.
+ // But, it maybe *shouldn't* be necessary.
+ whenever(constants.hasDataCapabilities).thenReturn(true)
+
testableLooper = TestableLooper.get(this)
- val interactor = FakeMobileIconInteractor(tableLogBuffer)
-
- val viewModelCommon =
- MobileIconViewModel(
- subscriptionId = 1,
- interactor,
- logger,
- constants,
- testScope.backgroundScope,
+ airplaneModeRepository = FakeAirplaneModeRepository()
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ FakeConnectivityRepository(),
)
- viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
+
+ interactor = FakeMobileIconInteractor(tableLogBuffer)
+ createViewModel()
}
// Note: The following tests are more like integration tests, since they stand up a full
- // [WifiViewModel] and test the interactions between the view, view-binder, and view-model.
+ // [MobileIconViewModel] and test the interactions between the view, view-binder, and
+ // view-model.
@Test
fun setVisibleState_icon_iconShownDotHidden() {
@@ -130,7 +138,25 @@
}
@Test
- fun isIconVisible_alwaysTrue() {
+ fun isIconVisible_noData_outputsFalse() {
+ whenever(constants.hasDataCapabilities).thenReturn(false)
+ createViewModel()
+
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isFalse()
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun isIconVisible_hasData_outputsTrue() {
+ whenever(constants.hasDataCapabilities).thenReturn(true)
+ createViewModel()
+
val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
ViewUtils.attachView(view)
@@ -142,6 +168,34 @@
}
@Test
+ fun isIconVisible_notAirplaneMode_outputsTrue() {
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isTrue()
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun isIconVisible_airplaneMode_outputsTrue() {
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isFalse()
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
fun onDarkChanged_iconHasNewColor() {
whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
@@ -184,6 +238,18 @@
private fun View.getDotView(): View {
return this.requireViewById(R.id.status_bar_dot)
}
+
+ private fun createViewModel() {
+ viewModelCommon =
+ MobileIconViewModel(
+ subscriptionId = 1,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ testScope.backgroundScope,
+ )
+ viewModel = QsMobileIconViewModel(viewModelCommon, statusBarPipelineFlags)
+ }
}
private const val SLOT_NAME = "TestSlotName"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index c960a06..f983030 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -21,10 +21,13 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -46,8 +49,8 @@
private lateinit var qsIcon: QsMobileIconViewModel
private lateinit var keyguardIcon: KeyguardMobileIconViewModel
private lateinit var interactor: FakeMobileIconInteractor
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
- @Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@@ -57,6 +60,11 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ FakeAirplaneModeRepository(),
+ FakeConnectivityRepository(),
+ )
interactor = FakeMobileIconInteractor(tableLogBuffer)
interactor.apply {
setLevel(1)
@@ -68,7 +76,13 @@
isDataConnected.value = true
}
commonImpl =
- MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
+ MobileIconViewModel(
+ SUB_1_ID,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ testScope.backgroundScope,
+ )
homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
@@ -78,14 +92,14 @@
@Test
fun `location based view models receive same icon id when common impl updates`() =
testScope.runTest {
- var latestHome: Int? = null
- val homeJob = homeIcon.iconId.onEach { latestHome = it }.launchIn(this)
+ var latestHome: SignalIconModel? = null
+ val homeJob = homeIcon.icon.onEach { latestHome = it }.launchIn(this)
- var latestQs: Int? = null
- val qsJob = qsIcon.iconId.onEach { latestQs = it }.launchIn(this)
+ var latestQs: SignalIconModel? = null
+ val qsJob = qsIcon.icon.onEach { latestQs = it }.launchIn(this)
- var latestKeyguard: Int? = null
- val keyguardJob = keyguardIcon.iconId.onEach { latestKeyguard = it }.launchIn(this)
+ var latestKeyguard: SignalIconModel? = null
+ val keyguardJob = keyguardIcon.icon.onEach { latestKeyguard = it }.launchIn(this)
var expected = defaultSignal(level = 1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index b91a4df..bec276a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -19,16 +19,18 @@
import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE
-import com.android.settingslib.graph.SignalDrawable
import com.android.settingslib.mobile.TelephonyIcons.THREE_G
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -49,7 +51,8 @@
class MobileIconViewModelTest : SysuiTestCase() {
private lateinit var underTest: MobileIconViewModel
private lateinit var interactor: FakeMobileIconInteractor
- @Mock private lateinit var logger: ConnectivityPipelineLogger
+ private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var constants: ConnectivityConstants
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@@ -59,6 +62,15 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(constants.hasDataCapabilities).thenReturn(true)
+
+ airplaneModeRepository = FakeAirplaneModeRepository()
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ FakeConnectivityRepository(),
+ )
+
interactor = FakeMobileIconInteractor(tableLogBuffer)
interactor.apply {
setLevel(1)
@@ -69,15 +81,94 @@
setNumberOfLevels(4)
isDataConnected.value = true
}
- underTest =
- MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
+ createAndSetViewModel()
}
@Test
+ fun isVisible_notDataCapable_alwaysFalse() =
+ testScope.runTest {
+ // Create a new view model here so the constants are properly read
+ whenever(constants.hasDataCapabilities).thenReturn(false)
+ createAndSetViewModel()
+
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_notAirplane_notForceHidden_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ interactor.isForceHidden.value = false
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_airplane_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ interactor.isForceHidden.value = false
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_forceHidden_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ interactor.isForceHidden.value = true
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isVisible_respondsToUpdates() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ interactor.isForceHidden.value = false
+
+ assertThat(latest).isTrue()
+
+ airplaneModeRepository.setIsAirplaneMode(true)
+ assertThat(latest).isFalse()
+
+ airplaneModeRepository.setIsAirplaneMode(false)
+ assertThat(latest).isTrue()
+
+ interactor.isForceHidden.value = true
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun iconId_correctLevel_notCutout() =
testScope.runTest {
- var latest: Int? = null
- val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel? = null
+ val job = underTest.icon.onEach { latest = it }.launchIn(this)
val expected = defaultSignal()
assertThat(latest).isEqualTo(expected)
@@ -90,8 +181,8 @@
testScope.runTest {
interactor.setIsDefaultDataEnabled(false)
- var latest: Int? = null
- val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel? = null
+ val job = underTest.icon.onEach { latest = it }.launchIn(this)
val expected = defaultSignal(level = 1, connected = false)
assertThat(latest).isEqualTo(expected)
@@ -102,8 +193,8 @@
@Test
fun `icon - uses empty state - when not in service`() =
testScope.runTest {
- var latest: Int? = null
- val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ var latest: SignalIconModel? = null
+ val job = underTest.icon.onEach { latest = it }.launchIn(this)
interactor.isInService.value = false
@@ -364,14 +455,7 @@
testScope.runTest {
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(false)
- underTest =
- MobileIconViewModel(
- SUB_1_ID,
- interactor,
- logger,
- constants,
- testScope.backgroundScope,
- )
+ createAndSetViewModel()
var inVisible: Boolean? = null
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -403,14 +487,7 @@
testScope.runTest {
// Create a new view model here so the constants are properly read
whenever(constants.shouldShowActivityConfig).thenReturn(true)
- underTest =
- MobileIconViewModel(
- SUB_1_ID,
- interactor,
- logger,
- constants,
- testScope.backgroundScope,
- )
+ createAndSetViewModel()
var inVisible: Boolean? = null
val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
@@ -459,6 +536,16 @@
containerJob.cancel()
}
+ private fun createAndSetViewModel() {
+ underTest = MobileIconViewModel(
+ SUB_1_ID,
+ interactor,
+ airplaneModeInteractor,
+ constants,
+ testScope.backgroundScope,
+ )
+ }
+
companion object {
private const val SUB_1_ID = 1
@@ -466,10 +553,11 @@
fun defaultSignal(
level: Int = 1,
connected: Boolean = true,
- ): Int {
- return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
+ ): SignalIconModel {
+ return SignalIconModel(level, numberOfLevels = 4, showExclamationMark = !connected)
}
- fun emptySignal(): Int = SignalDrawable.getEmptyState(4)
+ fun emptySignal(): SignalIconModel =
+ SignalIconModel(level = 0, numberOfLevels = 4, showExclamationMark = true)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index 58b50c7..d9268a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -20,11 +20,14 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -46,6 +49,7 @@
private lateinit var underTest: MobileIconsViewModel
private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+ private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var constants: ConnectivityConstants
@@ -57,6 +61,12 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ airplaneModeInteractor =
+ AirplaneModeInteractor(
+ FakeAirplaneModeRepository(),
+ FakeConnectivityRepository(),
+ )
+
val subscriptionIdsFlow =
interactor.filteredSubscriptions
.map { subs -> subs.map { it.subscriptionId } }
@@ -66,6 +76,7 @@
MobileIconsViewModel(
subscriptionIdsFlow,
interactor,
+ airplaneModeInteractor,
logger,
constants,
testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
index 64a93cf..6557754 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_SEEDING_COMPLETED
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.QS_DEFAULT_POSITION
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.QS_PRIORITY_POSITION
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.SecureSettings
import java.util.Optional
@@ -102,6 +103,8 @@
`when`(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
+ `when`(controlsComponent.isEnabled()).thenReturn(true)
+
controller = DeviceControlsControllerImpl(
mContext,
controlsComponent,
@@ -168,4 +171,15 @@
seedCallback.value.accept(SeedResponse(TEST_PKG, true))
verify(callback).onControlsUpdate(QS_DEFAULT_POSITION)
}
+
+ @Test
+ fun testControlsDisabledRemoveFromAutoTracker() {
+ `when`(controlsComponent.isEnabled()).thenReturn(false)
+ val callback: DeviceControlsController.Callback = mock()
+
+ controller.setCallback(callback)
+
+ verify(callback).removeControlsAutoTracker()
+ verify(callback, never()).onControlsUpdate(anyInt())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
index 756397a..74ed7fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
@@ -42,13 +42,35 @@
pixelDensity = 2f,
color = Color.RED,
opacity = 30,
- shouldFillRipple = true,
+ baseRingFadeParams =
+ RippleShader.FadeParams(
+ fadeInStart = 0f,
+ fadeInEnd = 0.3f,
+ fadeOutStart = 0.5f,
+ fadeOutEnd = 1f
+ ),
+ sparkleRingFadeParams =
+ RippleShader.FadeParams(
+ fadeInStart = 0.1f,
+ fadeInEnd = 0.2f,
+ fadeOutStart = 0.7f,
+ fadeOutEnd = 0.9f
+ ),
+ centerFillFadeParams =
+ RippleShader.FadeParams(
+ fadeInStart = 0f,
+ fadeInEnd = 0.1f,
+ fadeOutStart = 0.2f,
+ fadeOutEnd = 0.3f
+ ),
sparkleStrength = 0.3f
)
val rippleAnimation = RippleAnimation(config)
with(rippleAnimation.rippleShader) {
- assertThat(rippleFill).isEqualTo(config.shouldFillRipple)
+ assertThat(baseRingFadeParams).isEqualTo(config.baseRingFadeParams)
+ assertThat(sparkleRingFadeParams).isEqualTo(config.sparkleRingFadeParams)
+ assertThat(centerFillFadeParams).isEqualTo(config.centerFillFadeParams)
assertThat(pixelDensity).isEqualTo(config.pixelDensity)
assertThat(color).isEqualTo(ColorUtils.setAlphaComponent(config.color, config.opacity))
assertThat(sparkleStrength).isEqualTo(config.sparkleStrength)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index f4226bc..3d75967 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -25,6 +25,8 @@
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -105,8 +107,13 @@
}
keyguardRepository = FakeKeyguardRepository()
+ val featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
val keyguardInteractor =
- KeyguardInteractor(repository = keyguardRepository, commandQueue = commandQueue)
+ KeyguardInteractor(
+ repository = keyguardRepository,
+ commandQueue = commandQueue,
+ featureFlags = featureFlags
+ )
// Needs to be run on the main thread
runBlocking(IMMEDIATE) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 9bb52be..8660d09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -117,8 +117,11 @@
SUPERVISED_USER_CREATION_APP_PACKAGE,
)
- featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
userRepository = FakeUserRepository()
keyguardRepository = FakeKeyguardRepository()
telephonyRepository = FakeTelephonyRepository()
@@ -139,6 +142,7 @@
KeyguardInteractor(
repository = keyguardRepository,
commandQueue = commandQueue,
+ featureFlags = featureFlags,
),
manager = manager,
applicationScope = testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index 9a4ca56..8a35cb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -82,7 +82,6 @@
private val userRepository = FakeUserRepository()
private val keyguardRepository = FakeKeyguardRepository()
- private val featureFlags = FakeFeatureFlags()
private lateinit var guestUserInteractor: GuestUserInteractor
private lateinit var refreshUsersScheduler: RefreshUsersScheduler
@@ -233,6 +232,11 @@
}
private fun viewModel(): StatusBarUserChipViewModel {
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
return StatusBarUserChipViewModel(
context = context,
interactor =
@@ -244,9 +248,9 @@
KeyguardInteractor(
repository = keyguardRepository,
commandQueue = commandQueue,
+ featureFlags = featureFlags,
),
- featureFlags =
- FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) },
+ featureFlags = featureFlags,
manager = manager,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 3d4bbdb..1337d1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -134,6 +134,11 @@
resetOrExitSessionReceiver = resetOrExitSessionReceiver,
)
+ val featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ }
underTest =
UserSwitcherViewModel.Factory(
userInteractor =
@@ -145,11 +150,9 @@
KeyguardInteractor(
repository = keyguardRepository,
commandQueue = commandQueue,
+ featureFlags = featureFlags
),
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- },
+ featureFlags = featureFlags,
manager = manager,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
new file mode 100644
index 0000000..5ef62c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.condition;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ConditionalCoreStartableTest extends SysuiTestCase {
+ public static class FakeConditionalCoreStartable extends ConditionalCoreStartable {
+ interface Callback {
+ void onStart();
+ void bootCompleted();
+ }
+
+ private final Callback mCallback;
+
+ public FakeConditionalCoreStartable(Monitor monitor, Set<Condition> conditions,
+ Callback callback) {
+ super(monitor, conditions);
+ mCallback = callback;
+ }
+
+ @Override
+ protected void onStart() {
+ mCallback.onStart();
+ }
+
+ @Override
+ protected void bootCompleted() {
+ mCallback.bootCompleted();
+ }
+ }
+
+
+ final Set<Condition> mConditions = new HashSet<>();
+
+ @Mock
+ Condition mCondition;
+
+ @Mock
+ Monitor mMonitor;
+
+ @Mock
+ FakeConditionalCoreStartable.Callback mCallback;
+
+ @Mock
+ Monitor.Subscription.Token mSubscriptionToken;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mConditions.clear();
+ }
+
+ /**
+ * Verifies that {@link ConditionalCoreStartable#onStart()} is predicated on conditions being
+ * met.
+ */
+ @Test
+ public void testOnStartCallback() {
+ final CoreStartable coreStartable =
+ new FakeConditionalCoreStartable(mMonitor,
+ new HashSet<>(Arrays.asList(mCondition)),
+ mCallback);
+
+ when(mMonitor.addSubscription(any())).thenReturn(mSubscriptionToken);
+ coreStartable.start();
+
+ final ArgumentCaptor<Monitor.Subscription> subscriptionCaptor = ArgumentCaptor.forClass(
+ Monitor.Subscription.class);
+ verify(mMonitor).addSubscription(subscriptionCaptor.capture());
+
+ final Monitor.Subscription subscription = subscriptionCaptor.getValue();
+
+ assertThat(subscription.getConditions()).containsExactly(mCondition);
+
+ verify(mCallback, never()).onStart();
+
+ subscription.getCallback().onConditionsChanged(true);
+
+ verify(mCallback).onStart();
+ verify(mMonitor).removeSubscription(mSubscriptionToken);
+ }
+
+
+ /**
+ * Verifies that {@link ConditionalCoreStartable#bootCompleted()} ()} is predicated on
+ * conditions being met.
+ */
+ @Test
+ public void testBootCompleted() {
+ final CoreStartable coreStartable =
+ new FakeConditionalCoreStartable(mMonitor,
+ new HashSet<>(Arrays.asList(mCondition)),
+ mCallback);
+
+ when(mMonitor.addSubscription(any())).thenReturn(mSubscriptionToken);
+ coreStartable.onBootCompleted();
+
+ final ArgumentCaptor<Monitor.Subscription> subscriptionCaptor = ArgumentCaptor.forClass(
+ Monitor.Subscription.class);
+ verify(mMonitor).addSubscription(subscriptionCaptor.capture());
+
+ final Monitor.Subscription subscription = subscriptionCaptor.getValue();
+
+ assertThat(subscription.getConditions()).containsExactly(mCondition);
+
+ verify(mCallback, never()).bootCompleted();
+
+ subscription.getCallback().onConditionsChanged(true);
+
+ verify(mCallback).bootCompleted();
+ verify(mMonitor).removeSubscription(mSubscriptionToken);
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
index b7a8d2e..9b4f496 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
@@ -18,6 +18,8 @@
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
@@ -25,16 +27,35 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
-/** Collect [flow] in a new [Job] and return a getter for the last collected value. */
+/**
+ * Collect [flow] in a new [Job] and return a getter for the last collected value.
+ * ```
+ * fun myTest() = runTest {
+ * // ...
+ * val actual by collectLastValue(underTest.flow)
+ * assertThat(actual).isEqualTo(expected)
+ * }
+ * ```
+ */
fun <T> TestScope.collectLastValue(
flow: Flow<T>,
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
-): () -> T? {
+): FlowValue<T?> {
var lastValue: T? = null
backgroundScope.launch(context, start) { flow.collect { lastValue = it } }
- return {
+ return FlowValueImpl {
runCurrent()
lastValue
}
}
+
+/** @see collectLastValue */
+interface FlowValue<T> : ReadOnlyProperty<Any?, T?> {
+ operator fun invoke(): T?
+}
+
+private class FlowValueImpl<T>(private val block: () -> T?) : FlowValue<T> {
+ override operator fun invoke(): T? = block()
+ override fun getValue(thisRef: Any?, property: KProperty<*>): T? = invoke()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index 044679d..01dac36 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.repository
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -26,6 +27,14 @@
private val _isFingerprintEnrolled = MutableStateFlow<Boolean>(false)
override val isFingerprintEnrolled: StateFlow<Boolean> = _isFingerprintEnrolled.asStateFlow()
+ private val _isFaceEnrolled = MutableStateFlow(false)
+ override val isFaceEnrolled: Flow<Boolean>
+ get() = _isFaceEnrolled
+
+ private val _isFaceAuthEnabled = MutableStateFlow(false)
+ override val isFaceAuthenticationEnabled: Flow<Boolean>
+ get() = _isFaceAuthEnabled
+
private val _isStrongBiometricAllowed = MutableStateFlow(false)
override val isStrongBiometricAllowed = _isStrongBiometricAllowed.asStateFlow()
@@ -44,4 +53,12 @@
fun setFingerprintEnabledByDevicePolicy(isFingerprintEnabledByDevicePolicy: Boolean) {
_isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
}
+
+ fun setFaceEnrolled(isFaceEnrolled: Boolean) {
+ _isFaceEnrolled.value = isFaceEnrolled
+ }
+
+ fun setIsFaceAuthEnabled(enabled: Boolean) {
+ _isFaceAuthEnabled.value = enabled
+ }
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 8c2c964..677871f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -357,7 +357,6 @@
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.accessibilityTitle = context.getString(R.string.autofill_save_accessibility_title);
params.windowAnimations = R.style.AutofillSaveAnimation;
- params.setTrustedOverlay();
show();
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1428fa8..d78fe86 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12764,8 +12764,10 @@
// restored. This distinction is important for system-process packages that live in the
// system user's process but backup/restore data for non-system users.
// TODO (b/123688746): Handle all system-process packages with singleton check.
- final int instantiatedUserId =
- PLATFORM_PACKAGE_NAME.equals(packageName) ? UserHandle.USER_SYSTEM : targetUserId;
+ boolean useSystemUser = PLATFORM_PACKAGE_NAME.equals(packageName)
+ || getPackageManagerInternal().getSystemUiServiceComponent().getPackageName()
+ .equals(packageName);
+ final int instantiatedUserId = useSystemUser ? UserHandle.USER_SYSTEM : targetUserId;
IPackageManager pm = AppGlobals.getPackageManager();
ApplicationInfo app = null;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fa3a3bf..9f15128 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3507,11 +3507,12 @@
synchronized (VolumeStreamState.class) {
List<Integer> streamsToMute = new ArrayList<>();
for (int stream = 0; stream < mStreamStates.length; stream++) {
- if (streamAlias == mStreamVolumeAlias[stream]) {
+ VolumeStreamState vss = mStreamStates[stream];
+ if (streamAlias == mStreamVolumeAlias[stream] && vss.isMutable()) {
if (!(readCameraSoundForced()
- && (mStreamStates[stream].getStreamType()
+ && (vss.getStreamType()
== AudioSystem.STREAM_SYSTEM_ENFORCED))) {
- boolean changed = mStreamStates[stream].mute(state, /* apply= */ false);
+ boolean changed = vss.mute(state, /* apply= */ false);
if (changed) {
streamsToMute.add(stream);
}
@@ -3830,8 +3831,15 @@
// VOLUME_CHANGED_ACTION intent to see if the current device is the one being modified
final int currDev = getDeviceForStream(vi.getStreamType());
+ final boolean skipping = (currDev == ada.getInternalType());
+
AudioService.sVolumeLogger.log(new DeviceVolumeEvent(vi.getStreamType(), index, ada,
- currDev, callingPackage));
+ currDev, callingPackage, skipping));
+
+ if (skipping) {
+ // setDeviceVolume was called on a device currently being used
+ return;
+ }
// TODO handle unmuting of current audio device
// if a stream is not muted but the VolumeInfo is for muting, set the volume index
@@ -5201,7 +5209,8 @@
if (!shouldMute) {
// unmute
// ring and notifications volume should never be 0 when not silenced
- if (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING) {
+ if (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_RING
+ || mStreamVolumeAlias[streamType] == AudioSystem.STREAM_NOTIFICATION) {
synchronized (VolumeStreamState.class) {
final VolumeStreamState vss = mStreamStates[streamType];
for (int i = 0; i < vss.mIndexMap.size(); i++) {
@@ -5926,6 +5935,8 @@
}
}
+ readVolumeGroupsSettings(userSwitch);
+
// apply new ringer mode before checking volume for alias streams so that streams
// muted by ringer mode have the correct volume
setRingerModeInt(getRingerModeInternal(), false);
@@ -5943,8 +5954,6 @@
}
}
- readVolumeGroupsSettings(userSwitch);
-
if (DEBUG_VOL) {
Log.d(TAG, "Restoring device volume behavior");
}
@@ -8384,9 +8393,10 @@
}
mVolumeGroupState.updateVolumeIndex(groupIndex, device);
// Only propage mute of stream when applicable
- if (mIndexMin == 0 || isCallStream(mStreamType)) {
+ if (isMutable()) {
// For call stream, align mute only when muted, not when index is set to 0
- mVolumeGroupState.mute(forceMuteState ? mIsMuted : groupIndex == 0);
+ mVolumeGroupState.mute(
+ forceMuteState ? mIsMuted : groupIndex == 0 || mIsMuted);
}
}
}
@@ -8435,6 +8445,12 @@
return mIsMuted || mIsMutedInternally;
}
+
+ private boolean isMutable() {
+ return isStreamAffectedByMute(mStreamType)
+ && (mIndexMin == 0 || isCallStream(mStreamType));
+ }
+
/**
* Mute/unmute the stream
* @param state the new mute state
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 6cbe03e..36908dc 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -174,15 +174,17 @@
final String mDeviceAddress;
final String mCaller;
final int mDeviceForStream;
+ final boolean mSkipped;
DeviceVolumeEvent(int streamType, int index, @NonNull AudioDeviceAttributes device,
- int deviceForStream, String callingPackage) {
+ int deviceForStream, String callingPackage, boolean skipped) {
mStream = streamType;
mVolIndex = index;
mDeviceNativeType = "0x" + Integer.toHexString(device.getInternalType());
mDeviceAddress = device.getAddress();
mDeviceForStream = deviceForStream;
mCaller = callingPackage;
+ mSkipped = skipped;
// log metrics
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_VOLUME_EVENT)
.set(MediaMetrics.Property.EVENT, "setDeviceVolume")
@@ -197,14 +199,18 @@
@Override
public String eventToString() {
- return new StringBuilder("setDeviceVolume(stream:")
+ final StringBuilder sb = new StringBuilder("setDeviceVolume(stream:")
.append(AudioSystem.streamToString(mStream))
.append(" index:").append(mVolIndex)
.append(" device:").append(mDeviceNativeType)
.append(" addr:").append(mDeviceAddress)
- .append(") from ").append(mCaller)
- .append(" currDevForStream:Ox").append(Integer.toHexString(mDeviceForStream))
- .toString();
+ .append(") from ").append(mCaller);
+ if (mSkipped) {
+ sb.append(" skipped [device in use]");
+ } else {
+ sb.append(" currDevForStream:Ox").append(Integer.toHexString(mDeviceForStream));
+ }
+ return sb.toString();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 2b7fbfb..0ee25e2 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1322,6 +1322,23 @@
}
/**
+ * @return Default refresh rate while the device has high brightness mode enabled for HDR.
+ */
+ public int getDefaultRefreshRateInHbmHdr() {
+ return mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInHbmHdr);
+ }
+
+ /**
+ * @return Default refresh rate while the device has high brightness mode enabled because of
+ * high lux.
+ */
+ public int getDefaultRefreshRateInHbmSunlight() {
+ return mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInHbmSunlight);
+ }
+
+ /**
* @return Default refresh rate in the higher blocking zone of the associated display
*/
public int getDefaultHighBlockingZoneRefreshRate() {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 045be76..1bbdc20 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -121,6 +121,10 @@
private final DeviceConfigInterface mDeviceConfig;
private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
+ @GuardedBy("mLock")
+ @Nullable
+ private DisplayDeviceConfig mDefaultDisplayDeviceConfig;
+
// A map from the display ID to the collection of votes and their priority. The latter takes
// the form of another map from the priority to the vote itself so that each priority is
// guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
@@ -160,6 +164,7 @@
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
mSettingsObserver = new SettingsObserver(context, handler);
mBrightnessObserver = new BrightnessObserver(context, handler, injector);
+ mDefaultDisplayDeviceConfig = null;
mUdfpsObserver = new UdfpsObserver();
final BallotBox ballotBox = (displayId, priority, vote) -> {
synchronized (mLock) {
@@ -529,11 +534,15 @@
* @param displayDeviceConfig configurations relating to the underlying display device.
*/
public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
- mSettingsObserver.setRefreshRates(displayDeviceConfig,
- /* attemptLoadingFromDeviceConfig= */ true);
- mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
- /* attemptLoadingFromDeviceConfig= */ true);
- mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
+ synchronized (mLock) {
+ mDefaultDisplayDeviceConfig = displayDeviceConfig;
+ mSettingsObserver.setRefreshRates(displayDeviceConfig,
+ /* attemptLoadingFromDeviceConfig= */ true);
+ mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
+ /* attemptLoadingFromDeviceConfig= */ true);
+ mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
+ mHbmObserver.setupHdrRefreshRates(displayDeviceConfig);
+ }
}
/**
@@ -561,7 +570,7 @@
/**
* Sets the display mode switching type.
- * @param newType
+ * @param newType new mode switching type
*/
public void setModeSwitchingType(@DisplayManager.SwitchingType int newType) {
synchronized (mLock) {
@@ -678,6 +687,18 @@
notifyDesiredDisplayModeSpecsChangedLocked();
}
+ @GuardedBy("mLock")
+ private float getMaxRefreshRateLocked(int displayId) {
+ Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
+ float maxRefreshRate = 0f;
+ for (Display.Mode mode : modes) {
+ if (mode.getRefreshRate() > maxRefreshRate) {
+ maxRefreshRate = mode.getRefreshRate();
+ }
+ }
+ return maxRefreshRate;
+ }
+
private void notifyDesiredDisplayModeSpecsChangedLocked() {
if (mDesiredDisplayModeSpecsListener != null
&& !mHandler.hasMessages(MSG_REFRESH_RATE_RANGE_CHANGED)) {
@@ -996,25 +1017,29 @@
// of low priority voters. It votes [0, max(PEAK, MIN)]
public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 7;
+ // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
+ // rate to max value (same as for PRIORITY_UDFPS) on lock screen
+ public static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8;
+
// LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
- public static final int PRIORITY_LOW_POWER_MODE = 8;
+ public static final int PRIORITY_LOW_POWER_MODE = 9;
// PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
// higher priority voters' result is a range, it will fix the rate to a single choice.
// It's used to avoid refresh rate switches in certain conditions which may result in the
// user seeing the display flickering when the switches occur.
- public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 9;
+ public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 10;
// Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
- public static final int PRIORITY_SKIN_TEMPERATURE = 10;
+ public static final int PRIORITY_SKIN_TEMPERATURE = 11;
// The proximity sensor needs the refresh rate to be locked in order to function, so this is
// set to a high priority.
- public static final int PRIORITY_PROXIMITY = 11;
+ public static final int PRIORITY_PROXIMITY = 12;
// The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
// to function, so this needs to be the highest priority of all votes.
- public static final int PRIORITY_UDFPS = 12;
+ public static final int PRIORITY_UDFPS = 13;
// Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
// APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -1117,6 +1142,8 @@
return "PRIORITY_USER_SETTING_MIN_REFRESH_RATE";
case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE:
return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE";
+ case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE:
+ return "PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE";
default:
return Integer.toString(priority);
}
@@ -2329,6 +2356,7 @@
private class UdfpsObserver extends IUdfpsHbmListener.Stub {
private final SparseBooleanArray mLocalHbmEnabled = new SparseBooleanArray();
+ private final SparseBooleanArray mAuthenticationPossible = new SparseBooleanArray();
public void observe() {
StatusBarManagerInternal statusBar =
@@ -2354,25 +2382,28 @@
private void updateHbmStateLocked(int displayId, boolean enabled) {
mLocalHbmEnabled.put(displayId, enabled);
- updateVoteLocked(displayId);
+ updateVoteLocked(displayId, enabled, Vote.PRIORITY_UDFPS);
}
- private void updateVoteLocked(int displayId) {
+ @Override
+ public void onAuthenticationPossible(int displayId, boolean isPossible) {
+ synchronized (mLock) {
+ mAuthenticationPossible.put(displayId, isPossible);
+ updateVoteLocked(displayId, isPossible,
+ Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void updateVoteLocked(int displayId, boolean enabled, int votePriority) {
final Vote vote;
- if (mLocalHbmEnabled.get(displayId)) {
- Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
- float maxRefreshRate = 0f;
- for (Display.Mode mode : modes) {
- if (mode.getRefreshRate() > maxRefreshRate) {
- maxRefreshRate = mode.getRefreshRate();
- }
- }
+ if (enabled) {
+ float maxRefreshRate = DisplayModeDirector.this.getMaxRefreshRateLocked(displayId);
vote = Vote.forRefreshRates(maxRefreshRate, maxRefreshRate);
} else {
vote = null;
}
-
- DisplayModeDirector.this.updateVoteLocked(displayId, Vote.PRIORITY_UDFPS, vote);
+ DisplayModeDirector.this.updateVoteLocked(displayId, votePriority, vote);
}
void dumpLocked(PrintWriter pw) {
@@ -2383,6 +2414,13 @@
final String enabled = mLocalHbmEnabled.valueAt(i) ? "enabled" : "disabled";
pw.println(" Display " + displayId + ": " + enabled);
}
+ pw.println(" mAuthenticationPossible: ");
+ for (int i = 0; i < mAuthenticationPossible.size(); i++) {
+ final int displayId = mAuthenticationPossible.keyAt(i);
+ final String isPossible = mAuthenticationPossible.valueAt(i) ? "possible"
+ : "impossible";
+ pw.println(" Display " + displayId + ": " + isPossible);
+ }
}
}
@@ -2505,7 +2543,7 @@
* HBM that are associated with that display. Restrictions are retrieved from
* DisplayManagerInternal but originate in the display-device-config file.
*/
- public static class HbmObserver implements DisplayManager.DisplayListener {
+ public class HbmObserver implements DisplayManager.DisplayListener {
private final BallotBox mBallotBox;
private final Handler mHandler;
private final SparseIntArray mHbmMode = new SparseIntArray();
@@ -2525,10 +2563,24 @@
mDeviceConfigDisplaySettings = displaySettings;
}
- public void observe() {
- mRefreshRateInHbmSunlight = mDeviceConfigDisplaySettings.getRefreshRateInHbmSunlight();
- mRefreshRateInHbmHdr = mDeviceConfigDisplaySettings.getRefreshRateInHbmHdr();
+ /**
+ * Sets up the refresh rate to be used when HDR is enabled
+ */
+ public void setupHdrRefreshRates(DisplayDeviceConfig displayDeviceConfig) {
+ mRefreshRateInHbmHdr = mDeviceConfigDisplaySettings
+ .getRefreshRateInHbmHdr(displayDeviceConfig);
+ mRefreshRateInHbmSunlight = mDeviceConfigDisplaySettings
+ .getRefreshRateInHbmSunlight(displayDeviceConfig);
+ }
+ /**
+ * Sets up the HDR refresh rates, and starts observing for the changes in the display that
+ * might impact it
+ */
+ public void observe() {
+ synchronized (mLock) {
+ setupHdrRefreshRates(mDefaultDisplayDeviceConfig);
+ }
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mInjector.registerDisplayListener(this, mHandler,
DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
@@ -2760,26 +2812,33 @@
-1);
}
- public int getRefreshRateInHbmSunlight() {
- final int defaultRefreshRateInHbmSunlight =
- mContext.getResources().getInteger(
- R.integer.config_defaultRefreshRateInHbmSunlight);
-
- final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT,
- defaultRefreshRateInHbmSunlight);
-
+ public int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) {
+ int refreshRate =
+ (displayDeviceConfig == null) ? mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInHbmHdr)
+ : displayDeviceConfig.getDefaultRefreshRateInHbmHdr();
+ try {
+ refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR,
+ refreshRate);
+ } catch (NullPointerException e) {
+ // Do Nothing
+ }
return refreshRate;
}
- public int getRefreshRateInHbmHdr() {
- final int defaultRefreshRateInHbmHdr =
- mContext.getResources().getInteger(R.integer.config_defaultRefreshRateInHbmHdr);
-
- final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
- DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR,
- defaultRefreshRateInHbmHdr);
-
+ public int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) {
+ int refreshRate =
+ (displayDeviceConfig == null) ? mContext.getResources()
+ .getInteger(R.integer.config_defaultRefreshRateInHbmSunlight)
+ : displayDeviceConfig.getDefaultRefreshRateInHbmSunlight();
+ try {
+ refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT,
+ refreshRate);
+ } catch (NullPointerException e) {
+ // Do Nothing
+ }
return refreshRate;
}
@@ -2829,14 +2888,18 @@
0).sendToTarget();
}
- final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight();
- mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED,
- refreshRateInHbmSunlight, 0)
+ synchronized (mLock) {
+ final int refreshRateInHbmSunlight =
+ getRefreshRateInHbmSunlight(mDefaultDisplayDeviceConfig);
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED,
+ refreshRateInHbmSunlight, 0)
.sendToTarget();
- final int refreshRateInHbmHdr = getRefreshRateInHbmHdr();
- mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED, refreshRateInHbmHdr, 0)
+ final int refreshRateInHbmHdr =
+ getRefreshRateInHbmHdr(mDefaultDisplayDeviceConfig);
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED, refreshRateInHbmHdr, 0)
.sendToTarget();
+ }
}
private int[] getIntArrayProperty(String prop) {
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index f87a146..f74356d 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -244,8 +244,6 @@
}
mListener.onDreamStopped(dream.mToken);
- } else if (dream.mCanDoze && !mCurrentDream.mCanDoze) {
- mListener.stopDozing(dream.mToken);
}
} finally {
@@ -272,7 +270,7 @@
try {
service.asBinder().linkToDeath(mCurrentDream, 0);
service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze,
- mCurrentDream.mDreamingStartedCallback);
+ mCurrentDream.mIsPreviewMode, mCurrentDream.mDreamingStartedCallback);
} catch (RemoteException ex) {
Slog.e(TAG, "The dream service died unexpectedly.", ex);
stopDream(true /*immediate*/, "attach failed");
@@ -292,7 +290,6 @@
*/
public interface Listener {
void onDreamStopped(Binder token);
- void stopDozing(Binder token);
}
private final class DreamRecord implements DeathRecipient, ServiceConnection {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 148b80e..5b375d7 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -499,12 +499,7 @@
}
synchronized (mLock) {
- if (mCurrentDream == null) {
- return;
- }
-
- final boolean sameDream = mCurrentDream.token == token;
- if ((sameDream && mCurrentDream.isDozing) || (!sameDream && !mCurrentDream.isDozing)) {
+ if (mCurrentDream != null && mCurrentDream.token == token && mCurrentDream.isDozing) {
mCurrentDream.isDozing = false;
mDozeWakeLock.release();
mPowerManagerInternal.setDozeOverrideFromDreamManager(
@@ -665,6 +660,10 @@
Slog.i(TAG, "Entering dreamland.");
+ if (mCurrentDream != null && mCurrentDream.isDozing) {
+ stopDozingInternal(mCurrentDream.token);
+ }
+
mCurrentDream = new DreamRecord(name, userId, isPreviewMode, canDoze);
if (!mCurrentDream.name.equals(mAmbientDisplayComponent)) {
@@ -770,11 +769,6 @@
}
}
}
-
- @Override
- public void stopDozing(Binder token) {
- stopDozingInternal(token);
- }
};
private final ContentObserver mDozeEnabledObserver = new ContentObserver(null) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index faa219e..cbbee5d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4831,6 +4831,13 @@
Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
+ " token: " + token);
return;
+ } else {
+ // Called with current IME's token.
+ if (mMethodMap.get(id) != null
+ && mSettings.getEnabledInputMethodListWithFilterLocked(
+ (info) -> info.getId().equals(id)).isEmpty()) {
+ throw new IllegalStateException("Requested IME is not enabled: " + id);
+ }
}
final long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 1235352..f0aff2a 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -300,6 +300,7 @@
public void deliverOnFlushComplete(int requestCode) throws PendingIntent.CanceledException {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setDontSendToRestrictedApps(true);
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
mPendingIntent.send(mContext, 0, new Intent().putExtra(KEY_FLUSH_COMPLETE, requestCode),
null, null, null, options.toBundle());
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 844c22b..c32a57c 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -94,6 +94,7 @@
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.ApplicationPackageManager;
+import android.app.BroadcastOptions;
import android.app.backup.IBackupManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -641,7 +642,10 @@
fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
PackageManager.installStatusToPublicStatus(returnCode));
try {
- target.sendIntent(context, 0, fillIn, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ target.sendIntent(context, 0, fillIn, null /* onFinished*/, null /* handler */,
+ null /* requiredPermission */, options.toBundle());
} catch (IntentSender.SendIntentException ignored) {
}
}
@@ -2473,15 +2477,19 @@
if (disabledPs != null) {
dataOwnerPkg = disabledPs.getPkg();
}
- try {
- PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
- } catch (PackageManagerException e) {
- String errorMsg = "System app: " + packageName + " cannot be downgraded to"
- + " older than its preloaded version on the system image. "
- + e.getMessage();
- Slog.w(TAG, errorMsg);
- return Pair.create(
- PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
+ if (!Build.IS_DEBUGGABLE && !dataOwnerPkg.isDebuggable()) {
+ // Only restrict non-debuggable builds and non-debuggable version of the app
+ try {
+ PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
+ } catch (PackageManagerException e) {
+ String errorMsg =
+ "System app: " + packageName + " cannot be downgraded to"
+ + " older than its preloaded version on the system"
+ + " image. " + e.getMessage();
+ Slog.w(TAG, errorMsg);
+ return Pair.create(
+ PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 5e0fc3b..e37222f 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1114,12 +1114,16 @@
// Flag for bubble
ActivityOptions options = ActivityOptions.fromBundle(startActivityOptions);
- if (options != null && options.isApplyActivityFlagsForBubbles()) {
- // Flag for bubble to make behaviour match documentLaunchMode=always.
- intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
- intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ if (options != null) {
+ if (options.isApplyActivityFlagsForBubbles()) {
+ // Flag for bubble to make behaviour match documentLaunchMode=always.
+ intents[0].addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+ intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ if (options.isApplyMultipleTaskFlagForShortcut()) {
+ intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
}
-
intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intents[0].setSourceBounds(sourceBounds);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index bb23d89d..02cf433 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -27,6 +27,7 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PackageDeleteObserver;
@@ -1360,7 +1361,10 @@
PackageInstaller.STATUS_PENDING_USER_ACTION);
fillIn.putExtra(Intent.EXTRA_INTENT, intent);
try {
- mTarget.sendIntent(mContext, 0, fillIn, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (SendIntentException ignored) {
}
}
@@ -1385,7 +1389,10 @@
PackageManager.deleteStatusToString(returnCode, msg));
fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
try {
- mTarget.sendIntent(mContext, 0, fillIn, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (SendIntentException ignored) {
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 7c2e3ea..1823de8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -54,6 +54,7 @@
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyEventLogger;
@@ -4274,7 +4275,10 @@
fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
fillIn.putExtra(Intent.EXTRA_INTENT, intent);
try {
- target.sendIntent(context, 0, fillIn, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ target.sendIntent(context, 0, fillIn, null /* onFinished */,
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (IntentSender.SendIntentException ignored) {
}
}
@@ -4315,7 +4319,10 @@
}
}
try {
- target.sendIntent(context, 0, fillIn, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ target.sendIntent(context, 0, fillIn, null /* onFinished */,
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (IntentSender.SendIntentException ignored) {
}
}
@@ -4349,7 +4356,10 @@
intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready");
}
try {
- target.sendIntent(context, 0, intent, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ target.sendIntent(context, 0, intent, null /* onFinished */,
+ null /* handler */, null /* requiredPermission */, options.toBundle());
} catch (IntentSender.SendIntentException ignored) {
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8d2714c..4786037 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -53,6 +53,7 @@
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.ApplicationPackageManager;
+import android.app.BroadcastOptions;
import android.app.IActivityManager;
import android.app.admin.IDevicePolicyManager;
import android.app.admin.SecurityLog;
@@ -4798,7 +4799,11 @@
}
if (pi != null) {
try {
- pi.sendIntent(null, success ? 1 : 0, null, null, null);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+ pi.sendIntent(null, success ? 1 : 0, null /* intent */,
+ null /* onFinished*/, null /* handler */,
+ null /* requiredPermission */, options.toBundle());
} catch (SendIntentException e) {
Slog.w(TAG, e);
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 7ce7f7e..810fa5f 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -247,6 +247,9 @@
private static final String MAX_NUM_COMPONENTS_ERR_MSG =
"Total number of components has exceeded the maximum number: " + MAX_NUM_COMPONENTS;
+ /** The maximum permission name length. */
+ private static final int MAX_PERMISSION_NAME_LENGTH = 512;
+
@IntDef(flag = true, prefix = { "PARSE_" }, value = {
PARSE_CHATTY,
PARSE_COLLECT_CERTIFICATES,
@@ -1275,6 +1278,11 @@
// that may change.
String name = sa.getNonResourceString(
R.styleable.AndroidManifestUsesPermission_name);
+ if (TextUtils.length(name) > MAX_PERMISSION_NAME_LENGTH) {
+ return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "The name in the <uses-permission> is greater than "
+ + MAX_PERMISSION_NAME_LENGTH);
+ }
int maxSdkVersion = 0;
TypedValue val = sa.peekValue(
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f7b9d8b..8aca912 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3906,6 +3906,10 @@
}
}
+ boolean isFinishing() {
+ return finishing;
+ }
+
/**
* This method is to only be called from the client via binder when the activity is destroyed
* AND finished.
@@ -8289,7 +8293,13 @@
}
void recomputeConfiguration() {
- onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+ // We check if the current activity is transparent. In that case we need to
+ // recomputeConfiguration of the first opaque activity beneath, to allow a
+ // proper computation of the new bounds.
+ if (!mLetterboxUiController.applyOnOpaqueActivityBelow(
+ ActivityRecord::recomputeConfiguration)) {
+ onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+ }
}
boolean isInTransition() {
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index fad2dda..b8870a1 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -245,7 +246,22 @@
|| orientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
return false;
}
- return getIgnoreOrientationRequest();
+ return getIgnoreOrientationRequest()
+ && !shouldRespectOrientationRequestDueToPerAppOverride();
+ }
+
+ private boolean shouldRespectOrientationRequestDueToPerAppOverride() {
+ if (mDisplayContent == null) {
+ return false;
+ }
+ ActivityRecord activity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ return activity != null && activity.getTaskFragment() != null
+ // Checking TaskFragment rather than ActivityRecord to ensure that transition
+ // between fullscreen and PiP would work well. Checking TaskFragment rather than
+ // Task to ensure that Activity Embedding is excluded.
+ && activity.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && activity.mLetterboxUiController.isOverrideRespectRequestedOrientationEnabled();
}
boolean getIgnoreOrientationRequest() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index aede04b..b0eeceb 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1679,7 +1679,7 @@
}
// The orientation source may not be the top if it uses SCREEN_ORIENTATION_BEHIND.
final ActivityRecord topCandidate = !r.isVisibleRequested() ? topRunningActivity() : r;
- if (handleTopActivityLaunchingInDifferentOrientation(
+ if (topCandidate != null && handleTopActivityLaunchingInDifferentOrientation(
topCandidate, r, true /* checkOpening */)) {
// Display orientation should be deferred until the top fixed rotation is finished.
return false;
@@ -2116,6 +2116,10 @@
w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
}, true /* traverseTopToBottom */);
mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
+ if (!mDisplayRotation.hasSeamlessRotatingWindow()) {
+ // Make sure DisplayRotation#isRotatingSeamlessly() will return false.
+ mDisplayRotation.cancelSeamlessRotation();
+ }
}
mWmService.mDisplayManagerInternal.performTraversal(transaction);
@@ -3095,27 +3099,6 @@
}
/**
- * Returns true if the input point is within an app window.
- */
- boolean pointWithinAppWindow(int x, int y) {
- final int[] targetWindowType = {-1};
- final PooledConsumer fn = PooledLambda.obtainConsumer((w, nonArg) -> {
- if (targetWindowType[0] != -1) {
- return;
- }
-
- if (w.isOnScreen() && w.isVisible() && w.getFrame().contains(x, y)) {
- targetWindowType[0] = w.mAttrs.type;
- return;
- }
- }, PooledLambda.__(WindowState.class), mTmpRect);
- forAllWindows(fn, true /* traverseTopToBottom */);
- fn.recycle();
- return FIRST_APPLICATION_WINDOW <= targetWindowType[0]
- && targetWindowType[0] <= LAST_APPLICATION_WINDOW;
- }
-
- /**
* Find the task whose outside touch area (for resizing) (x, y) falls within.
* Returns null if the touch doesn't fall into a resizing area.
*/
@@ -4824,7 +4807,7 @@
mInsetsStateController.getImeSourceProvider().checkShowImePostLayout();
mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
- if (!mWmService.mDisplayFrozen) {
+ if (!mWmService.mDisplayFrozen && !mDisplayRotation.isRotatingSeamlessly()) {
mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
mLastHasContent,
mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index e04900c..47bdba3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -250,10 +250,7 @@
}
ActivityRecord topActivity = mDisplayContent.topRunningActivity(
/* considerKeyguardState= */ true);
- if (topActivity == null
- // Checking windowing mode on activity level because we don't want to
- // show toast in case of activity embedding.
- || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ if (!isTreatmentEnabledForActivity(topActivity)) {
return;
}
showToast(R.string.display_rotation_camera_compat_toast_after_rotation);
@@ -309,21 +306,28 @@
}
boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
- return isTreatmentEnabledForDisplay() && isCameraActiveInFullscreen(activity);
+ return isTreatmentEnabledForDisplay()
+ && isCameraActive(activity, /* mustBeFullscreen */ true);
}
+
/**
* Whether camera compat treatment is applicable for the given activity.
*
* <p>Conditions that need to be met:
* <ul>
- * <li>{@link #isCameraActiveForPackage} is {@code true} for the activity.
+ * <li>Camera is active for the package.
* <li>The activity is in fullscreen
* <li>The activity has fixed orientation but not "locked" or "nosensor" one.
* </ul>
*/
boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
- return activity != null && isCameraActiveInFullscreen(activity)
+ return isTreatmentEnabledForActivity(activity, /* mustBeFullscreen */ true);
+ }
+
+ private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity,
+ boolean mustBeFullscreen) {
+ return activity != null && isCameraActive(activity, mustBeFullscreen)
&& activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
// "locked" and "nosensor" values are often used by camera apps that can't
// handle dynamic changes so we shouldn't force rotate them.
@@ -331,8 +335,10 @@
&& activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED;
}
- private boolean isCameraActiveInFullscreen(@NonNull ActivityRecord activity) {
- return !activity.inMultiWindowMode()
+ private boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
+ // Checking windowing mode on activity level because we don't want to
+ // apply treatment in case of activity embedding.
+ return (!mustBeFullscreen || !activity.inMultiWindowMode())
&& mCameraIdPackageBiMap.containsPackageName(activity.packageName)
&& activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
}
@@ -385,7 +391,8 @@
}
// Checking that the whole app is in multi-window mode as we shouldn't show toast
// for the activity embedding case.
- if (topActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+ if (topActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
+ && isTreatmentEnabledForActivity(topActivity, /* mustBeFullscreen */ false)) {
showToast(R.string.display_rotation_camera_compat_toast_in_split_screen);
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 7066a33..5136670 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -20,6 +20,7 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_CAMERA_COMPAT_TREATMENT;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_COMPAT_FAKE_FOCUS;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
import android.annotation.IntDef;
@@ -42,10 +43,6 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxConfiguration" : TAG_ATM;
- @VisibleForTesting
- static final String DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS =
- "enable_compat_fake_focus";
-
/**
* Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
* set-fixed-orientation-letterbox-aspect-ratio or via {@link
@@ -115,12 +112,6 @@
/** Letterboxed app window is aligned to the right side. */
static final int LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM = 2;
- @VisibleForTesting
- static final String PROPERTY_COMPAT_FAKE_FOCUS_OPT_IN = "com.android.COMPAT_FAKE_FOCUS_OPT_IN";
- @VisibleForTesting
- static final String PROPERTY_COMPAT_FAKE_FOCUS_OPT_OUT =
- "com.android.COMPAT_FAKE_FOCUS_OPT_OUT";
-
final Context mContext;
// Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
@@ -317,6 +308,9 @@
mDeviceConfig.updateFlagActiveStatus(
/* isActive */ true,
/* key */ KEY_ALLOW_IGNORE_ORIENTATION_REQUEST);
+ mDeviceConfig.updateFlagActiveStatus(
+ /* isActive */ mIsCompatFakeFocusEnabled,
+ /* key */ KEY_ENABLE_COMPAT_FAKE_FOCUS);
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
mLetterboxConfigurationPersister.start();
@@ -1066,9 +1060,7 @@
/** Whether fake sending focus is enabled for unfocused apps in splitscreen */
boolean isCompatFakeFocusEnabled() {
- return mIsCompatFakeFocusEnabled
- && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, true);
+ return mIsCompatFakeFocusEnabled && mDeviceConfig.getFlag(KEY_ENABLE_COMPAT_FAKE_FOCUS);
}
/**
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
index d004fa6..b364872 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
@@ -45,6 +45,9 @@
"allow_ignore_orientation_request";
private static final boolean DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST = true;
+ static final String KEY_ENABLE_COMPAT_FAKE_FOCUS = "enable_compat_fake_focus";
+ private static final boolean DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS = true;
+
@VisibleForTesting
static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
KEY_ENABLE_CAMERA_COMPAT_TREATMENT,
@@ -52,7 +55,9 @@
KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
DEFAULT_VALUE_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY,
KEY_ALLOW_IGNORE_ORIENTATION_REQUEST,
- DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST
+ DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST,
+ KEY_ENABLE_COMPAT_FAKE_FOCUS,
+ DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS
);
// Whether camera compatibility treatment is enabled.
@@ -72,6 +77,11 @@
private boolean mIsAllowIgnoreOrientationRequest =
DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST;
+ // Whether sending compat fake focus for split screen resumed activities is enabled. This is
+ // needed because some game engines wait to get focus before drawing the content of the app
+ // which isn't guaranteed by default in multi-window modes.
+ private boolean mIsCompatFakeFocusAllowed = DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS;
+
// Set of active device configs that need to be updated in
// DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
@@ -117,6 +127,8 @@
return mIsDisplayRotationImmersiveAppCompatPolicyEnabled;
case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST:
return mIsAllowIgnoreOrientationRequest;
+ case KEY_ENABLE_COMPAT_FAKE_FOCUS:
+ return mIsCompatFakeFocusAllowed;
default:
throw new AssertionError("Unexpected flag name: " + key);
}
@@ -140,6 +152,10 @@
mIsAllowIgnoreOrientationRequest =
getDeviceConfig(key, defaultValue);
break;
+ case KEY_ENABLE_COMPAT_FAKE_FOCUS:
+ mIsCompatFakeFocusAllowed =
+ getDeviceConfig(key, defaultValue);
+ break;
default:
throw new AssertionError("Unexpected flag name: " + key);
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 8d6de58..d2e8ad1 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -25,6 +25,7 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
+import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
@@ -80,6 +81,7 @@
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.TaskDescription;
import android.content.pm.ActivityInfo.ScreenOrientation;
@@ -103,7 +105,10 @@
import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
import java.io.PrintWriter;
+import java.util.Optional;
import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
/** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */
// TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in
@@ -113,6 +118,9 @@
// TODO(b/263021211): Consider renaming to more generic CompatUIController.
final class LetterboxUiController {
+ private static final Predicate<ActivityRecord> FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE =
+ activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing();
+
private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
private static final float UNDEFINED_ASPECT_RATIO = 0f;
@@ -143,6 +151,8 @@
private final boolean mIsOverrideOrientationOnlyForCameraEnabled;
// Corresponds to OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION
private final boolean mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled;
+ // Corresponds to OVERRIDE_RESPECT_REQUESTED_ORIENTATION
+ private final boolean mIsOverrideRespectRequestedOrientationEnabled;
// Corresponds to OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION
private final boolean mIsOverrideCameraCompatDisableForceRotationEnabled;
@@ -272,6 +282,8 @@
isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION);
+ mIsOverrideRespectRequestedOrientationEnabled =
+ isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
mIsOverrideCameraCompatDisableForceRotationEnabled =
isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION);
@@ -418,6 +430,10 @@
mIsRefreshAfterRotationRequested = isRequested;
}
+ boolean isOverrideRespectRequestedOrientationEnabled() {
+ return mIsOverrideRespectRequestedOrientationEnabled;
+ }
+
/**
* Whether should fix display orientation to landscape natural orientation when a task is
* fullscreen and the display is ignoring orientation requests.
@@ -1381,7 +1397,8 @@
return;
}
final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity(
- ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */,
+ FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */,
+ mActivityRecord /* boundary */, false /* includeBoundary */,
true /* traverseTopToBottom */);
if (firstOpaqueActivityBeneath == null) {
// We skip letterboxing if the translucent activity doesn't have any opaque
@@ -1457,6 +1474,32 @@
return mInheritedCompatDisplayInsets;
}
+ /**
+ * In case of translucent activities, it consumes the {@link ActivityRecord} of the first opaque
+ * activity beneath using the given consumer and returns {@code true}.
+ */
+ boolean applyOnOpaqueActivityBelow(@NonNull Consumer<ActivityRecord> consumer) {
+ return findOpaqueNotFinishingActivityBelow()
+ .map(activityRecord -> {
+ consumer.accept(activityRecord);
+ return true;
+ }).orElse(false);
+ }
+
+ /**
+ * @return The first not finishing opaque activity beneath the current translucent activity
+ * if it exists and the strategy is enabled.
+ */
+ private Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() {
+ if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(mActivityRecord.getTask().getActivity(
+ FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */,
+ mActivityRecord /* boundary */, false /* includeBoundary */,
+ true /* traverseTopToBottom */));
+ }
+
private void inheritConfiguration(ActivityRecord firstOpaque) {
// To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities
// which are not already providing one (e.g. permission dialogs) and presumably also
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 1fc061b..67fd557 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -33,6 +33,8 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.Process.SYSTEM_UID;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
@@ -215,10 +217,16 @@
int y = (int) ev.getY();
mService.mH.post(PooledLambda.obtainRunnable((nonArg) -> {
synchronized (mService.mGlobalLock) {
- // Unfreeze the task list once we touch down in a task
final RootWindowContainer rac = mService.mRootWindowContainer;
final DisplayContent dc = rac.getDisplayContent(displayId).mDisplayContent;
- if (dc.pointWithinAppWindow(x, y)) {
+ final WindowState win = dc.getTouchableWinAtPointLocked((float) x, (float) y);
+ if (win == null) {
+ return;
+ }
+ // Unfreeze the task list once we touch down in a task
+ final boolean isAppWindowTouch = FIRST_APPLICATION_WINDOW <= win.mAttrs.type
+ && win.mAttrs.type <= LAST_APPLICATION_WINDOW;
+ if (isAppWindowTouch) {
final Task stack = mService.getTopDisplayFocusedRootTask();
final Task topTask = stack != null ? stack.getTopMostTask() : null;
resetFreezeTaskListReordering(topTask);
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index f3713eb..6b3c533 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -53,6 +53,8 @@
}
}
+ private final DisplayInfo mDisplayInfo;
+ private final Mode mDefaultMode;
private final Mode mLowRefreshRateMode;
private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate();
private final HighRefreshRateDenylist mHighRefreshRateDenylist;
@@ -83,7 +85,9 @@
RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
HighRefreshRateDenylist denylist) {
- mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
+ mDisplayInfo = displayInfo;
+ mDefaultMode = displayInfo.getDefaultMode();
+ mLowRefreshRateMode = findLowRefreshRateMode(displayInfo, mDefaultMode);
mHighRefreshRateDenylist = denylist;
mWmService = wmService;
}
@@ -92,10 +96,9 @@
* Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
* default mode.
*/
- private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
- Mode mode = displayInfo.getDefaultMode();
+ private Mode findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode) {
float[] refreshRates = displayInfo.getDefaultRefreshRates();
- float bestRefreshRate = mode.getRefreshRate();
+ float bestRefreshRate = defaultMode.getRefreshRate();
mMinSupportedRefreshRate = bestRefreshRate;
mMaxSupportedRefreshRate = bestRefreshRate;
for (int i = refreshRates.length - 1; i >= 0; i--) {
@@ -121,13 +124,39 @@
}
int getPreferredModeId(WindowState w) {
- // If app is animating, it's not able to control refresh rate because we want the animation
- // to run in default refresh rate.
- if (w.isAnimating(TRANSITION | PARENTS)) {
+ final int preferredDisplayModeId = w.mAttrs.preferredDisplayModeId;
+ if (preferredDisplayModeId <= 0) {
+ // Unspecified, use default mode.
return 0;
}
- return w.mAttrs.preferredDisplayModeId;
+ // If app is animating, it's not able to control refresh rate because we want the animation
+ // to run in default refresh rate. But if the display size of default mode is different
+ // from the using preferred mode, then still keep the preferred mode to avoid disturbing
+ // the animation.
+ if (w.isAnimating(TRANSITION | PARENTS)) {
+ Display.Mode preferredMode = null;
+ for (Display.Mode mode : mDisplayInfo.supportedModes) {
+ if (preferredDisplayModeId == mode.getModeId()) {
+ preferredMode = mode;
+ break;
+ }
+ }
+ if (preferredMode != null) {
+ final int pW = preferredMode.getPhysicalWidth();
+ final int pH = preferredMode.getPhysicalHeight();
+ if ((pW != mDefaultMode.getPhysicalWidth()
+ || pH != mDefaultMode.getPhysicalHeight())
+ && pW == mDisplayInfo.getNaturalWidth()
+ && pH == mDisplayInfo.getNaturalHeight()) {
+ // Prefer not to change display size when animating.
+ return preferredDisplayModeId;
+ }
+ }
+ return 0;
+ }
+
+ return preferredDisplayModeId;
}
/**
@@ -165,12 +194,9 @@
// of that mode id.
final int preferredModeId = w.mAttrs.preferredDisplayModeId;
if (preferredModeId > 0) {
- DisplayInfo info = w.getDisplayInfo();
- if (info != null) {
- for (Display.Mode mode : info.supportedModes) {
- if (preferredModeId == mode.getModeId()) {
- return mode.getRefreshRate();
- }
+ for (Display.Mode mode : mDisplayInfo.supportedModes) {
+ if (preferredModeId == mode.getModeId()) {
+ return mode.getRefreshRate();
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 45dacbb..03b3ef0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5679,14 +5679,6 @@
&& imeTarget.compareTo(this) <= 0;
return inTokenWithAndAboveImeTarget;
}
-
- // The condition is for the system dialog not belonging to any Activity.
- // (^FLAG_NOT_FOCUSABLE & FLAG_ALT_FOCUSABLE_IM) means the dialog is still focusable but
- // should be placed above the IME window.
- if ((mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM))
- == FLAG_ALT_FOCUSABLE_IM && isTrustedOverlay() && canAddInternalSystemWindow()) {
- return true;
- }
return false;
}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index a49577b..1aeb0ca 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -2816,6 +2816,12 @@
binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mountId, int newStatus) {
if (!isValid()) {
+ if (newStatus == IDataLoaderStatusListener::DATA_LOADER_BOUND) {
+ // Async "bound" came to already destroyed stub.
+ // Unbind immediately to avoid invalid stub sitting around in DataLoaderManagerService.
+ mService.mDataLoaderManager->unbindFromDataLoader(mountId);
+ return binder::Status::ok();
+ }
return binder::Status::
fromServiceSpecificError(-EINVAL, "onStatusChange came to invalid DataLoaderStub");
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 80de823..0f2176f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -205,6 +205,7 @@
private static final String TAG = AlarmManagerServiceTest.class.getSimpleName();
private static final int SYSTEM_UI_UID = 12345;
private static final int TEST_CALLING_USER = UserHandle.getUserId(TEST_CALLING_UID);
+ private static final int TEST_CALLING_UID_2 = TEST_CALLING_UID + 1;
private long mAppStandbyWindow;
private long mAllowWhileIdleWindow;
@@ -3375,10 +3376,40 @@
final int type = ((i & 1) == 0) ? ELAPSED_REALTIME : ELAPSED_REALTIME_WAKEUP;
setTestAlarm(type, mNowElapsedTest + i, getNewMockPendingIntent());
}
+ for (int i = 0; i < 4; i++) {
+ final int type = ((i & 1) == 0) ? ELAPSED_REALTIME : ELAPSED_REALTIME_WAKEUP;
+ setTestAlarm(
+ type,
+ mNowElapsedTest + i,
+ getNewMockPendingIntent(),
+ 0,
+ FLAG_STANDALONE,
+ TEST_CALLING_UID_2);
+ }
mNowElapsedTest += 100;
mTestTimer.expire();
- verify(() -> MetricsHelper.pushAlarmBatchDelivered(10, 5));
+ final ArgumentCaptor<int[]> uidsCaptor = ArgumentCaptor.forClass(int[].class);
+ final ArgumentCaptor<int[]> alarmsPerUidCaptor = ArgumentCaptor.forClass(int[].class);
+ final ArgumentCaptor<int[]> wakeupAlarmsPerUidCaptor = ArgumentCaptor.forClass(int[].class);
+
+ verify(() -> MetricsHelper.pushAlarmBatchDelivered(
+ eq(14),
+ eq(7),
+ uidsCaptor.capture(),
+ alarmsPerUidCaptor.capture(),
+ wakeupAlarmsPerUidCaptor.capture()));
+ assertEquals(2, uidsCaptor.getValue().length);
+ assertEquals(2, alarmsPerUidCaptor.getValue().length);
+ assertEquals(2, wakeupAlarmsPerUidCaptor.getValue().length);
+ final int uid1Idx = uidsCaptor.getValue()[0] == TEST_CALLING_UID ? 0 : 1;
+ final int uid2Idx = 1 - uid1Idx;
+ assertEquals(TEST_CALLING_UID, uidsCaptor.getValue()[uid1Idx]);
+ assertEquals(TEST_CALLING_UID_2, uidsCaptor.getValue()[uid2Idx]);
+ assertEquals(10, alarmsPerUidCaptor.getValue()[uid1Idx]);
+ assertEquals(5, wakeupAlarmsPerUidCaptor.getValue()[uid1Idx]);
+ assertEquals(4, alarmsPerUidCaptor.getValue()[uid2Idx]);
+ assertEquals(2, wakeupAlarmsPerUidCaptor.getValue()[uid2Idx]);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 77e5d1d..d1fbc1e 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -53,6 +53,8 @@
private static final int DEFAULT_REFRESH_RATE = 120;
private static final int DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE = 55;
private static final int DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE = 95;
+ private static final int DEFAULT_REFRESH_RATE_IN_HBM_HDR = 90;
+ private static final int DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT = 100;
private static final int[] LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{10, 30};
private static final int[] LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{1, 21};
private static final int[] HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{160};
@@ -240,6 +242,10 @@
DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
assertEquals(mDisplayDeviceConfig.getDefaultPeakRefreshRate(), DEFAULT_PEAK_REFRESH_RATE);
assertEquals(mDisplayDeviceConfig.getDefaultRefreshRate(), DEFAULT_REFRESH_RATE);
+ assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight(),
+ DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
+ assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr(),
+ DEFAULT_REFRESH_RATE_IN_HBM_HDR);
assertArrayEquals(mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(),
LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
assertArrayEquals(mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(),
@@ -578,6 +584,12 @@
when(mResources.getIntArray(
R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
.thenReturn(HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
+ when(mResources.getInteger(
+ R.integer.config_defaultRefreshRateInHbmHdr))
+ .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_HDR);
+ when(mResources.getInteger(
+ R.integer.config_defaultRefreshRateInHbmSunlight))
+ .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index af39dd4..ff37564 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -1873,6 +1873,10 @@
.thenReturn(65);
when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
.thenReturn(85);
+ when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
+ .thenReturn(95);
+ when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
+ .thenReturn(100);
when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
.thenReturn(new int[]{5});
when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
@@ -1883,8 +1887,21 @@
when(
resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
.thenReturn(new int[]{7000});
+ when(resources.getInteger(
+ com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
+ .thenReturn(3);
+ ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
+ doAnswer((Answer<Void>) invocation -> {
+ valueArgumentCaptor.getValue().type = 4;
+ valueArgumentCaptor.getValue().data = 13;
+ return null;
+ }).when(resources).getValue(eq(com.android.internal.R.dimen
+ .config_displayWhiteBalanceBrightnessFilterIntercept),
+ valueArgumentCaptor.capture(), eq(true));
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+ SensorManager sensorManager = createMockSensorManager(createLightSensor());
+ director.start(sensorManager);
// We don't expect any interaction with DeviceConfig when the director is initialized
// because we explicitly avoid doing this as this can lead to a latency spike in the
// startup of DisplayManagerService
@@ -1894,6 +1911,8 @@
0.0);
assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 65);
assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 85);
+ assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 95);
+ assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 100);
assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
new int[]{250});
assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
@@ -1903,6 +1922,7 @@
assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
new int[]{10});
+
// Notify that the default display is updated, such that DisplayDeviceConfig has new values
DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50);
@@ -1913,6 +1933,8 @@
when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
+ when(displayDeviceConfig.getDefaultRefreshRateInHbmHdr()).thenReturn(65);
+ when(displayDeviceConfig.getDefaultRefreshRateInHbmSunlight()).thenReturn(75);
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
@@ -1928,6 +1950,8 @@
new int[]{25});
assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
new int[]{30});
+ assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65);
+ assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75);
// Notify that the default display is updated, such that DeviceConfig has new values
FakeDeviceConfig config = mInjector.getDeviceConfig();
@@ -1938,7 +1962,8 @@
config.setLowDisplayBrightnessThresholds(new int[]{10});
config.setHighDisplayBrightnessThresholds(new int[]{255});
config.setHighAmbientBrightnessThresholds(new int[]{8000});
-
+ config.setRefreshRateInHbmHdr(70);
+ config.setRefreshRateInHbmSunlight(80);
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0);
@@ -1954,6 +1979,8 @@
new int[]{10});
assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
new int[]{20});
+ assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 70);
+ assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 80);
}
@Test
@@ -2011,6 +2038,74 @@
eq(lightSensorTwo), anyInt(), any(Handler.class));
}
+ @Test
+ public void testAuthenticationPossibleSetsPhysicalRateRangesToMax() throws RemoteException {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+ // don't call director.start(createMockSensorManager());
+ // DisplayObserver will reset mSupportedModesByDisplay
+ director.onBootCompleted();
+ ArgumentCaptor<IUdfpsHbmListener> captor =
+ ArgumentCaptor.forClass(IUdfpsHbmListener.class);
+ verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
+
+ captor.getValue().onAuthenticationPossible(DISPLAY_ID, true);
+
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
+ assertThat(vote.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
+ assertThat(vote.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
+ }
+
+ @Test
+ public void testAuthenticationPossibleUnsetsVote() throws RemoteException {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+ director.start(createMockSensorManager());
+ director.onBootCompleted();
+ ArgumentCaptor<IUdfpsHbmListener> captor =
+ ArgumentCaptor.forClass(IUdfpsHbmListener.class);
+ verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
+ captor.getValue().onAuthenticationPossible(DISPLAY_ID, true);
+ captor.getValue().onAuthenticationPossible(DISPLAY_ID, false);
+
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE);
+ assertNull(vote);
+ }
+
+ @Test
+ public void testUdfpsRequestSetsPhysicalRateRangesToMax() throws RemoteException {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+ // don't call director.start(createMockSensorManager());
+ // DisplayObserver will reset mSupportedModesByDisplay
+ director.onBootCompleted();
+ ArgumentCaptor<IUdfpsHbmListener> captor =
+ ArgumentCaptor.forClass(IUdfpsHbmListener.class);
+ verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
+
+ captor.getValue().onHbmEnabled(DISPLAY_ID);
+
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
+ assertThat(vote.refreshRateRange.min).isWithin(FLOAT_TOLERANCE).of(90);
+ assertThat(vote.refreshRateRange.max).isWithin(FLOAT_TOLERANCE).of(90);
+ }
+
+ @Test
+ public void testUdfpsRequestUnsetsUnsetsVote() throws RemoteException {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+ director.start(createMockSensorManager());
+ director.onBootCompleted();
+ ArgumentCaptor<IUdfpsHbmListener> captor =
+ ArgumentCaptor.forClass(IUdfpsHbmListener.class);
+ verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
+ captor.getValue().onHbmEnabled(DISPLAY_ID);
+ captor.getValue().onHbmEnabled(DISPLAY_ID);
+
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
+ assertNull(vote);
+ }
+
private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
}
diff --git a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
index 303a370..1ef1197 100644
--- a/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java
@@ -99,7 +99,24 @@
mLooper.dispatchAll();
// Verify that dream service is called to attach.
- verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/, any());
+ verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/,
+ eq(false) /*preview*/, any());
+ }
+
+ @Test
+ public void startDream_attachOnServiceConnectedInPreviewMode() throws RemoteException {
+ // Call dream controller to start dreaming.
+ mDreamController.startDream(mToken, mDreamName, true /*isPreview*/, false /*doze*/,
+ 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
+
+ // Mock service connected.
+ final ServiceConnection serviceConnection = captureServiceConnection();
+ serviceConnection.onServiceConnected(mDreamName, mIBinder);
+ mLooper.dispatchAll();
+
+ // Verify that dream service is called to attach.
+ verify(mIDreamService).attach(eq(mToken), eq(false) /*doze*/,
+ eq(true) /*preview*/, any());
}
@Test
@@ -129,7 +146,7 @@
// Mock second dream started.
verify(newDreamService).attach(eq(newToken), eq(false) /*doze*/,
- mRemoteCallbackCaptor.capture());
+ eq(false) /*preview*/, mRemoteCallbackCaptor.capture());
mRemoteCallbackCaptor.getValue().sendResult(null /*data*/);
mLooper.dispatchAll();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 4954e89..c2b3783 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -147,6 +147,20 @@
}
@Test
+ public void testOpenedCameraInSplitScreen_orientationNotFixed_doNotShowToast() {
+ configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
+ spyOn(mTask);
+ spyOn(mDisplayRotationCompatPolicy);
+ doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
+ doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mTask).getWindowingMode();
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ verify(mDisplayRotationCompatPolicy, never()).showToast(
+ R.string.display_rotation_camera_compat_toast_in_split_screen);
+ }
+
+ @Test
public void testOnScreenRotationAnimationFinished_treatmentNotEnabled_doNotShowToast() {
when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
/* checkDeviceConfig */ anyBoolean()))
@@ -172,7 +186,7 @@
@Test
public void testOnScreenRotationAnimationFinished_notFullscreen_doNotShowToast() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- doReturn(WINDOWING_MODE_MULTI_WINDOW).when(mActivity).getWindowingMode();
+ doReturn(true).when(mActivity).inMultiWindowMode();
spyOn(mDisplayRotationCompatPolicy);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
@@ -184,6 +198,18 @@
}
@Test
+ public void testOnScreenRotationAnimationFinished_orientationNotFixed_doNotShowToast() {
+ configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ spyOn(mDisplayRotationCompatPolicy);
+
+ mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+
+ verify(mDisplayRotationCompatPolicy, never()).showToast(
+ R.string.display_rotation_camera_compat_toast_after_rotation);
+ }
+
+ @Test
public void testOnScreenRotationAnimationFinished_showToast() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index 12b7c9d..e1fc0cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -18,7 +18,6 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.server.wm.LetterboxConfiguration.DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
@@ -26,8 +25,6 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -37,7 +34,6 @@
import android.content.Context;
import android.platform.test.annotations.Presubmit;
-import android.provider.DeviceConfig;
import androidx.test.filters.SmallTest;
@@ -233,34 +229,6 @@
LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
}
- @Test
- public void testIsCompatFakeFocusEnabledOnDevice() {
- boolean wasFakeFocusEnabled = DeviceConfig
- .getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, false);
-
- // Set runtime flag to true and build time flag to false
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
- mLetterboxConfiguration.setIsCompatFakeFocusEnabled(false);
- assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabled());
-
- // Set runtime flag to false and build time flag to true
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "false", false);
- mLetterboxConfiguration.setIsCompatFakeFocusEnabled(true);
- assertFalse(mLetterboxConfiguration.isCompatFakeFocusEnabled());
-
- // Set runtime flag to true so that both are enabled
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, "true", false);
- assertTrue(mLetterboxConfiguration.isCompatFakeFocusEnabled());
-
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- DEVICE_CONFIG_KEY_ENABLE_COMPAT_FAKE_FOCUS, Boolean.toString(wasFakeFocusEnabled),
- false);
- }
-
private void assertForHorizontalMove(int from, int expected, int expectedTime,
boolean halfFoldPose, BiConsumer<LetterboxConfiguration, Boolean> move) {
// We are in the current position
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 9d2eb26..63797778 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -21,7 +21,9 @@
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.os.Parcel;
@@ -258,6 +260,14 @@
assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+
+ // If there will be display size change when switching from preferred mode to default mode,
+ // then keep the current preferred mode during animating.
+ mDisplayInfo = spy(mDisplayInfo);
+ final Mode defaultMode = new Mode(4321 /* width */, 1234 /* height */, LOW_REFRESH_RATE);
+ doReturn(defaultMode).when(mDisplayInfo).getDefaultMode();
+ mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
+ assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 571dd69..c426913 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -255,6 +255,51 @@
}
@Test
+ public void testCheckOpaqueIsLetterboxedWhenStrategyIsApplied() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2000, 1000);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ // Translucent Activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .build();
+ doReturn(false).when(translucentActivity).fillsParent();
+ spyOn(mActivity);
+ mTask.addChild(translucentActivity);
+ verify(mActivity).isFinishing();
+ }
+
+ @Test
+ public void testTranslucentActivitiesWhenUnfolding() {
+ mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
+ setUpDisplaySizeWithApp(2800, 1400);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
+ 1.0f /*letterboxVerticalPositionMultiplier*/);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+ // We launch a transparent activity
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setLaunchedFromUid(mActivity.getUid())
+ .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
+ .build();
+ doReturn(false).when(translucentActivity).fillsParent();
+ mTask.addChild(translucentActivity);
+
+ mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ spyOn(mActivity);
+
+ // Halffold
+ setFoldablePosture(translucentActivity, true /* isHalfFolded */, false /* isTabletop */);
+ verify(mActivity).recomputeConfiguration();
+ clearInvocations(mActivity);
+
+ // Unfold
+ setFoldablePosture(translucentActivity, false /* isHalfFolded */, false /* isTabletop */);
+ verify(mActivity).recomputeConfiguration();
+ }
+
+ @Test
public void testRestartProcessIfVisible() {
setUpDisplaySizeWithApp(1000, 2500);
doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
@@ -1876,6 +1921,43 @@
}
@Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION})
+ public void testOverrideRespectRequestedOrientationIsEnabled_orientationIsRespected() {
+ // Set up a display in landscape
+ setUpDisplaySizeWithApp(2800, 1400);
+
+ final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
+ RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ // Display should be rotated.
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, activity.mDisplayContent.getOrientation());
+
+ // No size compat mode
+ assertFalse(activity.inSizeCompatMode());
+ }
+
+ @Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION})
+ public void testOverrideRespectRequestedOrientationIsEnabled_multiWindow_orientationIgnored() {
+ // Set up a display in landscape
+ setUpDisplaySizeWithApp(2800, 1400);
+
+ final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
+ RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
+ TaskFragment taskFragment = activity.getTaskFragment();
+ spyOn(taskFragment);
+ activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ doReturn(WINDOWING_MODE_MULTI_WINDOW).when(taskFragment).getWindowingMode();
+
+ // Display should not be rotated.
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.mDisplayContent.getOrientation());
+
+ // No size compat mode
+ assertFalse(activity.inSizeCompatMode());
+ }
+
+ @Test
public void testSplitAspectRatioForUnresizableLandscapeApps() {
// Set up a display in portrait and ignoring orientation request.
int screenWidth = 1400;
@@ -3286,14 +3368,20 @@
}
- private void setFoldablePosture(boolean isHalfFolded, boolean isTabletop) {
- final DisplayRotation r = mActivity.mDisplayContent.getDisplayRotation();
+ private void setFoldablePosture(ActivityRecord activity, boolean isHalfFolded,
+ boolean isTabletop) {
+ final DisplayRotation r = activity.mDisplayContent.getDisplayRotation();
doReturn(isHalfFolded).when(r).isDisplaySeparatingHinge();
doReturn(false).when(r).isDeviceInPosture(any(DeviceState.class), anyBoolean());
if (isHalfFolded) {
- doReturn(true).when(r).isDeviceInPosture(DeviceState.HALF_FOLDED, isTabletop);
+ doReturn(true).when(r)
+ .isDeviceInPosture(DeviceState.HALF_FOLDED, isTabletop);
}
- mActivity.recomputeConfiguration();
+ activity.recomputeConfiguration();
+ }
+
+ private void setFoldablePosture(boolean isHalfFolded, boolean isTabletop) {
+ setFoldablePosture(mActivity, isHalfFolded, isTabletop);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 514aec1..219f441 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -42,7 +42,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -975,19 +974,6 @@
assertFalse(sameTokenWindow.needsRelativeLayeringToIme());
}
- @UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
- @Test
- public void testNeedsRelativeLayeringToIme_systemDialog() {
- WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY,
- mDisplayContent,
- "SystemDialog", true);
- mDisplayContent.setImeLayeringTarget(mAppWindow);
- mAppWindow.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- makeWindowVisible(mImeWindow);
- systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
- assertTrue(systemDialogWindow.needsRelativeLayeringToIme());
- }
-
@Test
public void testSetFreezeInsetsState() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 7959d82..77fca45 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -22,7 +22,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -32,7 +31,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
-import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
@@ -545,28 +543,4 @@
assertZOrderGreaterThan(mTransaction, popupWindow.getSurfaceControl(),
mDisplayContent.getImeContainer().getSurfaceControl());
}
-
- @Test
- public void testSystemDialogWindow_expectHigherThanIme_inMultiWindow() {
- // Simulate the app window is in multi windowing mode and being IME target
- mAppWindow.getConfiguration().windowConfiguration.setWindowingMode(
- WINDOWING_MODE_MULTI_WINDOW);
- mDisplayContent.setImeLayeringTarget(mAppWindow);
- mDisplayContent.setImeInputTarget(mAppWindow);
- makeWindowVisible(mImeWindow);
-
- // Create a popupWindow
- final WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY,
- mDisplayContent, "SystemDialog", true);
- systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
- spyOn(systemDialogWindow);
-
- mDisplayContent.assignChildLayers(mTransaction);
-
- // Verify the surface layer of the popupWindow should higher than IME
- verify(systemDialogWindow).needsRelativeLayeringToIme();
- assertThat(systemDialogWindow.needsRelativeLayeringToIme()).isTrue();
- assertZOrderGreaterThan(mTransaction, systemDialogWindow.getSurfaceControl(),
- mDisplayContent.getImeContainer().getSurfaceControl());
- }
}
diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java
index fd92c65..c6f18fd 100644
--- a/tests/testables/src/android/testing/TestableSettingsProvider.java
+++ b/tests/testables/src/android/testing/TestableSettingsProvider.java
@@ -49,14 +49,15 @@
}
void clearValuesAndCheck(Context context) {
- int userId = UserHandle.myUserId();
- mValues.put(key("global", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY);
- mValues.put(key("secure", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY);
- mValues.put(key("system", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY);
-
+ // Ensure we swapped over to use TestableSettingsProvider
Settings.Global.clearProviderForTest();
Settings.Secure.clearProviderForTest();
Settings.System.clearProviderForTest();
+
+ // putString will eventually invoking the mocked call() method and update mValues
+ Settings.Global.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY);
+ Settings.Secure.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY);
+ Settings.System.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY);
// Verify that if any test is using TestableContext, they all have the correct settings
// provider.
assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,