Merge "Revert "Move wallpaper local color extraction to background"" 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 8c00c6a..c0e89d2 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3452,7 +3452,7 @@
public class WindowOrganizer {
ctor public WindowOrganizer();
- method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public void applyTransaction(@NonNull android.window.WindowContainerTransaction);
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 22a1a47..d6f44e6 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -325,6 +325,13 @@
"android:activity.applyMultipleTaskFlagForShortcut";
/**
+ * Indicates to apply {@link Intent#FLAG_ACTIVITY_NO_USER_ACTION} to the launching shortcut.
+ * @hide
+ */
+ private static final String KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT =
+ "android:activity.applyNoUserActionFlagForShortcut";
+
+ /**
* For Activity transitions, the calling Activity's TransitionListener used to
* notify the called Activity when the shared element and the exit transitions
* complete.
@@ -457,6 +464,7 @@
private boolean mDisallowEnterPictureInPictureWhileLaunching;
private boolean mApplyActivityFlagsForBubbles;
private boolean mApplyMultipleTaskFlagForShortcut;
+ private boolean mApplyNoUserActionFlagForShortcut;
private boolean mTaskAlwaysOnTop;
private boolean mTaskOverlay;
private boolean mTaskOverlayCanResume;
@@ -1256,6 +1264,8 @@
KEY_APPLY_ACTIVITY_FLAGS_FOR_BUBBLES, false);
mApplyMultipleTaskFlagForShortcut = opts.getBoolean(
KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT, false);
+ mApplyNoUserActionFlagForShortcut = opts.getBoolean(
+ KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT, false);
if (opts.containsKey(KEY_ANIM_SPECS)) {
Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS);
mAnimSpecs = new AppTransitionAnimationSpec[specs.length];
@@ -1835,6 +1845,16 @@
return mApplyMultipleTaskFlagForShortcut;
}
+ /** @hide */
+ public void setApplyNoUserActionFlagForShortcut(boolean apply) {
+ mApplyNoUserActionFlagForShortcut = apply;
+ }
+
+ /** @hide */
+ public boolean isApplyNoUserActionFlagForShortcut() {
+ return mApplyNoUserActionFlagForShortcut;
+ }
+
/**
* 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
@@ -2167,6 +2187,9 @@
b.putBoolean(KEY_APPLY_MULTIPLE_TASK_FLAG_FOR_SHORTCUT,
mApplyMultipleTaskFlagForShortcut);
}
+ if (mApplyNoUserActionFlagForShortcut) {
+ b.putBoolean(KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT, true);
+ }
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/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index bab2061..097f622 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6638,6 +6638,13 @@
public static final int KEYGUARD_DISABLE_IRIS = 1 << 8;
/**
+ * Disable all keyguard shortcuts.
+ *
+ * @hide
+ */
+ public static final int KEYGUARD_DISABLE_SHORTCUTS_ALL = 1 << 9;
+
+ /**
* NOTE: Please remember to update the DevicePolicyManagerTest's testKeyguardDisabledFeatures
* CTS test when adding to the list above.
*/
@@ -6680,7 +6687,8 @@
*/
public static final int ORG_OWNED_PROFILE_KEYGUARD_FEATURES_PARENT_ONLY =
DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
- | DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
+ | DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS
+ | DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL;
/**
* Keyguard features that when set on a normal or organization-owned managed profile, have
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/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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c8e1131..953f17a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1391,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);
@@ -8630,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/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/TaskFragmentAnimationParams.java b/core/java/android/window/TaskFragmentAnimationParams.java
index 12ad914..c8f6327 100644
--- a/core/java/android/window/TaskFragmentAnimationParams.java
+++ b/core/java/android/window/TaskFragmentAnimationParams.java
@@ -33,6 +33,13 @@
public static final TaskFragmentAnimationParams DEFAULT =
new TaskFragmentAnimationParams.Builder().build();
+ /**
+ * The default value for animation background color, which means to use the theme window
+ * background color.
+ */
+ @ColorInt
+ public static final int DEFAULT_ANIMATION_BACKGROUND_COLOR = 0;
+
@ColorInt
private final int mAnimationBackgroundColor;
@@ -104,12 +111,13 @@
public static final class Builder {
@ColorInt
- private int mAnimationBackgroundColor = 0;
+ private int mAnimationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR;
/**
* Sets the {@link ColorInt} to use for the background during the animation with this
* TaskFragment if the animation requires a background. The default value is
- * {@code 0}, which is to use the theme window background.
+ * {@link #DEFAULT_ANIMATION_BACKGROUND_COLOR}, which is to use the theme window background
+ * color.
*
* @param color a packed color int, {@code AARRGGBB}, for the animation background color.
* @return this {@link Builder}.
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index bfa3447..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;
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 2a80d02..740fbac 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -61,9 +61,7 @@
* Apply multiple WindowContainer operations at once.
*
* Note that using this API requires the caller to hold
- * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}, unless the caller is using
- * {@link TaskFragmentOrganizer}, in which case it is allowed to change TaskFragment that is
- * created by itself.
+ * {@link android.Manifest.permission#MANAGE_ACTIVITY_TASKS}.
*
* @param t The transaction to apply.
* @param callback This transaction will use the synchronization scheme described in
@@ -72,8 +70,7 @@
* @return An ID for the sync operation which will later be passed to transactionReady callback.
* This lets the caller differentiate overlapping sync operations.
*/
- @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
- conditional = true)
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
public int applySyncTransaction(@NonNull WindowContainerTransaction t,
@NonNull WindowContainerTransactionCallback callback) {
try {
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/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index b9373be..3303c0e 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -573,13 +573,6 @@
public static final String PERSISTS_WIDGET_PROVIDER_INFO = "persists_widget_provider_info";
/**
- * (boolean) Whether the clipboard overlay shows an edit button (as opposed to requiring tapping
- * the preview to send an edit intent).
- */
- public static final String CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON =
- "clipboard_overlay_show_edit_button";
-
- /**
* (boolean) Whether to show smart chips (based on TextClassifier) in the clipboard overlay.
*/
public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions";
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index f724e55..c946db1 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -46,6 +46,38 @@
*/
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? */
@@ -75,33 +107,6 @@
return MAIN_RESOLVER;
}
- /** 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.
- */
- public static final Flag FSI_FORCE_DEMOTE =
- devFlag("persist.sysui.notification.fsi_force_demote");
-
- /** 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");
-
- }
-
- //// == Everything below this line is the implementation == ////
-
/**
* Creates a flag that is enabled by default in debuggable builds.
* It can be enabled by setting this flag's SystemProperty to 1.
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/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
index 205c5fd..d2b612a 100644
--- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
+++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
@@ -73,6 +73,23 @@
mOnPropertiesChangedListener);
}
+ public void registerForCurrentUser() {
+ ContentResolver r = mContext.getContentResolver();
+ r.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT),
+ false, this);
+ r.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT),
+ false, this);
+ r.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE),
+ false, this);
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ runnable -> mMainHandler.post(runnable),
+ mOnPropertiesChangedListener);
+ }
+
public void unregister() {
mContext.getContentResolver().unregisterContentObserver(this);
DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
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/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b1610d7..8952f37 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -428,7 +428,7 @@
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return 0;
} else if (err != NO_ERROR) {
- jniThrowException(env, OutOfResourcesException, NULL);
+ jniThrowException(env, OutOfResourcesException, statusToString(err).c_str());
return 0;
}
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-ca/strings.xml b/core/res/res/values-ca/strings.xml
index ca1d87f..4cf9384 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -52,7 +52,7 @@
<string name="needPuk2" msgid="7032612093451537186">"Escriviu el PUK2 per desbloquejar la targeta SIM."</string>
<string name="enablePin" msgid="2543771964137091212">"No és correcte; activa el bloqueig de RUIM/SIM."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
- <item quantity="many">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
+ <item quantity="many">Et queden <xliff:g id="NUMBER_1">%d</xliff:g> intents; si no l\'encertes, la SIM es bloquejarà.</item>
<item quantity="other">Et queden <xliff:g id="NUMBER_1">%d</xliff:g> intents; si no l\'encertes, la SIM es bloquejarà.</item>
<item quantity="one">Et queda <xliff:g id="NUMBER_0">%d</xliff:g> intent; si no l\'encertes, la SIM es bloquejarà.</item>
</plurals>
@@ -182,7 +182,7 @@
<string name="low_memory" product="watch" msgid="3479447988234030194">"L\'emmagatzematge del rellotge està ple. Suprimeix uns quants fitxers per alliberar espai."</string>
<string name="low_memory" product="tv" msgid="6663680413790323318">"L\'espai d\'emmagatzematge del dispositiu Android TV és ple. Suprimeix alguns fitxers per alliberar espai."</string>
<string name="low_memory" product="default" msgid="2539532364144025569">"L\'emmagatzematge del telèfon és ple. Suprimeix uns quants fitxers per alliberar espai."</string>
- <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{L\'autoritat de certificació s\'ha instal·lat}many{Certificate authorities installed}other{Les autoritats de certificació s\'han instal·lat}}"</string>
+ <string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{L\'autoritat de certificació s\'ha instal·lat}many{Les autoritats de certificació s\'han instal·lat}other{Les autoritats de certificació s\'han instal·lat}}"</string>
<string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Per un tercer desconegut"</string>
<string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"Per l\'administrador del teu perfil de treball"</string>
<string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"Per <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string>
@@ -256,7 +256,7 @@
<string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Utilitza aquesta opció en la majoria de circumstàncies. Et permet fer un seguiment del progrés de l\'informe, introduir més dades sobre el problema i fer captures de pantalla. És possible que ometi seccions poc utilitzades que requereixen molt de temps."</string>
<string name="bugreport_option_full_title" msgid="7681035745950045690">"Informe complet"</string>
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"Utilitza aquesta opció perquè la interferència en el sistema sigui mínima si el dispositiu no respon o va massa lent, o bé si necessites totes les seccions de l\'informe. No et permet introduir més dades ni fer més captures de pantalla."</string>
- <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Es farà una captura de pantalla de l\'informe d\'errors d\'aquí a # segon.}many{Taking screenshot for bug report in # seconds.}other{Es farà una captura de pantalla de l\'informe d\'errors d\'aquí a # segons.}}"</string>
+ <string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Es farà una captura de pantalla de l\'informe d\'errors d\'aquí a # segon.}many{Es farà una captura de pantalla de l\'informe d\'errors d\'aquí a # segons.}other{Es farà una captura de pantalla de l\'informe d\'errors d\'aquí a # segons.}}"</string>
<string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"S\'ha fet la captura de pantalla amb l\'informe d\'errors"</string>
<string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"No s\'ha pogut fer la captura de pantalla amb l\'informe d\'errors"</string>
<string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Mode silenciós"</string>
@@ -1090,7 +1090,7 @@
<string name="enable_explore_by_touch_warning_message" product="default" msgid="4312979647356179250">"<xliff:g id="ACCESSIBILITY_SERVICE_NAME">%1$s</xliff:g> vol activar l\'exploració tàctil. Quan l\'exploració per tàctil està activada, pots escoltar o veure les descripcions del contingut seleccionat o utilitzar gestos per interaccionar amb el telèfon."</string>
<string name="oneMonthDurationPast" msgid="4538030857114635777">"Fa 1 mes"</string>
<string name="beforeOneMonthDurationPast" msgid="8315149541372065392">"Fa més d\'1 mes"</string>
- <string name="last_num_days" msgid="2393660431490280537">"{count,plural, =1{Darrer dia (#)}many{Last # days}other{# darrers dies}}"</string>
+ <string name="last_num_days" msgid="2393660431490280537">"{count,plural, =1{Darrer dia (#)}many{# darrers dies}other{# darrers dies}}"</string>
<string name="last_month" msgid="1528906781083518683">"Darrer mes"</string>
<string name="older" msgid="1645159827884647400">"Més antigues"</string>
<string name="preposition_for_date" msgid="2780767868832729599">"el <xliff:g id="DATE">%s</xliff:g>"</string>
@@ -1117,14 +1117,14 @@
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"d\'aquí a <xliff:g id="COUNT">%d</xliff:g> h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"d\'aquí a <xliff:g id="COUNT">%d</xliff:g> d"</string>
<string name="duration_years_shortest_future" msgid="5537464088352970388">"d\'aquí a <xliff:g id="COUNT">%d</xliff:g> a"</string>
- <string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Fa # minut}many{# minutes ago}other{Fa # minuts}}"</string>
- <string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Fa # hora}many{# hours ago}other{Fa # hores}}"</string>
- <string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Fa # dia}many{# days ago}other{Fa # dies}}"</string>
- <string name="duration_years_relative" msgid="8731202348869424370">"{count,plural, =1{Fa # any}many{# years ago}other{Fa # anys}}"</string>
- <string name="duration_minutes_relative_future" msgid="5259574171747708115">"{count,plural, =1{# minut}many{# minutes}other{# minuts}}"</string>
- <string name="duration_hours_relative_future" msgid="6670440478481140565">"{count,plural, =1{# hora}many{# hours}other{# hores}}"</string>
- <string name="duration_days_relative_future" msgid="8870658635774250746">"{count,plural, =1{# dia}many{# days}other{# dies}}"</string>
- <string name="duration_years_relative_future" msgid="8855853883925918380">"{count,plural, =1{# any}many{# years}other{# anys}}"</string>
+ <string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Fa # minut}many{Fa # minuts}other{Fa # minuts}}"</string>
+ <string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{Fa # hora}many{Fa # hores}other{Fa # hores}}"</string>
+ <string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{Fa # dia}many{Fa # dies}other{Fa # dies}}"</string>
+ <string name="duration_years_relative" msgid="8731202348869424370">"{count,plural, =1{Fa # any}many{Fa # anys}other{Fa # anys}}"</string>
+ <string name="duration_minutes_relative_future" msgid="5259574171747708115">"{count,plural, =1{# minut}many{# minuts}other{# minuts}}"</string>
+ <string name="duration_hours_relative_future" msgid="6670440478481140565">"{count,plural, =1{# hora}many{# hores}other{# hores}}"</string>
+ <string name="duration_days_relative_future" msgid="8870658635774250746">"{count,plural, =1{# dia}many{# dies}other{# dies}}"</string>
+ <string name="duration_years_relative_future" msgid="8855853883925918380">"{count,plural, =1{# any}many{# anys}other{# anys}}"</string>
<string name="VideoView_error_title" msgid="5750686717225068016">"Problema amb el vídeo"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="3782449246085134720">"Aquest vídeo no és vàlid per a la reproducció en aquest dispositiu."</string>
<string name="VideoView_error_text_unknown" msgid="7658683339707607138">"No es pot reproduir aquest vídeo."</string>
@@ -1511,7 +1511,7 @@
<string name="skip_button_label" msgid="3566599811326688389">"Omet"</string>
<string name="no_matches" msgid="6472699895759164599">"No s\'ha trobat cap coincidència"</string>
<string name="find_on_page" msgid="5400537367077438198">"Troba-ho a la pàgina"</string>
- <string name="matches_found" msgid="2296462299979507689">"{count,plural, =1{# coincidència}many{# of {total}}other{# de {total}}}"</string>
+ <string name="matches_found" msgid="2296462299979507689">"{count,plural, =1{# coincidència}many{# de {total}}other{# de {total}}}"</string>
<string name="action_mode_done" msgid="2536182504764803222">"Fet"</string>
<string name="progress_erasing" msgid="6891435992721028004">"S\'està esborrant l\'emmagatzematge compartit…"</string>
<string name="share" msgid="4157615043345227321">"Comparteix"</string>
@@ -1866,14 +1866,14 @@
<string name="data_saver_description" msgid="4995164271550590517">"Per reduir l\'ús de dades, la funció Estalvi de dades evita que determinades aplicacions enviïn o rebin dades en segon pla. L\'aplicació que estiguis fent servir podrà accedir a les dades, però menys sovint. Això vol dir, per exemple, que les imatges no es mostraran fins que no les toquis."</string>
<string name="data_saver_enable_title" msgid="7080620065745260137">"Vols activar l\'Estalvi de dades?"</string>
<string name="data_saver_enable_button" msgid="4399405762586419726">"Activa"</string>
- <string name="zen_mode_duration_minutes_summary" msgid="4555514757230849789">"{count,plural, =1{Durant 1 minut (fins a les {formattedTime})}many{For # minutes (until {formattedTime})}other{Durant # minuts (fins a les {formattedTime})}}"</string>
- <string name="zen_mode_duration_minutes_summary_short" msgid="1187553788355486950">"{count,plural, =1{Durant 1 min (fins a les {formattedTime})}many{For # min (until {formattedTime})}other{Durant # min (fins a les {formattedTime})}}"</string>
- <string name="zen_mode_duration_hours_summary" msgid="3866333100793277211">"{count,plural, =1{Durant 1 hora (fins a les {formattedTime})}many{For # hours (until {formattedTime})}other{Durant # hores (fins a les {formattedTime})}}"</string>
- <string name="zen_mode_duration_hours_summary_short" msgid="687919813833347945">"{count,plural, =1{Durant 1 h (fins a les {formattedTime})}many{For # hr (until {formattedTime})}other{Durant # h (fins a les {formattedTime})}}"</string>
- <string name="zen_mode_duration_minutes" msgid="2340007982276569054">"{count,plural, =1{Durant 1 minut}many{For # minutes}other{Durant # minuts}}"</string>
- <string name="zen_mode_duration_minutes_short" msgid="2435756450204526554">"{count,plural, =1{Durant 1 min}many{For # min}other{Durant # min}}"</string>
- <string name="zen_mode_duration_hours" msgid="7841806065034711849">"{count,plural, =1{Durant 1 hora}many{For # hours}other{Durant # hores}}"</string>
- <string name="zen_mode_duration_hours_short" msgid="3666949653933099065">"{count,plural, =1{Durant 1 h}many{For # hr}other{Durant # h}}"</string>
+ <string name="zen_mode_duration_minutes_summary" msgid="4555514757230849789">"{count,plural, =1{Durant 1 minut (fins a les {formattedTime})}many{Durant # minuts (fins a les {formattedTime})}other{Durant # minuts (fins a les {formattedTime})}}"</string>
+ <string name="zen_mode_duration_minutes_summary_short" msgid="1187553788355486950">"{count,plural, =1{Durant 1 min (fins a les {formattedTime})}many{Durant # min (fins a les {formattedTime})}other{Durant # min (fins a les {formattedTime})}}"</string>
+ <string name="zen_mode_duration_hours_summary" msgid="3866333100793277211">"{count,plural, =1{Durant 1 hora (fins a les {formattedTime})}many{Durant # hores (fins a les {formattedTime})}other{Durant # hores (fins a les {formattedTime})}}"</string>
+ <string name="zen_mode_duration_hours_summary_short" msgid="687919813833347945">"{count,plural, =1{Durant 1 h (fins a les {formattedTime})}many{Durant # h (fins a les {formattedTime})}other{Durant # h (fins a les {formattedTime})}}"</string>
+ <string name="zen_mode_duration_minutes" msgid="2340007982276569054">"{count,plural, =1{Durant 1 minut}many{Durant # minuts}other{Durant # minuts}}"</string>
+ <string name="zen_mode_duration_minutes_short" msgid="2435756450204526554">"{count,plural, =1{Durant 1 min}many{Durant # min}other{Durant # min}}"</string>
+ <string name="zen_mode_duration_hours" msgid="7841806065034711849">"{count,plural, =1{Durant 1 hora}many{Durant # hores}other{Durant # hores}}"</string>
+ <string name="zen_mode_duration_hours_short" msgid="3666949653933099065">"{count,plural, =1{Durant 1 h}many{Durant # h}other{Durant # h}}"</string>
<string name="zen_mode_until_next_day" msgid="1403042784161725038">"Finalitza: <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_until" msgid="2250286190237669079">"Fins a les <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Fins a les <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (propera alarma)"</string>
@@ -2004,7 +2004,7 @@
<string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Desa per a emplenament automàtic"</string>
<string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"El contingut no es pot emplenar automàticament"</string>
<string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Cap suggeriment d\'emplenament automàtic"</string>
- <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{1 suggeriment d\'emplenament automàtic}many{# autofill suggestions}other{# suggeriments d\'emplenament automàtic}}"</string>
+ <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{1 suggeriment d\'emplenament automàtic}many{# suggeriments d\'emplenament automàtic}other{# suggeriments d\'emplenament automàtic}}"</string>
<string name="autofill_save_title" msgid="7719802414283739775">"Vols desar-ho a "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_title_with_type" msgid="3002460014579799605">"Vols desar <xliff:g id="TYPE">%1$s</xliff:g> a "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_title_with_2types" msgid="3783270967447869241">"Vols desar <xliff:g id="TYPE_0">%1$s</xliff:g> i <xliff:g id="TYPE_1">%2$s</xliff:g> a "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
@@ -2115,7 +2115,7 @@
<string name="mime_type_presentation_ext" msgid="8761049335564371468">"Presentació <xliff:g id="EXTENSION">%1$s</xliff:g>"</string>
<string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"El Bluetooth es mantindrà activat durant el mode d\'avió"</string>
<string name="car_loading_profile" msgid="8219978381196748070">"S\'està carregant"</string>
- <string name="file_count" msgid="3220018595056126969">"{count,plural, =1{{file_name} i # fitxer}many{{file_name} + # files}other{{file_name} i # fitxers}}"</string>
+ <string name="file_count" msgid="3220018595056126969">"{count,plural, =1{{file_name} i # fitxer}many{{file_name} i # fitxers}other{{file_name} i # fitxers}}"</string>
<string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"No hi ha cap suggeriment de persones amb qui compartir"</string>
<string name="chooser_all_apps_button_label" msgid="3230427756238666328">"Llista d\'aplicacions"</string>
<string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Aquesta aplicació no té permís de gravació, però pot capturar àudio a través d\'aquest dispositiu USB."</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 51688d3..0e17b9a 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -217,7 +217,7 @@
<string name="silent_mode" msgid="8796112363642579333">"Lydløs"</string>
<string name="turn_on_radio" msgid="2961717788170634233">"Slå trådløs til"</string>
<string name="turn_off_radio" msgid="7222573978109933360">"Slå trådløs fra"</string>
- <string name="screen_lock" msgid="2072642720826409809">"Skærmlås"</string>
+ <string name="screen_lock" msgid="2072642720826409809">"Skærmlås"</string>
<string name="power_off" msgid="4111692782492232778">"Sluk"</string>
<string name="silent_mode_silent" msgid="5079789070221150912">"Ringeren er deaktiveret"</string>
<string name="silent_mode_vibrate" msgid="8821830448369552678">"Ringervibrering"</string>
@@ -241,7 +241,7 @@
<string name="global_actions" product="tablet" msgid="4412132498517933867">"Valgmuligheder for tabletcomputeren"</string>
<string name="global_actions" product="tv" msgid="3871763739487450369">"Valgmuligheder for Android TV"</string>
<string name="global_actions" product="default" msgid="6410072189971495460">"Indstillinger for telefon"</string>
- <string name="global_action_lock" msgid="6949357274257655383">"Skærmlås"</string>
+ <string name="global_action_lock" msgid="6949357274257655383">"Skærmlås"</string>
<string name="global_action_power_off" msgid="4404936470711393203">"Sluk"</string>
<string name="global_action_power_options" msgid="1185286119330160073">"Afbryderknap"</string>
<string name="global_action_restart" msgid="4678451019561687074">"Genstart"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 514f8ef..2f51055 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -52,7 +52,7 @@
<string name="needPuk2" msgid="7032612093451537186">"Escribir PUK2 para desbloquear la tarjeta SIM."</string>
<string name="enablePin" msgid="2543771964137091212">"Error; habilita el bloqueo de SIM/RUIM."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
- <item quantity="many">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
+ <item quantity="many">Tienes <xliff:g id="NUMBER_1">%d</xliff:g> intentos más antes de que se bloquee la tarjeta SIM.</item>
<item quantity="other">Tienes <xliff:g id="NUMBER_1">%d</xliff:g> intentos más antes de que se bloquee la tarjeta SIM.</item>
<item quantity="one">Tienes <xliff:g id="NUMBER_0">%d</xliff:g> un intento más antes de que se bloquee la tarjeta SIM.</item>
</plurals>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index f921c7b..29e8abf 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -52,7 +52,7 @@
<string name="needPuk2" msgid="7032612093451537186">"Introduce el código PUK2 para desbloquear la tarjeta SIM."</string>
<string name="enablePin" msgid="2543771964137091212">"Error, habilitar bloqueo de SIM/RUIM."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
- <item quantity="many">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
+ <item quantity="many">Te quedan <xliff:g id="NUMBER_1">%d</xliff:g> intentos para bloquear la tarjeta SIM.</item>
<item quantity="other">Te quedan <xliff:g id="NUMBER_1">%d</xliff:g> intentos para bloquear la tarjeta SIM.</item>
<item quantity="one">Te queda <xliff:g id="NUMBER_0">%d</xliff:g> intento para bloquear la tarjeta SIM.</item>
</plurals>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 28d3e6a..fc7a86e 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -427,9 +427,9 @@
<string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Android TV gailuko deien erregistroa aldatzeko baimena ematen die aplikazioei, jasotako eta egindako deiei buruzko datuak barne. Baliteke asmo txarreko aplikazioek deien erregistroa ezabatzea edo aldatzea."</string>
<string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Telefonoaren deien erregistroa aldatzeko baimena ematen die aplikazioei, sarrerako eta irteerako deiei buruzko datuak barne. Asmo txarreko aplikazioek deien erregistroa ezabatzeko edo aldatzeko erabil dezakete."</string>
<string name="permlab_bodySensors" msgid="662918578601619569">"Atzitu gorputz-sentsoreen datuak (esaterako, bihotz-maiztasuna) aplikazioa erabili bitartean"</string>
- <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Aplikazioak erabiltzen diren bitartean, gorputz-sentsoreen datuak (besteak beste, bihotz-maiztasuna, tenperatura eta odolean dagoen oxigenoaren ehunekoa) atzitzeko baimena ematen die aplikazio horiei."</string>
+ <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Aplikazioak erabiltzen diren bitartean, gorputz-sentsoreen datuak (besteak beste, bihotz-maiztasuna, tenperatura eta odolean dagoen oxigenoaren ehunekoa) erabiltzeko baimena ematen die aplikazio horiei."</string>
<string name="permlab_bodySensors_background" msgid="4912560779957760446">"Atzitu gorputz-sentsoreen datuak (adib., bihotz-maiztasunarenak) atzeko planoan"</string>
- <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Aplikazioak atzeko planoan egon bitartean, gorputz-sentsoreen datuak (besteak beste, bihotz-maiztasuna, tenperatura eta odolean dagoen oxigenoaren ehunekoa) atzitzeko baimena ematen die aplikazio horiei."</string>
+ <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Aplikazioak atzeko planoan egon bitartean, gorputz-sentsoreen datuak (besteak beste, bihotz-maiztasuna, tenperatura eta odolean dagoen oxigenoaren ehunekoa) erabiltzeko baimena ematen die aplikazio horiei."</string>
<string name="permlab_readCalendar" msgid="6408654259475396200">"irakurri egutegiko gertaerak eta xehetasunak"</string>
<string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"Aplikazioak tabletan gordetako egutegiko gertaerak irakur ditzake eta egutegiko datuak parteka eta gorde ditzake."</string>
<string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"Aplikazioak Android TV gailuan gordeta dituzun egutegiko gertaerak irakur ditzake, baita egutegiko datuak partekatu eta gorde ere."</string>
@@ -439,7 +439,7 @@
<string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"Android TV gailuan egutegiko gertaerak gehitzeko eta gehitutakoak kentzeko edo aldatzeko aukera dute aplikazioek. Gainera, egutegien jabeenak diruditen mezuak bidal ditzakete, edo gertaerak aldatu jabeei ezer esan gabe."</string>
<string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"Telefonoko gertaerak gehitzeko, kentzeko edo aldatzeko aukera du aplikazioak. Gainera, egutegien jabeenak diruditen mezuak bidal ditzake, eta gertaerak alda ditzake jabeei beraiei jakinarazi gabe."</string>
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"atzitu kokapen-hornitzaileen komando gehigarriak"</string>
- <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Kokapen-hornitzailearen agindu gehigarriak atzitzeko baimena ematen die aplikazioei. Horrela, agian aplikazioek GPSaren edo bestelako kokapenaren iturburuen funtzionamenduan eragina izan dezakete."</string>
+ <string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Kokapen-hornitzailearen agindu gehigarriak erabiltzeko baimena ematen die aplikazioei. Horrela, agian aplikazioek GPSaren edo bestelako kokapenaren iturburuen funtzionamenduan eragina izan dezakete."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"lortu kokapen zehatza aurreko planoan bakarrik"</string>
<string name="permdesc_accessFineLocation" msgid="6732174080240016335">"Abian denean, aplikazioak kokapen zehatza lor dezake kokapen-zerbitzuen bidez. Aplikazioak kokapena lortu ahal izateko, kokapen-zerbitzuek aktibatuta egon behar dute gailuan. Bateria-erabilera areagotzen du horrek."</string>
<string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"atzitu gutxi gorabeherako kokapena aurreko planoan bakarrik"</string>
@@ -460,21 +460,21 @@
<string name="permdesc_camera" msgid="5240801376168647151">"Aplikazioak abian den bitartean erabil dezake kamera argazkiak ateratzeko eta bideoak grabatzeko."</string>
<string name="permlab_backgroundCamera" msgid="7549917926079731681">"Argazkiak atera eta bideoak grabatu atzeko planoan."</string>
<string name="permdesc_backgroundCamera" msgid="1615291686191138250">"Aplikazioak edonoiz erabil dezake kamera argazkiak ateratzeko eta bideoak grabatzeko."</string>
- <string name="permlab_systemCamera" msgid="3642917457796210580">"eman sistemako kamerak atzitzeko baimena aplikazio edo zerbitzu bati argazkiak ateratzeko eta bideoak grabatzeko"</string>
+ <string name="permlab_systemCamera" msgid="3642917457796210580">"eman sistemako kamerak erabiltzeko baimena aplikazio edo zerbitzu bati argazkiak ateratzeko eta bideoak grabatzeko"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"Pribilegioa duen edo sistemakoa den aplikazio honek edonoiz erabil dezake kamera argazkiak ateratzeko eta bideoak grabatzeko. Halaber, android.permission.CAMERA baimena izan behar du aplikazioak."</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"eman jakinarazpenak jasotzeko baimena aplikazioari edo zerbitzuari kamerak ireki edo ixten direnean."</string>
<string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"Kamera ireki edo itxi dela (eta zer aplikaziorekin) dioten jakinarazpenak jaso ditzake aplikazio honek."</string>
<string name="permlab_vibrate" msgid="8596800035791962017">"kontrolatu dardara"</string>
<string name="permdesc_vibrate" msgid="8733343234582083721">"Bibragailua kontrolatzeko baimena ematen die aplikazioei."</string>
- <string name="permdesc_vibrator_state" msgid="7050024956594170724">"Dardara-egoera atzitzeko baimena ematen die aplikazioei."</string>
+ <string name="permdesc_vibrator_state" msgid="7050024956594170724">"Dardara-egoera erabiltzeko baimena ematen die aplikazioei."</string>
<string name="permlab_callPhone" msgid="1798582257194643320">"deitu zuzenean telefono-zenbakietara"</string>
<string name="permdesc_callPhone" msgid="5439809516131609109">"Telefono-zenbakietara zuk esku hartu gabe deitzeko baimena ematen die aplikazioei. Horrela, ustekabeko gastuak edo deiak eragin daitezke. Asmo txarreko aplikazioek erabil dezakete zuk berretsi gabeko deiak eginda gastuak eragiteko."</string>
<string name="permlab_accessImsCallService" msgid="442192920714863782">"atzitu IMS dei-zerbitzua"</string>
<string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Zuk ezer egin beharrik gabe deiak egiteko IMS zerbitzua erabiltzeko baimena ematen die aplikazioei."</string>
<string name="permlab_readPhoneState" msgid="8138526903259297969">"irakurri telefonoaren egoera eta identitatea"</string>
- <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Gailuaren telefono-eginbideak atzitzeko baimena ematen die aplikazioei. Baimen horrek aplikazioari telefono-zenbakia eta gailu IDak zein diren, deirik aktibo dagoen eta deia zer zenbakirekin konektatuta dagoen zehazteko baimena ematen die aplikazioei."</string>
+ <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Gailuaren telefono-eginbideak erabiltzeko baimena ematen die aplikazioei. Baimen horrek aplikazioari telefono-zenbakia eta gailu IDak zein diren, deirik aktibo dagoen eta deia zer zenbakirekin konektatuta dagoen zehazteko baimena ematen die aplikazioei."</string>
<string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"irakurri oinarrizko egoera telefonikoa eta identitatea"</string>
- <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Gailuaren oinarrizko eginbide telefonikoak atzitzeko baimena ematen dio aplikazioari."</string>
+ <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Gailuaren oinarrizko eginbide telefonikoak erabiltzeko baimena ematen dio aplikazioari."</string>
<string name="permlab_manageOwnCalls" msgid="9033349060307561370">"bideratu deiak sistemaren bidez"</string>
<string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Deiak sistemaren bidez bideratzea baimentzen die aplikazioei, deien zerbitzua ahal bezain ona izan dadin."</string>
<string name="permlab_callCompanionApp" msgid="3654373653014126884">"ikusi eta kontrolatu deiak sistemaren bidez."</string>
@@ -484,7 +484,7 @@
<string name="permlab_acceptHandover" msgid="2925523073573116523">"jarraitu beste aplikazio batean hasitako deia"</string>
<string name="permdesc_acceptHandovers" msgid="7129026180128626870">"Beste aplikazio batean hasitako dei batekin jarraitzeko baimena ematen die aplikazioei."</string>
<string name="permlab_readPhoneNumbers" msgid="5668704794723365628">"irakurri telefono-zenbakiak"</string>
- <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"Gailuaren telefono-zenbakiak atzitzeko baimena ematen die aplikazioei."</string>
+ <string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"Gailuaren telefono-zenbakiak erabiltzeko baimena ematen die aplikazioei."</string>
<string name="permlab_wakeLock" product="automotive" msgid="1904736682319375676">"mantendu piztuta autoko pantaila"</string>
<string name="permlab_wakeLock" product="tablet" msgid="1527660973931694000">"eragotzi tableta inaktibo ezartzea"</string>
<string name="permlab_wakeLock" product="tv" msgid="2856941418123343518">"Android TV gailua inaktibo ezar dadin eragotzi"</string>
@@ -631,7 +631,7 @@
<string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Sakatu hau aurpegi-eredua ezabatzeko eta, gero, gehitu aurpegia berriro"</string>
<string name="face_setup_notification_title" msgid="8843461561970741790">"Konfiguratu aurpegi bidez desblokeatzeko eginbidea"</string>
<string name="face_setup_notification_content" msgid="5463999831057751676">"Telefonoa desblokeatzeko, begira iezaiozu"</string>
- <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Aurpegi bidez desblokeatzeko aukera erabiltzeko, aktibatu "<b>"kamera atzitzeko baimena"</b>" Ezarpenak > Pribatutasuna atalean"</string>
+ <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Aurpegi bidez desblokeatzeko aukera erabiltzeko, aktibatu "<b>"kamera erabiltzeko baimena"</b>" Ezarpenak > Pribatutasuna atalean"</string>
<string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Konfiguratu telefonoa desblokeatzeko modu gehiago"</string>
<string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Sakatu hau hatz-marka bat gehitzeko"</string>
<string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Hatz-marka bidez desblokeatzea"</string>
@@ -1471,14 +1471,14 @@
<string name="ime_action_default" msgid="8265027027659800121">"Abiarazi"</string>
<string name="dial_number_using" msgid="6060769078933953531">"Markatu zenbakia \n<xliff:g id="NUMBER">%s</xliff:g> erabilita"</string>
<string name="create_contact_using" msgid="6200708808003692594">"Sortu kontaktu bat\n<xliff:g id="NUMBER">%s</xliff:g> erabilita"</string>
- <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"Aplikazio hauetako bat edo gehiago kontua orain eta etorkizunean atzitzeko baimena eskatzen ari dira."</string>
+ <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"Aplikazio hauetako bat edo gehiago kontua orain eta etorkizunean erabiltzeko baimena eskatzen ari dira."</string>
<string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"Eskaera onartu nahi duzu?"</string>
<string name="grant_permissions_header_text" msgid="3420736827804657201">"Sarbide-eskaera"</string>
<string name="allow" msgid="6195617008611933762">"Eman baimena"</string>
<string name="deny" msgid="6632259981847676572">"Ukatu"</string>
<string name="permission_request_notification_title" msgid="1810025922441048273">"Baimena eskatu da"</string>
<string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"Baimena eskatu da \n<xliff:g id="ACCOUNT">%s</xliff:g> konturako."</string>
- <string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"<xliff:g id="APP">%1$s</xliff:g> aplikazioak <xliff:g id="ACCOUNT">%2$s</xliff:g> kontua atzitzeko baimena\neskatu du."</string>
+ <string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"<xliff:g id="APP">%1$s</xliff:g> aplikazioak <xliff:g id="ACCOUNT">%2$s</xliff:g> kontua erabiltzeko baimena\neskatu du."</string>
<string name="forward_intent_to_owner" msgid="4620359037192871015">"Laneko profiletik kanpo ari zara aplikazioa erabiltzen"</string>
<string name="forward_intent_to_work" msgid="3620262405636021151">"Laneko profilean ari zara aplikazioa erabiltzen"</string>
<string name="input_method_binding_label" msgid="1166731601721983656">"Idazketa-metodoa"</string>
@@ -2050,11 +2050,11 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"DESINSTALATU"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"IREKI, HALA ERE"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Aplikazio kaltegarri bat hauteman da"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Gailuko erregistro guztiak atzitzeko baimena eman nahi diozu <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aplikazioari?"</string>
+ <string name="log_access_confirmation_title" msgid="2343578467290592708">"Gailuko erregistro guztiak erabiltzeko baimena eman nahi diozu <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> aplikazioari?"</string>
<string name="log_access_confirmation_allow" msgid="5302517782599389507">"Eman behin erabiltzeko baimena"</string>
<string name="log_access_confirmation_deny" msgid="7685790957455099845">"Ez eman baimenik"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak atzitzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak atzitzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak atzitzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak atzitzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea.\n\nLortu informazio gehiago g.co/android/devicelogs helbidean."</string>
+ <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak erabiltzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak erabiltzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea."</string>
+ <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Gailuko erregistroetan gailuan gertatzen den guztia gordetzen da. Arazoak bilatu eta konpontzeko erabil ditzakete aplikazioek erregistro horiek.\n\nBaliteke erregistro batzuek kontuzko informazioa edukitzea. Beraz, eman gailuko erregistro guztiak erabiltzeko baimena fidagarritzat jotzen dituzun aplikazioei bakarrik. \n\nNahiz eta gailuko erregistro guztiak erabiltzeko baimena ez eman aplikazio honi, aplikazioak hari dagozkion erregistroak atzitu ahalko ditu. Gainera, baliteke gailuaren fabrikatzaileak gailuko erregistro edo datu batzuk atzitu ahal izatea.\n\nLortu informazio gehiago g.co/android/devicelogs helbidean."</string>
<string name="log_access_do_not_show_again" msgid="1058690599083091552">"Ez erakutsi berriro"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> aplikazioak <xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakutsi nahi ditu"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Editatu"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 2795609..6ca95b3 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -53,7 +53,7 @@
<string name="enablePin" msgid="2543771964137091212">"Opération infructueuse. Activez le verrouillage SIM/RUIM."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="one">Il vous reste <xliff:g id="NUMBER_1">%d</xliff:g> tentative avant que votre carte SIM soit verrouillée.</item>
- <item quantity="many">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
+ <item quantity="many">Il vous reste <xliff:g id="NUMBER_1">%d</xliff:g> tentatives avant que votre carte SIM soit verrouillée.</item>
<item quantity="other">Il vous reste <xliff:g id="NUMBER_1">%d</xliff:g> tentatives avant que votre carte SIM soit verrouillée.</item>
</plurals>
<string name="imei" msgid="2157082351232630390">"Code IIEM"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 0888f2f..815ae1b 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -53,7 +53,7 @@
<string name="enablePin" msgid="2543771964137091212">"Échec de l\'opération. Veuillez activer le verrouillage de la carte SIM/RUIM."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="one">Il vous reste <xliff:g id="NUMBER_1">%d</xliff:g> tentative avant que votre carte SIM ne soit verrouillée.</item>
- <item quantity="many">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
+ <item quantity="many">Il vous reste <xliff:g id="NUMBER_1">%d</xliff:g> tentatives avant que votre carte SIM ne soit verrouillée.</item>
<item quantity="other">Il vous reste <xliff:g id="NUMBER_1">%d</xliff:g> tentatives avant que votre carte SIM ne soit verrouillée.</item>
</plurals>
<string name="imei" msgid="2157082351232630390">"Code IMEI"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index d1d7d3b..304d212 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -52,7 +52,7 @@
<string name="needPuk2" msgid="7032612093451537186">"Digita il PUK2 per sbloccare la SIM."</string>
<string name="enablePin" msgid="2543771964137091212">"Operazione non riuscita; attiva blocco SIM/RUIM."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
- <item quantity="many">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
+ <item quantity="many">Hai ancora <xliff:g id="NUMBER_1">%d</xliff:g> tentativi a disposizione prima che la SIM venga bloccata.</item>
<item quantity="other">Hai ancora <xliff:g id="NUMBER_1">%d</xliff:g> tentativi a disposizione prima che la SIM venga bloccata.</item>
<item quantity="one">Hai ancora <xliff:g id="NUMBER_0">%d</xliff:g> tentativo a disposizione prima che la SIM venga bloccata.</item>
</plurals>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 524ad37..7886d2b 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -53,7 +53,7 @@
<string name="enablePin" msgid="2543771964137091212">"Falha. Ative o bloqueio do chip/R-UIM."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="one">Tentativas restantes: <xliff:g id="NUMBER_1">%d</xliff:g>. Caso o código correto não seja digitado, o chip será bloqueado.</item>
- <item quantity="many">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
+ <item quantity="many">Tentativas restantes: <xliff:g id="NUMBER_1">%d</xliff:g>. Caso o código correto não seja digitado, o chip será bloqueado.</item>
<item quantity="other">Tentativas restantes: <xliff:g id="NUMBER_1">%d</xliff:g>. Caso o código correto não seja digitado, o chip será bloqueado.</item>
</plurals>
<string name="imei" msgid="2157082351232630390">"IMEI"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index c2cf7ab..561e47e 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -52,7 +52,7 @@
<string name="needPuk2" msgid="7032612093451537186">"Introduza o PUK2 para desbloquear o cartão SIM."</string>
<string name="enablePin" msgid="2543771964137091212">"Ação sem êxito. Ative o bloqueio do SIM/RUIM."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
- <item quantity="many">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
+ <item quantity="many">Tem mais <xliff:g id="NUMBER_1">%d</xliff:g> tentativas antes de o cartão SIM ficar bloqueado.</item>
<item quantity="other">Tem mais <xliff:g id="NUMBER_1">%d</xliff:g> tentativas antes de o cartão SIM ficar bloqueado.</item>
<item quantity="one">Tem mais <xliff:g id="NUMBER_0">%d</xliff:g> tentativa antes de o cartão SIM ficar bloqueado.</item>
</plurals>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 524ad37..7886d2b 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -53,7 +53,7 @@
<string name="enablePin" msgid="2543771964137091212">"Falha. Ative o bloqueio do chip/R-UIM."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="one">Tentativas restantes: <xliff:g id="NUMBER_1">%d</xliff:g>. Caso o código correto não seja digitado, o chip será bloqueado.</item>
- <item quantity="many">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
+ <item quantity="many">Tentativas restantes: <xliff:g id="NUMBER_1">%d</xliff:g>. Caso o código correto não seja digitado, o chip será bloqueado.</item>
<item quantity="other">Tentativas restantes: <xliff:g id="NUMBER_1">%d</xliff:g>. Caso o código correto não seja digitado, o chip será bloqueado.</item>
</plurals>
<string name="imei" msgid="2157082351232630390">"IMEI"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index cf3677e..c7658177 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -598,7 +598,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>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 60fae7e..2f3951f 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/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>
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 dffd1cc..dafa0ad 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6084,6 +6084,12 @@
different from the home screen wallpaper. -->
<bool name="config_independentLockscreenLiveWallpaper">false</bool>
+ <!-- Whether the vendor power press code need to be mapped. -->
+ <bool name="config_powerPressMapping">false</bool>
+
+ <!-- Power press vendor code. -->
+ <integer name="config_powerPressCode">-1</integer>
+
<!-- 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 200ea44..591ba5f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2655,6 +2655,8 @@
<java-symbol type="integer" name="config_sideFpsToastTimeout"/>
<java-symbol type="integer" name="config_sidefpsSkipWaitForPowerAcquireMessage"/>
<java-symbol type="integer" name="config_sidefpsSkipWaitForPowerVendorAcquireMessage"/>
+ <java-symbol type="integer" name="config_powerPressCode"/>
+ <java-symbol type="bool" name="config_powerPressMapping"/>
<!-- Clickable toast used during sidefps enrollment -->
<java-symbol type="layout" name="side_fps_toast" />
@@ -3304,7 +3306,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 +3361,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 +4287,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 -->
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/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/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/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 6852320..c3b6916 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-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index 33e6aa6..26246fc 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bo 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Bo 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Volskerm onder"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Gebruik eenhandmodus"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Swiep van die onderkant van die skerm af op of tik enige plek bo die program om uit te gaan"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Begin eenhandmodus"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 4b7a5ecb..7f0a3ab 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ከላይ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ከላይ 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"የታች ሙሉ ማያ ገጽ"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ባለአንድ እጅ ሁነታን በመጠቀም ላይ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ለመውጣት ከማያው ግርጌ ወደ ላይ ይጥረጉ ወይም ከመተግበሪያው በላይ ማንኛውም ቦታ ላይ መታ ያድርጉ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ባለአንድ እጅ ሁነታ ጀምር"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 851d2d1..7f81e50 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ضبط حجم النافذة العلوية ليكون ٥٠%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ضبط حجم النافذة العلوية ليكون ٣٠%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"عرض النافذة السفلية بملء الشاشة"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"استخدام وضع \"التصفح بيد واحدة\""</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"للخروج، مرِّر سريعًا من أسفل الشاشة إلى أعلاها أو انقر في أي مكان فوق التطبيق."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"بدء وضع \"التصفح بيد واحدة\""</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index f8c87ae..505ca96 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ স্ক্ৰীনখন ৫০% কৰক"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ স্ক্ৰীনখন ৩০% কৰক"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"তলৰ স্ক্ৰীনখন সম্পূৰ্ণ স্ক্ৰীন কৰক"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"এখন হাতেৰে ব্যৱহাৰ কৰা ম’ড ব্যৱহাৰ কৰা"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"বাহিৰ হ’বলৈ স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ওপৰলৈ ছোৱাইপ কৰক অথবা এপ্টোৰ ওপৰত যিকোনো ঠাইত টিপক"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"এখন হাতেৰে ব্যৱহাৰ কৰা ম\'ডটো আৰম্ভ কৰক"</string>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 10663ee..0d754ba 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yuxarı 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Yuxarı 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Aşağı tam ekran"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Birəlli rejim istifadəsi"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Çıxmaq üçün ekranın aşağısından yuxarıya doğru sürüşdürün və ya tətbiqin yuxarısında istənilən yerə toxunun"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Birəlli rejim başlasın"</string>
diff --git a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
index 47e661c5..46d9f2b 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji ekran 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gornji ekran 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Režim celog ekrana za donji ekran"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korišćenje režima jednom rukom"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Da biste izašli, prevucite nagore od dna ekrana ili dodirnite bilo gde iznad aplikacije"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pokrenite režim jednom rukom"</string>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index d2a790a..31d0484 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхні экран – 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Верхні экран – 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ніжні экран – поўнаэкранны рэжым"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Выкарыстоўваецца рэжым кіравання адной рукой"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Каб выйсці, правядзіце па экране пальцам знізу ўверх або націсніце ў любым месцы над праграмай"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Запусціць рэжым кіравання адной рукой"</string>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index cb76e0a..08fe365 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горен екран: 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Горен екран: 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Долен екран: Показване на цял екран"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Използване на режима за работа с една ръка"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"За изход прекарайте пръст нагоре от долната част на екрана или докоснете произволно място над приложението"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Стартиране на режима за работа с една ръка"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index f3d740b..ac0daa0 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"শীর্ষ ৫০%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"শীর্ষ ৩০%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"নীচের অংশ নিয়ে পূর্ণ স্ক্রিন"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"\'এক হাতে ব্যবহার করার মোড\'-এর ব্যবহার"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"বেরিয়ে আসার জন্য, স্ক্রিনের নিচ থেকে উপরের দিকে সোয়াইপ করুন অথবা অ্যাপ আইকনের উপরে যেকোনও জায়গায় ট্যাপ করুন"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"\'এক হাতে ব্যবহার করার মোড\' শুরু করুন"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 2405b2a..745e6fc 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gore 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gore 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Donji ekran kao cijeli ekran"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korištenje načina rada jednom rukom"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Da izađete, prevucite s dna ekrana prema gore ili dodirnite bilo gdje iznad aplikacije"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Započinjanje načina rada jednom rukom"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 965a72d..727c319 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -22,7 +22,7 @@
<string name="pip_phone_settings" msgid="5468987116750491918">"Configuració"</string>
<string name="pip_phone_enter_split" msgid="7042877263880641911">"Entra al mode de pantalla dividida"</string>
<string name="pip_menu_title" msgid="5393619322111827096">"Menú"</string>
- <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> està en pantalla en pantalla"</string>
+ <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> està en mode d\'imatge sobre imatge"</string>
<string name="pip_notification_message" msgid="8854051911700302620">"Si no vols que <xliff:g id="NAME">%s</xliff:g> utilitzi aquesta funció, toca per obrir la configuració i desactiva-la."</string>
<string name="pip_play" msgid="3496151081459417097">"Reprodueix"</string>
<string name="pip_pause" msgid="690688849510295232">"Posa en pausa"</string>
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Pantalla superior al 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Pantalla superior al 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"S\'està utilitzant el mode d\'una mà"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Per sortir, llisca cap amunt des de la part inferior de la pantalla o toca qualsevol lloc a sobre de l\'aplicació"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Inicia el mode d\'una mà"</string>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
index 94ba0db..daa8c1d 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings_tv.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Pantalla en pantalla"</string>
+ <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Imatge sobre imatge"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(Programa sense títol)"</string>
<string name="pip_close" msgid="2955969519031223530">"Tanca"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Pantalla completa"</string>
@@ -25,7 +25,7 @@
<string name="pip_expand" msgid="1051966011679297308">"Desplega"</string>
<string name="pip_collapse" msgid="3903295106641385962">"Replega"</string>
<string name="pip_edu_text" msgid="3672999496647508701">" Prem dos cops "<annotation icon="home_icon">" INICI "</annotation>" per accedir als controls"</string>
- <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú de pantalla en pantalla."</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Menú d\'imatge sobre imatge."</string>
<string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Mou cap a l\'esquerra"</string>
<string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Mou cap a la dreta"</string>
<string name="a11y_action_pip_move_up" msgid="98502616918621959">"Mou cap amunt"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 8fa64fb..395e12c 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % nahoře"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % nahoře"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolní část na celou obrazovku"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Používání režimu jedné ruky"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Režim ukončíte, když přejedete prstem z dolní části obrazovky nahoru nebo klepnete kamkoli nad aplikaci"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Spustit režim jedné ruky"</string>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index e3da6e0..5087c13 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Øverste 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Øverste 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Vis nederste del i fuld skærm"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Brug af enhåndstilstand"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Du kan afslutte ved at stryge opad fra bunden af skærmen eller trykke et vilkårligt sted over appen"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start enhåndstilstand"</string>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 39d7bab..e97f068 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % oben"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % oben"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Vollbild unten"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Einhandmodus wird verwendet"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Wenn du die App schließen möchtest, wische vom unteren Rand des Displays nach oben oder tippe auf eine beliebige Stelle oberhalb der App"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Einhandmodus starten"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 4f4265a..df82625 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Πάνω 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Πάνω 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Κάτω πλήρης οθόνη"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Χρήση λειτουργίας ενός χεριού"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Για έξοδο, σύρετε προς τα πάνω από το κάτω μέρος της οθόνης ή πατήστε οπουδήποτε πάνω από την εφαρμογή."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Έναρξη λειτουργίας ενός χεριού"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index e29b01b..b1c9ba8 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -47,6 +47,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Top 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Bottom full screen"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Split left"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Split right"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Split top"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Split bottom"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Using one-handed mode"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start one-handed mode"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 9228c59..4aa2211 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -47,6 +47,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Top 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Bottom full screen"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Split left"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Split right"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Split top"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Split bottom"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Using one-handed mode"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start one-handed mode"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index e29b01b..b1c9ba8 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -47,6 +47,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Top 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Bottom full screen"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Split left"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Split right"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Split top"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Split bottom"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Using one-handed mode"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start one-handed mode"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index e29b01b..b1c9ba8 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -47,6 +47,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Top 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Bottom full screen"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Split left"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Split right"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Split top"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Split bottom"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Using one-handed mode"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start one-handed mode"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index cf23114..265849c 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -47,6 +47,10 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Top 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Top 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Bottom full screen"</string>
+ <string name="accessibility_split_left" msgid="1713683765575562458">"Split left"</string>
+ <string name="accessibility_split_right" msgid="8441001008181296837">"Split right"</string>
+ <string name="accessibility_split_top" msgid="2789329702027147146">"Split top"</string>
+ <string name="accessibility_split_bottom" msgid="8694551025220868191">"Split bottom"</string>
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Using one-handed mode"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"To exit, swipe up from the bottom of the screen or tap anywhere above the app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start one-handed mode"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index a147be3..1196aaf 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior: 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Superior: 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Cómo usar el modo de una mano"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o presiona cualquier parte arriba de la app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar el modo de una mano"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index 8c48876..c3c832b 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Superior 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Superior 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla inferior completa"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usar modo Una mano"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para salir, desliza el dedo hacia arriba desde la parte inferior de la pantalla o toca cualquier zona que haya encima de la aplicación"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar modo Una mano"</string>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index 71f32cd..4722c07 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ülemine: 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Ülemine: 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alumine täisekraan"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ühekäerežiimi kasutamine"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Väljumiseks pühkige ekraani alaosast üles või puudutage rakenduse kohal olevat ala"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ühekäerežiimi käivitamine"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index a46095a..e1c2a0d 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Ezarri goialdea % 50en"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Ezarri goialdea % 30en"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ezarri behealdea pantaila osoan"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Esku bakarreko modua erabiltzea"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Irteteko, pasatu hatza pantailaren behealdetik gora edo sakatu aplikazioaren gainaldea"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Abiarazi esku bakarreko modua"</string>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 214330e..5a4871d 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"٪۵۰ بالا"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"٪۳۰ بالا"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"تمامصفحه پایین"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"استفاده از حالت یکدستی"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"برای خارج شدن، از پایین صفحهنمایش تند بهطرف بالا بکشید یا در هر جایی از بالای برنامه که میخواهید ضربه بزنید"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"آغاز «حالت یکدستی»"</string>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index b6972c6..56f444e 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Yläosa 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Yläosa 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alaosa koko näytölle"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Yhden käden moodin käyttö"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Poistu pyyhkäisemällä ylös näytön alareunasta tai napauttamalla sovelluksen yllä"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Käynnistä yhden käden moodi"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 6ed74e4..e48d794 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % dans le haut"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % dans le haut"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Plein écran dans le bas"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode Une main"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'application"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode Une main"</string>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 82f02ce2..301bca6 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Écran du haut à 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Écran du haut à 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Écran du bas en plein écran"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode une main"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran de bas en haut ou appuyez n\'importe où au-dessus de l\'application"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode une main"</string>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index b83be3d..967945c 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50 % arriba"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30 % arriba"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pantalla completa abaixo"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como se usa o modo dunha soa man?"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para saír, pasa o dedo cara arriba desde a parte inferior da pantalla ou toca calquera lugar da zona situada encima da aplicación"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar modo dunha soa man"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index d2e5a82..653e1a69 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"શીર્ષ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"શીર્ષ 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"તળિયાની પૂર્ણ સ્ક્રીન"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"એક-હાથે વાપરો મોડનો ઉપયોગ કરી રહ્યાં છીએ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"બહાર નીકળવા માટે, સ્ક્રીનની નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરો અથવા ઍપના આઇકન પર ગમે ત્યાં ટૅપ કરો"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"એક-હાથે વાપરો મોડ શરૂ કરો"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index ef4c2d9..25b658a 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ऊपर की स्क्रीन को 50% बनाएं"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ऊपर की स्क्रीन को 30% बनाएं"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"नीचे की स्क्रीन को फ़ुल स्क्रीन बनाएं"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"वन-हैंडेड मोड का इस्तेमाल करना"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"इस मोड से बाहर निकलने के लिए, स्क्रीन के सबसे निचले हिस्से से ऊपर की ओर स्वाइप करें या ऐप्लिकेशन के बाहर कहीं भी टैप करें"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"वन-हैंडेड मोड चालू करें"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index bbd95f7..57fd2fa 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gornji zaslon na 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gornji zaslon na 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Donji zaslon u cijeli zaslon"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korištenje načina rada jednom rukom"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Za izlaz prijeđite prstom od dna zaslona prema gore ili dodirnite bio gdje iznad aplikacije"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pokretanje načina rada jednom rukom"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index 1bb2213..ee7ff86 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Felső 50%-ra"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Felső 30%-ra"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Alsó teljes képernyőre"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Egykezes mód használata"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"A kilépéshez csúsztasson felfelé a képernyő aljáról, vagy koppintson az alkalmazás felett a képernyő bármelyik részére"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Egykezes mód indítása"</string>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index 223ecf1..4538cec 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Վերևի էկրանը՝ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Վերևի էկրանը՝ 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ներքևի էկրանը՝ լիաէկրան"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ինչպես օգտվել մեկ ձեռքի ռեժիմից"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Դուրս գալու համար մատը սահեցրեք էկրանի ներքևից վերև կամ հպեք հավելվածի վերևում որևէ տեղ։"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Գործարկել մեկ ձեռքի ռեժիմը"</string>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index 72dece4..6880d53 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Atas 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Layar penuh di bawah"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Menggunakan mode satu tangan"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Untuk keluar, geser layar dari bawah ke atas atau ketuk di mana saja di atas aplikasi"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Mulai mode satu tangan"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index aaa4846..23c93a3 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Efri 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Efri 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Neðri á öllum skjánum"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Notkun einhentrar stillingar"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Til að loka skaltu strjúka upp frá neðri hluta skjásins eða ýta hvar sem er fyrir ofan forritið"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ræsa einhenta stillingu"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 98d1b45..ece92cf 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Schermata superiore al 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Schermata superiore al 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Schermata inferiore a schermo intero"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Usare la modalità a una mano"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Per uscire, scorri verso l\'alto dalla parte inferiore dello schermo oppure tocca un punto qualsiasi sopra l\'app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Avvia la modalità a una mano"</string>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 9d7c2c0..948137f 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"עליון 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"למעלה 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"מסך תחתון מלא"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"איך להשתמש בתכונה \'מצב שימוש ביד אחת\'"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"כדי לצאת, יש להחליק למעלה מתחתית המסך או להקיש במקום כלשהו במסך מעל האפליקציה"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"הפעלה של מצב שימוש ביד אחת"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 548d63c..9fdb861 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"上 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"上 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"下部全画面"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"片手モードの使用"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"終了するには、画面を下から上にスワイプするか、アプリの任意の場所をタップします"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"片手モードを開始します"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 1b359b39e6..d827f19 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ზედა ეკრანი — 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ზედა ეკრანი — 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ქვედა ნაწილის სრულ ეკრანზე გაშლა"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ცალი ხელის რეჟიმის გამოყენება"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"გასასვლელად გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ ან შეეხეთ ნებისმიერ ადგილას აპის ზემოთ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ცალი ხელის რეჟიმის დაწყება"</string>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index 2ce45e2..b0b0375 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% жоғарғы жақта"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% жоғарғы жақта"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Төменгісін толық экранға шығару"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Бір қолмен енгізу режимін пайдалану"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Шығу үшін экранның төменгі жағынан жоғары қарай сырғытыңыз немесе қолданбаның үстінен кез келген жерден түртіңіз."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Бір қолмен енгізу режимін іске қосу"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index de87261..3783067 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ខាងលើ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ខាងលើ 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"អេក្រង់ពេញខាងក្រោម"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"កំពុងប្រើមុខងារប្រើដៃម្ខាង"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ដើម្បីចាកចេញ សូមអូសឡើងលើពីផ្នែកខាងក្រោមអេក្រង់ ឬចុចផ្នែកណាមួយនៅខាងលើកម្មវិធី"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ចាប់ផ្ដើមមុខងារប្រើដៃម្ខាង"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 9b47a00..45fd76f 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% ಮೇಲಕ್ಕೆ"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% ಮೇಲಕ್ಕೆ"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ಕೆಳಗಿನ ಪೂರ್ಣ ಪರದೆ"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ಒಂದು ಕೈ ಮೋಡ್ ಬಳಸುವುದರ ಬಗ್ಗೆ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ನಿರ್ಗಮಿಸಲು, ಸ್ಕ್ರೀನ್ನ ಕೆಳಗಿನಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ ಅಥವಾ ಆ್ಯಪ್ನ ಮೇಲೆ ಎಲ್ಲಿಯಾದರೂ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ಒಂದು ಕೈ ಮೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 6fa6386..0ee1feb 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"위쪽 화면 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"위쪽 화면 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"아래쪽 화면 전체화면"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"한 손 사용 모드 사용하기"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"화면 하단에서 위로 스와이프하거나 앱 상단을 탭하여 종료합니다."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"한 손 사용 모드 시작"</string>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index c2368d2..6798f49 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Үстүнкү экранды 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Үстүнкү экранды 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ылдыйкы экранды толук экран режимине өткөрүү"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Бир кол режимин колдонуу"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чыгуу үчүн экранды ылдый жагынан өйдө сүрүңүз же колдонмонун өйдө жагын басыңыз"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Бир кол режимин баштоо"</string>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index f4ff5e5..b758c0c 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ເທິງສຸດ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ເທິງສຸດ 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ເຕັມໜ້າຈໍລຸ່ມສຸດ"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ກຳລັງໃຊ້ໂໝດມືດຽວ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ເພື່ອອອກ, ໃຫ້ປັດຂຶ້ນຈາກລຸ່ມສຸດຂອງໜ້າຈໍ ຫຼື ແຕະບ່ອນໃດກໍໄດ້ຢູ່ເໜືອແອັບ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ເລີ່ມໂໝດມືດຽວ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 0d276ec..84f4264 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Viršutinis ekranas 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Viršutinis ekranas 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Apatinis ekranas viso ekrano režimu"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Vienos rankos režimo naudojimas"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Jei norite išeiti, perbraukite aukštyn nuo ekrano apačios arba palieskite bet kur virš programos"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pradėti vienos rankos režimą"</string>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index c6ccc27..bac75e3 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Augšdaļa 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Augšdaļa 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Apakšdaļu pa visu ekrānu"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Vienas rokas režīma izmantošana"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Lai izietu, velciet augšup no ekrāna apakšdaļas vai pieskarieties jebkurā vietā virs lietotnes"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pāriet vienas rokas režīmā"</string>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 526093d..b96f8a1 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горниот 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Горниот 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Долниот на цел екран"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Користење на режимот со една рака"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"За да излезете, повлечете нагоре од дното на екранот или допрете каде било над апликацијата"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Започни го режимот со една рака"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index bd3d94e..f67e7ab 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"മുകളിൽ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"മുകളിൽ 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"താഴെ പൂർണ്ണ സ്ക്രീൻ"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ഒറ്റക്കൈ മോഡ് എങ്ങനെ ഉപയോഗിക്കാം"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"പുറത്ത് കടക്കാൻ, സ്ക്രീനിന്റെ ചുവടെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക അല്ലെങ്കിൽ ആപ്പിന് മുകളിലായി എവിടെയെങ്കിലും ടാപ്പ് ചെയ്യുക"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ഒറ്റക്കൈ മോഡ് ആരംഭിച്ചു"</string>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index bd1e31e..00846ef 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Дээд 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Дээд 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Доод бүтэн дэлгэц"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Нэг гарын горимыг ашиглаж байна"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Гарахын тулд дэлгэцийн доод хэсгээс дээш шударч эсвэл апп дээр хүссэн газраа товшино уу"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Нэг гарын горимыг эхлүүлэх"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 01bf949..888e8dd 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"शीर्ष 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"शीर्ष 10"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"तळाशी फुल स्क्रीन"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"एकहाती मोड वापरणे"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"बाहेर पडण्यासाठी स्क्रीनच्या खालून वरच्या दिशेने स्वाइप करा किंवा ॲपवर कोठेही टॅप करा"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"एकहाती मोड सुरू करा"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index c9dc2eb..d4a659b 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Atas 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Atas 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Skrin penuh bawah"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Menggunakan mod sebelah tangan"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Untuk keluar, leret ke atas daripada bahagian bawah skrin atau ketik pada mana-mana di bahagian atas apl"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Mulakan mod sebelah tangan"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 5f0a412..6f8b3c7 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"အပေါ်ဘက် မျက်နှာပြင် ၅၀%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"အပေါ်ဘက် မျက်နှာပြင် ၃၀%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"အောက်ခြေ မျက်နှာပြင်အပြည့်"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"လက်တစ်ဖက်သုံးမုဒ် အသုံးပြုခြင်း"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ထွက်ရန် ဖန်သားပြင်၏အောက်ခြေမှ အပေါ်သို့ပွတ်ဆွဲပါ သို့မဟုတ် အက်ပ်အပေါ်ဘက် မည်သည့်နေရာတွင်မဆို တို့ပါ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"လက်တစ်ဖက်သုံးမုဒ်ကို စတင်လိုက်သည်"</string>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index e84c95d..1ea3907 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Sett størrelsen på den øverste delen av skjermen til 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Sett størrelsen på den øverste delen av skjermen til 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Utvid den nederste delen av skjermen til hele skjermen"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Bruk av enhåndsmodus"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"For å avslutte, sveip opp fra bunnen av skjermen eller trykk hvor som helst over appen"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Start enhåndsmodus"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index f1997be..fd3485c 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"माथिल्लो भाग ५०%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"माथिल्लो भाग ३०%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"तल्लो भाग फुल स्क्रिन"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"एक हाते मोड प्रयोग गरिँदै छ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"बाहिर निस्कन, स्क्रिनको पुछारबाट माथितिर स्वाइप गर्नुहोस् वा एपभन्दा माथि जुनसुकै ठाउँमा ट्याप गर्नुहोस्"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"एक हाते मोड सुरु गर्नुहोस्"</string>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index ba37ca7..c3ab297 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Bovenste scherm 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Bovenste scherm 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Onderste scherm op volledig scherm"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Bediening met 1 hand gebruiken"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Als je wilt afsluiten, swipe je omhoog vanaf de onderkant van het scherm of tik je ergens boven de app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Bediening met 1 hand starten"</string>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index 7ae576a..90c90d3 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ଉପର ଆଡ଼କୁ 50% କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ଉପର ଆଡ଼କୁ 30% କରନ୍ତୁ"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ତଳ ଅଂଶର ପୂର୍ଣ୍ଣ ସ୍କ୍ରୀନ୍"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ଏକ-ହାତ ମୋଡ୍ ବ୍ୟବହାର କରି"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ବାହାରି ଯିବା ପାଇଁ, ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ୍ କରନ୍ତୁ କିମ୍ବା ଆପରେ ଯେ କୌଣସି ସ୍ଥାନରେ ଟାପ୍ କରନ୍ତୁ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ଏକ-ହାତ ମୋଡ୍ ଆରମ୍ଭ କରନ୍ତୁ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 6784354..b27e034 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ਉੱਪਰ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ਉੱਪਰ 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"ਹੇਠਾਂ ਪੂਰੀ ਸਕ੍ਰੀਨ"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ਇੱਕ ਹੱਥ ਮੋਡ ਵਰਤਣਾ"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"ਬਾਹਰ ਜਾਣ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ ਜਾਂ ਐਪ \'ਤੇ ਕਿਤੇ ਵੀ ਟੈਪ ਕਰੋ"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ਇੱਕ ਹੱਥ ਮੋਡ ਸ਼ੁਰੂ ਕਰੋ"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 962ba0d..6d00aaa 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% górnej części ekranu"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% górnej części ekranu"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolna część ekranu na pełnym ekranie"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korzystanie z trybu jednej ręki"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Aby zamknąć, przesuń palcem z dołu ekranu w górę lub kliknij dowolne miejsce nad aplikacją"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Uruchom tryb jednej ręki"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index a94d157..b2f3121 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Parte superior a 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Parte inferior em tela cheia"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 345a89e..3f3be4b 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"50% no ecrã superior"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"30% no ecrã superior"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ecrã inferior inteiro"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utilize o modo para uma mão"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize rapidamente para cima a partir da parte inferior do ecrã ou toque em qualquer ponto acima da app."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index a94d157..b2f3121 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Parte superior a 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Parte superior a 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Parte inferior em tela cheia"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Como usar o modo para uma mão"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para sair, deslize de baixo para cima na tela ou toque em qualquer lugar acima do app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Iniciar o modo para uma mão"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index fa9a074..402ef6b 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Partea de sus: 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Partea de sus: 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Partea de jos pe ecran complet"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Folosirea modului cu o mână"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pentru a ieși, glisează în sus din partea de jos a ecranului sau atinge oriunde deasupra ferestrei aplicației"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Activează modul cu o mână"</string>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index b9c5951..dd59545 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхний на 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Верхний на 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Нижний во весь экран"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Использование режима управления одной рукой"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Чтобы выйти, проведите по экрану снизу вверх или коснитесь области за пределами приложения."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Запустить режим управления одной рукой"</string>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index c2d7c1a..91342c4 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ඉහළම 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ඉහළම 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"පහළ පූර්ණ තිරය"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"තනි-අත් ප්රකාරය භාවිත කරමින්"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"පිටවීමට, තිරයේ පහළ සිට ඉහළට ස්වයිප් කරන්න හෝ යෙදුමට ඉහළින් ඕනෑම තැනක තට්ටු කරන්න"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"තනි අත් ප්රකාරය ආරම්භ කරන්න"</string>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index 7bb415d..b9199c2 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Horná – 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Horná – 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Dolná – na celú obrazovku"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Používanie režimu jednej ruky"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ukončíte potiahnutím z dolnej časti obrazovky nahor alebo klepnutím kdekoľvek nad aplikáciu"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Spustiť režim jednej ruky"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index a64c52f..0fd44f0 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Zgornji 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Zgornji 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Spodnji v celozaslonski način"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Uporaba enoročnega načina"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Za izhod povlecite z dna zaslona navzgor ali se dotaknite na poljubnem mestu nad aplikacijo"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Zagon enoročnega načina"</string>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index f4fc5e7..00e63d2 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Lart 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Lart 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ekrani i plotë poshtë"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Po përdor modalitetin e përdorimit me një dorë"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Për të dalë, rrëshqit lart nga fundi i ekranit ose trokit diku mbi aplikacion"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Modaliteti i përdorimit me një dorë"</string>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 743f9a8..d0f689c 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Горњи екран 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Горњи екран 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Режим целог екрана за доњи екран"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Коришћење режима једном руком"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Да бисте изашли, превуците нагоре од дна екрана или додирните било где изнад апликације"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Покрените режим једном руком"</string>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 6562f9d..e3827d6 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Övre 50 %"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Övre 30 %"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Helskärm på nedre skärm"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Använda enhandsläge"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Avsluta genom att svepa uppåt från skärmens nederkant eller trycka ovanför appen"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Starta enhandsläge"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 56a1a32..8c2f2ad 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Juu 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Juu 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Skrini nzima ya chini"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Kutumia hali ya kutumia kwa mkono mmoja"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ili ufunge, telezesha kidole juu kutoka sehemu ya chini ya skrini au uguse mahali popote juu ya programu"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Anzisha hali ya kutumia kwa mkono mmoja"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 2f54264..4732e39 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"மேலே 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"மேலே 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"கீழ்ப்புறம் முழுத் திரை"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ஒற்றைக் கைப் பயன்முறையைப் பயன்படுத்துதல்"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"வெளியேற, திரையின் கீழிருந்து மேல்நோக்கி ஸ்வைப் செய்யவும் அல்லது ஆப்ஸுக்கு மேலே ஏதேனும் ஓர் இடத்தில் தட்டவும்"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ஒற்றைக் கைப் பயன்முறையைத் தொடங்கும்"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 6e49865..093a848 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ఎగువ 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ఎగువ 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"దిగువ ఫుల్-స్క్రీన్"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"వన్-హ్యాండెడ్ మోడ్ను ఉపయోగించడం"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"నిష్క్రమించడానికి, స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి లేదా యాప్ పైన ఎక్కడైనా ట్యాప్ చేయండి"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"వన్-హ్యాండెడ్ మోడ్ను ప్రారంభిస్తుంది"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index a9100e8..f7c810b 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"ด้านบน 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"ด้านบน 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"เต็มหน้าจอด้านล่าง"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"การใช้โหมดมือเดียว"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"หากต้องการออก ให้เลื่อนขึ้นจากด้านล่างของหน้าจอหรือแตะที่ใดก็ได้เหนือแอป"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"เริ่มโหมดมือเดียว"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 7a4232c..4660d6d 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Gawing 50% ang nasa itaas"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Gawing 30% ang nasa itaas"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"I-full screen ang nasa ibaba"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Paggamit ng one-hand mode"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Para lumabas, mag-swipe pataas mula sa ibaba ng screen o mag-tap kahit saan sa itaas ng app"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Simulan ang one-hand mode"</string>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 55e36dd..6fdfe56 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Üstte %50"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Üstte %30"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Altta tam ekran"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Tek el modunu kullanma"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Çıkmak için ekranın alt kısmından yukarı kaydırın veya uygulamanın üzerinde herhangi bir yere dokunun"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Tek el modunu başlat"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 2b04b90..c5cfd02 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Верхнє вікно на 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Верхнє вікно на 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Нижнє вікно на весь екран"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Як користуватися режимом керування однією рукою"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Щоб вийти, проведіть пальцем по екрану знизу вгору або торкніться екрана над додатком"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Увімкнути режим керування однією рукою"</string>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 8f818cb..9c138d9 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"اوپر %50"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"اوپر %30"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"نچلی فل اسکرین"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"ایک ہاتھ کی وضع کا استعمال کرنا"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"باہر نکلنے کیلئے، اسکرین کے نیچے سے اوپر کی طرف سوائپ کریں یا ایپ کے اوپر کہیں بھی تھپتھپائیں"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"ایک ہاتھ کی وضع شروع کریں"</string>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index a666cd9..907c5bd 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Tepada 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Tepada 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Pastda to‘liq ekran"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ixcham rejimdan foydalanish"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Chiqish uchun ekran pastidan tepaga suring yoki ilovaning tepasidagi istalgan joyga bosing."</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Ixcham rejimni ishga tushirish"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index a78e01c..211e231 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Trên 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Trên 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Toàn màn hình phía dưới"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Cách dùng chế độ một tay"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Để thoát, hãy vuốt lên từ cuối màn hình hoặc nhấn vào vị trí bất kỳ phía trên ứng dụng"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Bắt đầu chế độ một tay"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index bb3cf39..3d0637a 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"顶部 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"顶部 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"底部全屏"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用单手模式"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"如需退出,请从屏幕底部向上滑动,或点按应用上方的任意位置"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"启动单手模式"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 724f012..c4df086 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"頂部 50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"頂部 30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"底部全螢幕"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用單手模式"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"如要退出,請從螢幕底部向上滑動,或輕按應用程式上方的任何位置"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"開始單手模式"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 082049f..2d9e7f3 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"以 50% 的螢幕空間顯示頂端畫面"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"以 30% 的螢幕空間顯示頂端畫面"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"以全螢幕顯示底部畫面"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"使用單手模式"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"如要退出,請從螢幕底部向上滑動,或輕觸應用程式上方的任何位置"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"啟動單手模式"</string>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 18f722c..5c60056 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -47,6 +47,14 @@
<string name="accessibility_action_divider_top_50" msgid="8649582798829048946">"Okuphezulu okungu-50%"</string>
<string name="accessibility_action_divider_top_30" msgid="3572788224908570257">"Okuphezulu okungu-30%"</string>
<string name="accessibility_action_divider_bottom_full" msgid="2831868345092314060">"Ngaphansi kwesikrini esigcwele"</string>
+ <!-- no translation found for accessibility_split_left (1713683765575562458) -->
+ <skip />
+ <!-- no translation found for accessibility_split_right (8441001008181296837) -->
+ <skip />
+ <!-- no translation found for accessibility_split_top (2789329702027147146) -->
+ <skip />
+ <!-- no translation found for accessibility_split_bottom (8694551025220868191) -->
+ <skip />
<string name="one_handed_tutorial_title" msgid="4583241688067426350">"Ukusebenzisa imodi yesandla esisodwa"</string>
<string name="one_handed_tutorial_description" msgid="3486582858591353067">"Ukuze uphume, swayipha ngaphezulu kusuka ngezansi kwesikrini noma thepha noma kuphi ngenhla kohlelo lokusebenza"</string>
<string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Qalisa imodi yesandla esisodwa"</string>
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 adc65dd..63992329 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -225,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] -->
@@ -235,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/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
index 9e0a48b..e2106e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java
@@ -216,7 +216,6 @@
args.argi1 = homeTaskVisible ? 1 : 0;
args.argi2 = clearedTask ? 1 : 0;
args.argi3 = wasVisible ? 1 : 0;
- mMainHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT);
mMainHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget();
}
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 94db878..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
@@ -206,6 +206,7 @@
TvPipMenuController tvPipMenuController,
SyncTransactionQueue syncTransactionQueue,
TvPipBoundsState tvPipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipTransitionState pipTransitionState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
PipAnimationController pipAnimationController,
@@ -217,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 1135aa3..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
@@ -442,6 +442,7 @@
SyncTransactionQueue syncTransactionQueue,
PipTransitionState pipTransitionState,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipMenuController menuPhoneController,
PipAnimationController pipAnimationController,
@@ -453,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
@@ -471,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/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/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 61da10b..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
@@ -311,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
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 aad27b9..f11836e 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
@@ -79,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;
@@ -126,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;
@@ -313,6 +316,7 @@
@NonNull SyncTransactionQueue syncTransactionQueue,
@NonNull PipTransitionState pipTransitionState,
@NonNull PipBoundsState pipBoundsState,
+ @NonNull PipSizeSpecHandler pipSizeSpecHandler,
@NonNull PipBoundsAlgorithm boundsHandler,
@NonNull PipMenuController pipMenuController,
@NonNull PipAnimationController pipAnimationController,
@@ -328,6 +332,7 @@
mSyncTransactionQueue = syncTransactionQueue;
mPipTransitionState = pipTransitionState;
mPipBoundsState = pipBoundsState;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
mPipBoundsAlgorithm = boundsHandler;
mPipMenuController = pipMenuController;
mPipTransitionController = pipTransitionController;
@@ -645,7 +650,6 @@
}
mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
- mPipUiEventLoggerLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
// If the displayId of the task is different than what PipBoundsHandler has, then update
// it. This is possible if we entered PiP on an external display.
@@ -654,6 +658,17 @@
mOnDisplayIdChangeCallback.accept(info.displayId);
}
+ // UiEvent logging.
+ final PipUiEventLogger.PipUiEventEnum uiEventEnum;
+ if (isLaunchIntoPipTask()) {
+ uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER_CONTENT_PIP;
+ } else if (mPipTransitionState.getInSwipePipToHomeTransition()) {
+ uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_AUTO_ENTER;
+ } else {
+ uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER;
+ }
+ mPipUiEventLoggerLogger.log(uiEventEnum);
+
if (mPipTransitionState.getInSwipePipToHomeTransition()) {
if (!mWaitForFixedRotation) {
onEndOfSwipePipToHomeTransition();
@@ -1404,7 +1419,6 @@
@PipAnimationController.TransitionDirection int direction,
@PipAnimationController.AnimationType int type) {
final Rect preResizeBounds = new Rect(mPipBoundsState.getBounds());
- final boolean isPipTopLeft = isPipTopLeft();
mPipBoundsState.setBounds(destinationBounds);
if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
removePipImmediately();
@@ -1450,9 +1464,11 @@
null /* callback */, false /* withStartDelay */);
});
} else {
- applyFinishBoundsResize(wct, direction, isPipTopLeft);
+ applyFinishBoundsResize(wct, direction, false);
}
} else {
+ final boolean isPipTopLeft =
+ direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && isPipToTopLeft();
applyFinishBoundsResize(wct, direction, isPipTopLeft);
}
@@ -1521,6 +1537,14 @@
return topLeft.contains(mPipBoundsState.getBounds());
}
+ private boolean isPipToTopLeft() {
+ if (!mSplitScreenOptional.isPresent()) {
+ return false;
+ }
+ return mSplitScreenOptional.get().getActivateSplitPosition(mTaskInfo)
+ == SPLIT_POSITION_TOP_OR_LEFT;
+ }
+
/**
* The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
* and can be overridden to restore to an alternate windowing mode.
@@ -1601,7 +1625,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.
@@ -1652,8 +1681,7 @@
final Rect topLeft = new Rect();
final Rect bottomRight = new Rect();
mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
- final boolean isPipTopLeft = isPipTopLeft();
- destinationBoundsOut.set(isPipTopLeft ? topLeft : bottomRight);
+ destinationBoundsOut.set(isPipToTopLeft() ? topLeft : bottomRight);
return true;
}
@@ -1737,6 +1765,11 @@
mSurfaceControlTransactionFactory = factory;
}
+ public boolean isLaunchToSplit(TaskInfo taskInfo) {
+ return mSplitScreenOptional.isPresent()
+ && mSplitScreenOptional.get().isLaunchToSplit(taskInfo);
+ }
+
/**
* Dumps internal states.
*/
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 d9d1009..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
@@ -65,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;
@@ -83,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;
@@ -113,6 +116,7 @@
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
+ PipSizeSpecHandler pipSizeSpecHandler,
PipTransitionState pipTransitionState,
PipMenuController pipMenuController,
PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -123,6 +127,7 @@
pipBoundsAlgorithm, pipAnimationController);
mContext = context;
mPipTransitionState = pipTransitionState;
+ mPipSizeSpecHandler = pipSizeSpecHandler;
mEnterExitAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
@@ -308,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;
@@ -824,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/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
index 513ebba..3e5a19b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
@@ -78,6 +78,12 @@
@UiEvent(doc = "Activity enters picture-in-picture mode")
PICTURE_IN_PICTURE_ENTER(603),
+ @UiEvent(doc = "Activity enters picture-in-picture mode with auto-enter-pip API")
+ PICTURE_IN_PICTURE_AUTO_ENTER(1313),
+
+ @UiEvent(doc = "Activity enters picture-in-picture mode from content-pip API")
+ PICTURE_IN_PICTURE_ENTER_CONTENT_PIP(1314),
+
@UiEvent(doc = "Expands from picture-in-picture to fullscreen")
PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN(604),
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 d86468a..ec34f73 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
@@ -570,8 +570,12 @@
if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
return;
}
- mTouchHandler.getMotionHelper().expandLeavePip(
- clearedTask /* skipAnimation */);
+ if (mPipTaskOrganizer.isLaunchToSplit(task)) {
+ mTouchHandler.getMotionHelper().expandIntoSplit();
+ } else {
+ mTouchHandler.getMotionHelper().expandLeavePip(
+ clearedTask /* skipAnimation */);
+ }
}
});
@@ -1032,7 +1036,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);
}
/**
@@ -1069,7 +1077,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.
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
index e787ed9..d03d075 100644
--- 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
@@ -409,12 +409,6 @@
return new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
}
- /** Get the display layout. */
- @NonNull
- public DisplayLayout getDisplayLayout() {
- return mDisplayLayout;
- }
-
/** Update the display layout. */
public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
mDisplayLayout.set(displayLayout);
@@ -429,12 +423,11 @@
*/
public Rect getInsetBounds() {
Rect insetBounds = new Rect();
- final DisplayLayout displayLayout = getDisplayLayout();
- Rect insets = getDisplayLayout().stableInsets();
+ Rect insets = mDisplayLayout.stableInsets();
insetBounds.set(insets.left + mScreenEdgeInsets.x,
insets.top + mScreenEdgeInsets.y,
- displayLayout.width() - insets.right - mScreenEdgeInsets.x,
- displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
+ mDisplayLayout.width() - insets.right - mScreenEdgeInsets.x,
+ mDisplayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
return insetBounds;
}
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/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 7cb5cf2..c7ad4fd 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
@@ -42,6 +42,7 @@
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
+import android.app.TaskInfo;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
@@ -421,6 +422,14 @@
mStageCoordinator.goToFullscreenFromSplit();
}
+ public boolean isLaunchToSplit(TaskInfo taskInfo) {
+ return mStageCoordinator.isLaunchToSplit(taskInfo);
+ }
+
+ public int getActivateSplitPosition(TaskInfo taskInfo) {
+ return mStageCoordinator.getActivateSplitPosition(taskInfo);
+ }
+
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -583,9 +592,10 @@
}
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;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
@@ -605,9 +615,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
@@ -1037,13 +1047,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 219f87e..0c3eaf0 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
@@ -74,6 +74,7 @@
import android.app.ActivityOptions;
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -124,6 +125,7 @@
import com.android.wm.shell.common.TransactionPool;
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.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
@@ -208,6 +210,36 @@
private DefaultMixedHandler mMixedHandler;
private final Toast mSplitUnsupportedToast;
+ private SplitRequest mSplitRequest;
+
+ class SplitRequest {
+ @SplitPosition
+ int mActivatePosition;
+ int mActivateTaskId;
+ int mActivateTaskId2;
+ Intent mStartIntent;
+ Intent mStartIntent2;
+
+ SplitRequest(int taskId, Intent startIntent, int position) {
+ mActivateTaskId = taskId;
+ mStartIntent = startIntent;
+ mActivatePosition = position;
+ }
+ SplitRequest(Intent startIntent, int position) {
+ mStartIntent = startIntent;
+ mActivatePosition = position;
+ }
+ SplitRequest(Intent startIntent, Intent startIntent2, int position) {
+ mStartIntent = startIntent;
+ mStartIntent2 = startIntent2;
+ mActivatePosition = position;
+ }
+ SplitRequest(int taskId1, int taskId2, int position) {
+ mActivateTaskId = taskId1;
+ mActivateTaskId2 = taskId2;
+ mActivatePosition = position;
+ }
+ }
private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
new SplitWindowManager.ParentContainerCallbacks() {
@@ -393,6 +425,23 @@
setSideStagePosition(sideStagePosition, wct);
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
targetStage.evictAllChildren(evictWct);
+
+ // Apply surface bounds before animation start.
+ SurfaceControl.Transaction startT = mTransactionPool.acquire();
+ if (startT != null) {
+ updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */);
+ startT.apply();
+ mTransactionPool.release(startT);
+ }
+ // reparent the task to an invisible split root will make the activity invisible. Reorder
+ // the root task to front to make the entering transition from pip to split smooth.
+ wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, true);
+ wct.reorder(targetStage.mRootTaskInfo.token, true);
+ wct.setForceTranslucent(targetStage.mRootTaskInfo.token, true);
+ // prevent the fling divider to center transition
+ mIsDropEntering = true;
+
targetStage.addTask(task, wct);
if (ENABLE_SHELL_TRANSITIONS) {
@@ -486,6 +535,10 @@
RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper,
0 /* duration */, 0 /* statusBarTransitionDelay */);
ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
+ // Flag this as a no-user-action launch to prevent sending user leaving event to the current
+ // top activity since it's going to be put into another side of the split. This prevents the
+ // current top activity from going into pip mode due to user leaving event.
+ activityOptions.setApplyNoUserActionFlagForShortcut(true);
activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
try {
LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
@@ -547,7 +600,7 @@
}
}
- if (isEnteringSplit && !openingToSide) {
+ if (isEnteringSplit && !openingToSide && apps != null) {
mMainExecutor.execute(() -> exitSplitScreen(
mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
EXIT_REASON_UNKNOWN));
@@ -587,7 +640,7 @@
if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) {
updateWindowBounds(mSplitLayout, wct);
}
-
+ mSplitRequest = new SplitRequest(intent.getIntent(), position);
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
@@ -686,15 +739,18 @@
addActivityOptions(options1, mSideStage);
wct.startTask(taskId1, options1);
+ mSplitRequest = new SplitRequest(taskId1, taskId2, splitPosition);
startWithLegacyTransition(wct, taskId2, options2, splitPosition, splitRatio, adapter,
instanceId);
}
/** 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) {
@@ -703,15 +759,25 @@
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);
+ mSplitRequest = new SplitRequest(pendingIntent1.getIntent(),
+ pendingIntent2 != null ? pendingIntent2.getIntent() : null, splitPosition);
+ }
+ startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, shortcutInfo2, options2,
+ splitPosition, splitRatio, adapter, instanceId);
}
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
@@ -733,6 +799,7 @@
addActivityOptions(options1, mSideStage);
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+ mSplitRequest = new SplitRequest(taskId, pendingIntent.getIntent(), splitPosition);
startWithLegacyTransition(wct, taskId, options2, splitPosition, splitRatio, adapter,
instanceId);
}
@@ -763,18 +830,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);
}
/**
@@ -784,8 +852,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);
}
@@ -803,21 +872,29 @@
// Set false to avoid record new bounds with old task still on top;
mShouldUpdateRecents = false;
mIsSplitEntering = true;
-
+ if (mSplitRequest == null) {
+ mSplitRequest = new SplitRequest(mainTaskId,
+ mainPendingIntent != null ? mainPendingIntent.getIntent() : null,
+ sidePosition);
+ }
setSideStagePosition(sidePosition, wct);
if (!mMainStage.isActive()) {
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);
@@ -890,6 +967,7 @@
WindowContainerTransaction evictWct) {
mIsSplitEntering = false;
mShouldUpdateRecents = true;
+ mSplitRequest = null;
// If any stage has no child after animation finished, it means that split will display
// nothing, such status will happen if task and intent is same app but not support
// multi-instance, we should exit split and expand that app as full screen.
@@ -1148,8 +1226,8 @@
// Notify recents if we are exiting in a way that breaks the pair, and disable further
// updates to splits in the recents until we enter split again
if (shouldBreakPairedTaskInRecents(exitReason) && mShouldUpdateRecents) {
- recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId());
- recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId());
+ recentTasks.removeSplitPair(mMainStage.getLastVisibleTaskId());
+ recentTasks.removeSplitPair(mSideStage.getLastVisibleTaskId());
}
});
mShouldUpdateRecents = false;
@@ -1723,7 +1801,7 @@
mSplitLayout.init();
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (mLogger.isEnterRequestedByDrag()) {
+ if (mIsDropEntering) {
prepareEnterSplitScreen(wct);
} else {
// TODO (b/238697912) : Add the validation to prevent entering non-recovered status
@@ -1748,6 +1826,7 @@
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
+ mSplitRequest = null;
updateRecentTasksSplitPair();
if (!mLogger.hasStartedSession()) {
@@ -2300,6 +2379,33 @@
mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
}
+ boolean isLaunchToSplit(TaskInfo taskInfo) {
+ return getActivateSplitPosition(taskInfo) != SPLIT_POSITION_UNDEFINED;
+ }
+
+ int getActivateSplitPosition(TaskInfo taskInfo) {
+ if (mSplitRequest == null || taskInfo == null) {
+ return SPLIT_POSITION_UNDEFINED;
+ }
+ if (mSplitRequest.mActivateTaskId != 0
+ && mSplitRequest.mActivateTaskId2 == taskInfo.taskId) {
+ return mSplitRequest.mActivatePosition;
+ }
+ if (mSplitRequest.mActivateTaskId == taskInfo.taskId) {
+ return mSplitRequest.mActivatePosition;
+ }
+ final String packageName1 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent);
+ final String basePackageName = SplitScreenUtils.getPackageName(taskInfo.baseIntent);
+ if (packageName1 != null && packageName1.equals(basePackageName)) {
+ return mSplitRequest.mActivatePosition;
+ }
+ final String packageName2 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent2);
+ if (packageName2 != null && packageName2.equals(basePackageName)) {
+ return mSplitRequest.mActivatePosition;
+ }
+ return SPLIT_POSITION_UNDEFINED;
+ }
+
/** Synchronize split-screen state with transition and make appropriate preparations. */
public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index a841b7f..0359761 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -92,6 +92,7 @@
protected SurfaceControl mDimLayer;
protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
+ private int mLastVisibleTaskId = INVALID_TASK_ID;
// TODO(b/204308910): Extracts SplitDecorManager related code to common package.
private SplitDecorManager mSplitDecorManager;
@@ -123,6 +124,13 @@
}
/**
+ * Returns the last visible task's id.
+ */
+ int getLastVisibleTaskId() {
+ return mLastVisibleTaskId;
+ }
+
+ /**
* Returns the top visible child task's id.
*/
int getTopVisibleChildTaskId() {
@@ -221,6 +229,9 @@
return;
}
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ if (taskInfo.isVisible && taskInfo.taskId != mLastVisibleTaskId) {
+ mLastVisibleTaskId = taskInfo.taskId;
+ }
mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
taskInfo.isVisible);
if (!ENABLE_SHELL_TRANSITIONS) {
@@ -253,6 +264,9 @@
} else if (mChildrenTaskInfo.contains(taskId)) {
mChildrenTaskInfo.remove(taskId);
mChildrenLeashes.remove(taskId);
+ if (taskId == mLastVisibleTaskId) {
+ mLastVisibleTaskId = INVALID_TASK_ID;
+ }
mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
if (ENABLE_SHELL_TRANSITIONS) {
// Status is managed/synchronized by the transition lifecycle.
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 db6cbdc..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();
}
}
@@ -508,6 +515,13 @@
}
}
+ 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.isProto2Enabled()
@@ -546,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/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 2da4af8..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
@@ -104,8 +104,8 @@
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,
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/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 90c4440..2ab7a58 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -174,14 +174,13 @@
void ShaderCache::saveToDiskLocked() {
ATRACE_NAME("ShaderCache::saveToDiskLocked");
- if (mInitialized && mBlobCache && mSavePending) {
+ if (mInitialized && mBlobCache) {
if (mIDHash.size()) {
auto key = sIDKey;
set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size());
}
mBlobCache->writeToFile();
}
- mSavePending = false;
}
void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) {
@@ -224,10 +223,10 @@
}
set(bc, key.data(), keySize, value, valueSize);
- if (!mSavePending && mDeferredSaveDelay > 0) {
+ if (!mSavePending && mDeferredSaveDelayMs > 0) {
mSavePending = true;
std::thread deferredSaveThread([this]() {
- sleep(mDeferredSaveDelay);
+ usleep(mDeferredSaveDelayMs * 1000); // milliseconds to microseconds
std::lock_guard<std::mutex> lock(mMutex);
// Store file on disk if there a new shader or Vulkan pipeline cache size changed.
if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) {
@@ -236,6 +235,7 @@
mTryToStorePipelineCache = false;
mCacheDirty = false;
}
+ mSavePending = false;
});
deferredSaveThread.detach();
}
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 3e0fd51..4e3eb81 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -153,7 +153,8 @@
* pending. Each time a key/value pair is inserted into the cache via
* load, a deferred save is initiated if one is not already pending.
* This will wait some amount of time and then trigger a save of the cache
- * contents to disk.
+ * contents to disk, unless mDeferredSaveDelayMs is 0 in which case saving
+ * is disabled.
*/
bool mSavePending = false;
@@ -163,9 +164,11 @@
size_t mObservedBlobValueSize = 20 * 1024;
/**
- * The time in seconds to wait before saving newly inserted cache entries.
+ * The time in milliseconds to wait before saving newly inserted cache entries.
+ *
+ * WARNING: setting this to 0 will disable writing the cache to disk.
*/
- unsigned int mDeferredSaveDelay = 4;
+ unsigned int mDeferredSaveDelayMs = 4 * 1000;
/**
* "mMutex" is the mutex used to prevent concurrent access to the member
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 59c914f..4ceb13e 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -278,6 +278,11 @@
mSignal.signal();
}
+void DrawFrameTask::createHintSession(pid_t uiThreadId, pid_t renderThreadId) {
+ if (mHintSessionWrapper) return;
+ mHintSessionWrapper.emplace(uiThreadId, renderThreadId);
+}
+
DrawFrameTask::HintSessionWrapper::HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId) {
if (!Properties::useHintManager) return;
if (uiThreadId < 0 || renderThreadId < 0) return;
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index d6fc292..b135a21 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -90,6 +90,8 @@
void forceDrawNextFrame() { mForceDrawFrame = true; }
+ void createHintSession(pid_t uiThreadId, pid_t renderThreadId);
+
private:
class HintSessionWrapper {
public:
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index a44b498..d9b650c 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -38,11 +38,18 @@
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory)
: mRenderThread(RenderThread::getInstance()), mContext(nullptr) {
- mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* {
- return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
+ pid_t uiThreadId = pthread_gettid_np(pthread_self());
+ pid_t renderThreadId = getRenderThreadTid();
+ mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* {
+ CanvasContext* context =
+ CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
+ if (context != nullptr) {
+ mRenderThread.queue().post(
+ [=] { mDrawFrameTask.createHintSession(uiThreadId, renderThreadId); });
+ }
+ return context;
});
- mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode,
- pthread_gettid_np(pthread_self()), getRenderThreadTid());
+ mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode, uiThreadId, renderThreadId);
}
RenderProxy::~RenderProxy() {
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 974d85a..7bcd45c 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+#include <GrDirectContext.h>
+#include <Properties.h>
+#include <SkData.h>
+#include <SkRefCnt.h>
#include <cutils/properties.h>
#include <dirent.h>
#include <errno.h>
@@ -22,9 +26,12 @@
#include <stdlib.h>
#include <sys/types.h>
#include <utils/Log.h>
+
#include <cstdint>
+
#include "FileBlobCache.h"
#include "pipeline/skia/ShaderCache.h"
+#include "tests/common/TestUtils.h"
using namespace android::uirenderer::skiapipeline;
@@ -35,11 +42,38 @@
class ShaderCacheTestUtils {
public:
/**
- * "setSaveDelay" sets the time in seconds to wait before saving newly inserted cache entries.
- * If set to 0, then deferred save is disabled.
+ * Hack to reset all member variables of the given cache to their default / initial values.
+ *
+ * WARNING: this must be kept up to date manually, since ShaderCache's parent disables just
+ * reassigning a new instance.
*/
- static void setSaveDelay(ShaderCache& cache, unsigned int saveDelay) {
- cache.mDeferredSaveDelay = saveDelay;
+ static void reinitializeAllFields(ShaderCache& cache) {
+ ShaderCache newCache = ShaderCache();
+ std::lock_guard<std::mutex> lock(cache.mMutex);
+ // By order of declaration
+ cache.mInitialized = newCache.mInitialized;
+ cache.mBlobCache.reset(nullptr);
+ cache.mFilename = newCache.mFilename;
+ cache.mIDHash.clear();
+ cache.mSavePending = newCache.mSavePending;
+ cache.mObservedBlobValueSize = newCache.mObservedBlobValueSize;
+ cache.mDeferredSaveDelayMs = newCache.mDeferredSaveDelayMs;
+ cache.mTryToStorePipelineCache = newCache.mTryToStorePipelineCache;
+ cache.mInStoreVkPipelineInProgress = newCache.mInStoreVkPipelineInProgress;
+ cache.mNewPipelineCacheSize = newCache.mNewPipelineCacheSize;
+ cache.mOldPipelineCacheSize = newCache.mOldPipelineCacheSize;
+ cache.mCacheDirty = newCache.mCacheDirty;
+ cache.mNumShadersCachedInRam = newCache.mNumShadersCachedInRam;
+ }
+
+ /**
+ * "setSaveDelayMs" sets the time in milliseconds to wait before saving newly inserted cache
+ * entries. If set to 0, then deferred save is disabled, and "saveToDiskLocked" must be called
+ * manually, as seen in the "terminate" testing helper function.
+ */
+ static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) {
+ std::lock_guard<std::mutex> lock(cache.mMutex);
+ cache.mDeferredSaveDelayMs = saveDelayMs;
}
/**
@@ -48,8 +82,9 @@
*/
static void terminate(ShaderCache& cache, bool saveContent) {
std::lock_guard<std::mutex> lock(cache.mMutex);
- cache.mSavePending = saveContent;
- cache.saveToDiskLocked();
+ if (saveContent) {
+ cache.saveToDiskLocked();
+ }
cache.mBlobCache = NULL;
}
@@ -60,6 +95,38 @@
static bool validateCache(ShaderCache& cache, std::vector<T> hash) {
return cache.validateCache(hash.data(), hash.size() * sizeof(T));
}
+
+ /**
+ * Waits until cache::mSavePending is false, checking every 0.1 ms *while the mutex is free*.
+ *
+ * Fails if there was no save pending, or if the cache was already being written to disk, or if
+ * timeoutMs is exceeded.
+ *
+ * Note: timeoutMs only guards against mSavePending getting stuck like in b/268205519, and
+ * cannot protect against mutex-based deadlock. Reaching timeoutMs implies something is broken,
+ * so setting it to a sufficiently large value will not delay execution in the happy state.
+ */
+ static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) {
+ {
+ std::lock_guard<std::mutex> lock(cache.mMutex);
+ ASSERT_TRUE(cache.mSavePending);
+ }
+ bool saving = true;
+ float elapsedMilliseconds = 0;
+ while (saving) {
+ if (elapsedMilliseconds >= timeoutMs) {
+ FAIL() << "Timed out after waiting " << timeoutMs << " ms for a pending save";
+ }
+ // This small (0.1 ms) delay is to avoid working too much while waiting for
+ // deferredSaveThread to take the mutex and start the disk write.
+ const int delayMicroseconds = 100;
+ usleep(delayMicroseconds);
+ elapsedMilliseconds += (float)delayMicroseconds / 1000;
+
+ std::lock_guard<std::mutex> lock(cache.mMutex);
+ saving = cache.mSavePending;
+ }
+ }
};
} /* namespace skiapipeline */
@@ -81,6 +148,18 @@
return false;
}
+/**
+ * Attempts to delete the given file, and asserts that either:
+ * 1. Deletion was successful, OR
+ * 2. The file did not exist.
+ *
+ * Tip: wrap calls to this in ASSERT_NO_FATAL_FAILURE(x) if a test should exit early if this fails.
+ */
+void deleteFileAssertSuccess(const std::string& filePath) {
+ int deleteResult = remove(filePath.c_str());
+ ASSERT_TRUE(0 == deleteResult || ENOENT == errno);
+}
+
inline bool checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) {
return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size() &&
0 == memcmp(shader1->data(), shader2->data(), shader1->size());
@@ -91,6 +170,10 @@
return checkShader(shader, shader2);
}
+inline bool checkShader(const sk_sp<SkData>& shader, const std::string& program) {
+ return checkShader(shader, program.c_str());
+}
+
template <typename T>
bool checkShader(const sk_sp<SkData>& shader, std::vector<T>& program) {
sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size() * sizeof(T));
@@ -101,6 +184,10 @@
shader = SkData::MakeWithCString(program);
}
+void setShader(sk_sp<SkData>& shader, const std::string& program) {
+ setShader(shader, program.c_str());
+}
+
template <typename T>
void setShader(sk_sp<SkData>& shader, std::vector<T>& buffer) {
shader = SkData::MakeWithCopy(buffer.data(), buffer.size() * sizeof(T));
@@ -124,13 +211,13 @@
std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2";
// remove any test files from previous test run
- int deleteFile = remove(cacheFile1.c_str());
- ASSERT_TRUE(0 == deleteFile || ENOENT == errno);
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
std::srand(0);
// read the cache from a file that does not exist
ShaderCache::get().setFilename(cacheFile1.c_str());
- ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); // disable deferred save
+ ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 0); // disable deferred save
ShaderCache::get().initShaderDiskCache();
// read a key - should not be found since the cache is empty
@@ -184,7 +271,8 @@
ASSERT_TRUE(checkShader(outVS2, dataBuffer));
ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
- remove(cacheFile1.c_str());
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
}
TEST(ShaderCacheTest, testCacheValidation) {
@@ -196,13 +284,13 @@
std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2";
// remove any test files from previous test run
- int deleteFile = remove(cacheFile1.c_str());
- ASSERT_TRUE(0 == deleteFile || ENOENT == errno);
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
std::srand(0);
// generate identity and read the cache from a file that does not exist
ShaderCache::get().setFilename(cacheFile1.c_str());
- ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); // disable deferred save
+ ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 0); // disable deferred save
std::vector<uint8_t> identity(1024);
genRandomData(identity);
ShaderCache::get().initShaderDiskCache(
@@ -276,7 +364,81 @@
}
ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
- remove(cacheFile1.c_str());
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile1));
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile2));
+}
+
+using namespace android::uirenderer;
+RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
+ if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) {
+ // RENDERTHREAD_SKIA_PIPELINE_TEST declares both SkiaVK and SkiaGL variants.
+ GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan";
+ }
+ if (!folderExist(getExternalStorageFolder())) {
+ // Don't run the test if external storage folder is not available
+ return;
+ }
+ std::string cacheFile = getExternalStorageFolder() + "/shaderCacheTest";
+ GrDirectContext* grContext = renderThread.getGrContext();
+
+ // Remove any test files from previous test run
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile));
+
+ // The first iteration of this loop is to save an initial VkPipelineCache data blob to disk,
+ // which sets up the second iteration for a common scenario of comparing a "new" VkPipelineCache
+ // blob passed to "store" against the same blob that's already in the persistent cache from a
+ // previous launch. "reinitializeAllFields" is critical to emulate each iteration being as close
+ // to the state of a freshly launched app as possible, as the initial values of member variables
+ // like mInStoreVkPipelineInProgress and mOldPipelineCacheSize are critical to catch issues
+ // such as b/268205519
+ for (int flushIteration = 1; flushIteration <= 2; flushIteration++) {
+ SCOPED_TRACE("Frame flush iteration " + std::to_string(flushIteration));
+ // Reset *all* in-memory data and reload the cache from disk.
+ ShaderCacheTestUtils::reinitializeAllFields(ShaderCache::get());
+ ShaderCacheTestUtils::setSaveDelayMs(ShaderCache::get(), 10); // Delay must be > 0 to save.
+ ShaderCache::get().setFilename(cacheFile.c_str());
+ ShaderCache::get().initShaderDiskCache();
+
+ // 1st iteration: store pipeline data to be read back on a subsequent "boot" of the "app".
+ // 2nd iteration: ensure that an initial frame flush (without storing any shaders) given the
+ // same pipeline data that's already on disk doesn't break the cache.
+ ShaderCache::get().onVkFrameFlushed(grContext);
+ ASSERT_NO_FATAL_FAILURE(ShaderCacheTestUtils::waitForPendingSave(ShaderCache::get()));
+ }
+
+ constexpr char shader1[] = "sassas";
+ constexpr char shader2[] = "someVS";
+ constexpr int numIterations = 3;
+ // Also do n iterations of separate "store some shaders then flush the frame" pairs to just
+ // double-check the cache also doesn't get stuck from that use case.
+ for (int saveIteration = 1; saveIteration <= numIterations; saveIteration++) {
+ SCOPED_TRACE("Shader save iteration " + std::to_string(saveIteration));
+ // Write twice to the in-memory cache, which should start a deferred save with both queued.
+ sk_sp<SkData> inVS;
+ setShader(inVS, shader1 + std::to_string(saveIteration));
+ ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString());
+ setShader(inVS, shader2 + std::to_string(saveIteration));
+ ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString());
+
+ // Simulate flush to also save latest pipeline info.
+ ShaderCache::get().onVkFrameFlushed(grContext);
+ ASSERT_NO_FATAL_FAILURE(ShaderCacheTestUtils::waitForPendingSave(ShaderCache::get()));
+ }
+
+ // Reload from disk to ensure saving succeeded.
+ ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
+ ShaderCache::get().initShaderDiskCache();
+
+ // Read twice, ensure equal to last store.
+ sk_sp<SkData> outVS;
+ ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>());
+ ASSERT_TRUE(checkShader(outVS, shader1 + std::to_string(numIterations)));
+ ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>());
+ ASSERT_TRUE(checkShader(outVS, shader2 + std::to_string(numIterations)));
+
+ // Clean up.
+ ShaderCacheTestUtils::terminate(ShaderCache::get(), false);
+ ASSERT_NO_FATAL_FAILURE(deleteFileAssertSuccess(cacheFile));
}
} // namespace
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/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml
index 03c32ba..41f680e 100644
--- a/packages/CompanionDeviceManager/res/values-eu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml
@@ -17,10 +17,10 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4470785958457506021">"Gailu osagarriaren kudeatzailea"</string>
- <string name="confirmation_title" msgid="3785000297483688997">"Eman <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> atzitzeko baimena <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aplikazioari"</string>
+ <string name="confirmation_title" msgid="3785000297483688997">"Eman <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> erabiltzeko baimena <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aplikazioari"</string>
<string name="profile_name_watch" msgid="576290739483672360">"erlojua"</string>
<string name="chooser_title" msgid="2262294130493605839">"Aukeratu <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> aplikazioak kudeatu beharreko <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
- <string name="summary_watch" msgid="3002344206574997652">"Aplikazio hau <xliff:g id="DEVICE_NAME">%1$s</xliff:g> kudeatzeko beharrezkoa da. Jakinarazpenekin interakzioan aritzeko eta telefonoa, SMSak, kontaktuak, egutegia, deien erregistroa eta inguruko gailuak atzitzeko baimenak izango ditu <xliff:g id="APP_NAME">%2$s</xliff:g> aplikazioak."</string>
+ <string name="summary_watch" msgid="3002344206574997652">"Aplikazio hau <xliff:g id="DEVICE_NAME">%1$s</xliff:g> kudeatzeko beharrezkoa da. Jakinarazpenekin interakzioan aritzeko eta telefonoa, SMSak, kontaktuak, egutegia, deien erregistroa eta inguruko gailuak erabiltzeko baimenak izango ditu <xliff:g id="APP_NAME">%2$s</xliff:g> aplikazioak."</string>
<string name="permission_apps" msgid="6142133265286656158">"Aplikazioak"</string>
<string name="permission_apps_summary" msgid="798718816711515431">"Igorri zuzenean telefonoko aplikazioak"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Eman informazioa telefonotik hartzeko baimena <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aplikazioari"</string>
@@ -28,21 +28,21 @@
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Gailu batetik bestera aplikazioak igortzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> gailuaren izenean"</string>
<string name="title_automotive_projection" msgid="3296005598978412847"></string>
<string name="summary_automotive_projection" msgid="8683801274662496164"></string>
- <string name="title_computer" msgid="4693714143506569253">"Eman telefonoko informazio hau atzitzeko baimena <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aplikazioari"</string>
+ <string name="title_computer" msgid="4693714143506569253">"Eman telefonoko informazio hau erabiltzeko baimena <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aplikazioari"</string>
<string name="summary_computer" msgid="3798467601598297062"></string>
<string name="permission_notification" msgid="693762568127741203">"Jakinarazpenak"</string>
<string name="permission_notification_summary" msgid="884075314530071011">"Jakinarazpen guztiak irakur ditzake; besteak beste, kontaktuak, mezuak, argazkiak eta antzeko informazioa"</string>
<string name="permission_storage" msgid="6831099350839392343">"Argazkiak eta multimedia-edukia"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
<string name="helper_title_computer" msgid="4671071173916176037">"Google Play Services"</string>
- <string name="helper_summary_computer" msgid="9050724687678157852">"Telefonoko argazkiak, multimedia-edukia eta jakinarazpenak atzitzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> gailuaren izenean"</string>
+ <string name="helper_summary_computer" msgid="9050724687678157852">"Telefonoko argazkiak, multimedia-edukia eta jakinarazpenak erabiltzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> gailuaren izenean"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"gailua"</string>
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Eman baimena"</string>
<string name="consent_no" msgid="2640796915611404382">"Ez eman baimenik"</string>
<string name="consent_back" msgid="2560683030046918882">"Atzera"</string>
<string name="permission_sync_confirmation_title" msgid="667074294393493186">"Transferitu aplikazio-baimenak erlojura"</string>
- <string name="permission_sync_summary" msgid="8873391306499120778">"Erlojua errazago konfiguratzeko, konfigurazio-prozesua abian zen bitartean erlojuan instalatutako aplikazioek telefonoak darabiltzan baimen berak erabiliko dituzte.\n\n Baliteke baimen horien artean erlojuaren mikrofonoa eta kokapena atzitzeko baimenak egotea."</string>
+ <string name="permission_sync_summary" msgid="8873391306499120778">"Erlojua errazago konfiguratzeko, konfigurazio-prozesua abian zen bitartean erlojuan instalatutako aplikazioek telefonoak darabiltzan baimen berak erabiliko dituzte.\n\n Baliteke baimen horien artean erlojuaren mikrofonoa eta kokapena erabiltzeko baimenak egotea."</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Aplikazioaren ikonoa"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Informazio gehiagorako botoia"</string>
</resources>
diff --git a/packages/PrintSpooler/res/values-ca/strings.xml b/packages/PrintSpooler/res/values-ca/strings.xml
index 4ee4323..483a522 100644
--- a/packages/PrintSpooler/res/values-ca/strings.xml
+++ b/packages/PrintSpooler/res/values-ca/strings.xml
@@ -56,7 +56,7 @@
<string name="print_select_printer" msgid="7388760939873368698">"Selecciona una impressora"</string>
<string name="print_forget_printer" msgid="5035287497291910766">"Oblida la impressora"</string>
<plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
- <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> printers found</item>
+ <item quantity="many">S\'han trobat <xliff:g id="COUNT_1">%1$s</xliff:g> impressores</item>
<item quantity="other">S\'han trobat <xliff:g id="COUNT_1">%1$s</xliff:g> impressores</item>
<item quantity="one">S\'ha trobat <xliff:g id="COUNT_0">%1$s</xliff:g> impressora</item>
</plurals>
@@ -77,7 +77,7 @@
<string name="disabled_services_title" msgid="7313253167968363211">"Serveis desactivats"</string>
<string name="all_services_title" msgid="5578662754874906455">"Tots els serveis"</string>
<plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
- <item quantity="many">Install to discover <xliff:g id="COUNT_1">%1$s</xliff:g> printers</item>
+ <item quantity="many">Instal·la\'l per detectar <xliff:g id="COUNT_1">%1$s</xliff:g> impressores</item>
<item quantity="other">Instal·la\'l per detectar <xliff:g id="COUNT_1">%1$s</xliff:g> impressores</item>
<item quantity="one">Instal·la\'l per detectar <xliff:g id="COUNT_0">%1$s</xliff:g> impressora</item>
</plurals>
diff --git a/packages/PrintSpooler/res/values-es-rUS/strings.xml b/packages/PrintSpooler/res/values-es-rUS/strings.xml
index 90c1937..476614b 100644
--- a/packages/PrintSpooler/res/values-es-rUS/strings.xml
+++ b/packages/PrintSpooler/res/values-es-rUS/strings.xml
@@ -56,7 +56,7 @@
<string name="print_select_printer" msgid="7388760939873368698">"Seleccionar impresora"</string>
<string name="print_forget_printer" msgid="5035287497291910766">"No recordar impresora"</string>
<plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
- <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> printers found</item>
+ <item quantity="many">Se encontraron <xliff:g id="COUNT_1">%1$s</xliff:g> impresoras.</item>
<item quantity="other">Se encontraron <xliff:g id="COUNT_1">%1$s</xliff:g> impresoras.</item>
<item quantity="one">Se encontró <xliff:g id="COUNT_0">%1$s</xliff:g> impresora.</item>
</plurals>
@@ -77,7 +77,7 @@
<string name="disabled_services_title" msgid="7313253167968363211">"Servicios inhabilitados"</string>
<string name="all_services_title" msgid="5578662754874906455">"Todos los servicios"</string>
<plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
- <item quantity="many">Install to discover <xliff:g id="COUNT_1">%1$s</xliff:g> printers</item>
+ <item quantity="many">Instala para ver <xliff:g id="COUNT_1">%1$s</xliff:g> impresoras</item>
<item quantity="other">Instala para ver <xliff:g id="COUNT_1">%1$s</xliff:g> impresoras</item>
<item quantity="one">Instala para ver <xliff:g id="COUNT_0">%1$s</xliff:g> impresora</item>
</plurals>
diff --git a/packages/PrintSpooler/res/values-es/strings.xml b/packages/PrintSpooler/res/values-es/strings.xml
index 18e56db..507b2a7 100644
--- a/packages/PrintSpooler/res/values-es/strings.xml
+++ b/packages/PrintSpooler/res/values-es/strings.xml
@@ -56,7 +56,7 @@
<string name="print_select_printer" msgid="7388760939873368698">"Seleccionar impresora"</string>
<string name="print_forget_printer" msgid="5035287497291910766">"Olvidar impresora"</string>
<plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
- <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> printers found</item>
+ <item quantity="many">Se han encontrado <xliff:g id="COUNT_1">%1$s</xliff:g> impresoras</item>
<item quantity="other">Se han encontrado <xliff:g id="COUNT_1">%1$s</xliff:g> impresoras</item>
<item quantity="one">Se ha encontrado <xliff:g id="COUNT_0">%1$s</xliff:g> impresora</item>
</plurals>
@@ -77,7 +77,7 @@
<string name="disabled_services_title" msgid="7313253167968363211">"Servicios inhabilitados"</string>
<string name="all_services_title" msgid="5578662754874906455">"Todos los servicios"</string>
<plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
- <item quantity="many">Install to discover <xliff:g id="COUNT_1">%1$s</xliff:g> printers</item>
+ <item quantity="many">Instalar para descubrir <xliff:g id="COUNT_1">%1$s</xliff:g> impresoras</item>
<item quantity="other">Instalar para descubrir <xliff:g id="COUNT_1">%1$s</xliff:g> impresoras</item>
<item quantity="one">Instalar para descubrir <xliff:g id="COUNT_0">%1$s</xliff:g> impresora</item>
</plurals>
diff --git a/packages/PrintSpooler/res/values-fr-rCA/strings.xml b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
index 082c148..5298f8b 100644
--- a/packages/PrintSpooler/res/values-fr-rCA/strings.xml
+++ b/packages/PrintSpooler/res/values-fr-rCA/strings.xml
@@ -57,7 +57,7 @@
<string name="print_forget_printer" msgid="5035287497291910766">"Supprimer l\'imprimante"</string>
<plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
<item quantity="one"><xliff:g id="COUNT_1">%1$s</xliff:g> imprimante trouvée</item>
- <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> printers found</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> imprimante trouvées</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> imprimante trouvées</item>
</plurals>
<string name="printer_extended_description_template" msgid="1366699227703381874">"<xliff:g id="PRINT_SERVICE_LABEL">%1$s</xliff:g> - <xliff:g id="PRINTER_DESCRIPTION">%2$s</xliff:g>"</string>
@@ -78,7 +78,7 @@
<string name="all_services_title" msgid="5578662754874906455">"Tous les services"</string>
<plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
<item quantity="one">Installer pour détecter <xliff:g id="COUNT_1">%1$s</xliff:g> imprimante</item>
- <item quantity="many">Install to discover <xliff:g id="COUNT_1">%1$s</xliff:g> printers</item>
+ <item quantity="many">Installer pour détecter <xliff:g id="COUNT_1">%1$s</xliff:g> imprimantes</item>
<item quantity="other">Installer pour détecter <xliff:g id="COUNT_1">%1$s</xliff:g> imprimantes</item>
</plurals>
<string name="printing_notification_title_template" msgid="295903957762447362">"Impression de <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> en cours…"</string>
diff --git a/packages/PrintSpooler/res/values-fr/strings.xml b/packages/PrintSpooler/res/values-fr/strings.xml
index 560c5dc..4b7e83c 100644
--- a/packages/PrintSpooler/res/values-fr/strings.xml
+++ b/packages/PrintSpooler/res/values-fr/strings.xml
@@ -57,7 +57,7 @@
<string name="print_forget_printer" msgid="5035287497291910766">"Supprimer l\'imprimante"</string>
<plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
<item quantity="one"><xliff:g id="COUNT_1">%1$s</xliff:g> imprimante trouvée</item>
- <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> printers found</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> imprimantes trouvées</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> imprimantes trouvées</item>
</plurals>
<string name="printer_extended_description_template" msgid="1366699227703381874">"<xliff:g id="PRINT_SERVICE_LABEL">%1$s</xliff:g> – <xliff:g id="PRINTER_DESCRIPTION">%2$s</xliff:g>"</string>
@@ -78,7 +78,7 @@
<string name="all_services_title" msgid="5578662754874906455">"Tous les services"</string>
<plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
<item quantity="one">Installer pour détecter <xliff:g id="COUNT_1">%1$s</xliff:g> imprimante</item>
- <item quantity="many">Install to discover <xliff:g id="COUNT_1">%1$s</xliff:g> printers</item>
+ <item quantity="many">Installer pour détecter <xliff:g id="COUNT_1">%1$s</xliff:g> imprimantes</item>
<item quantity="other">Installer pour détecter <xliff:g id="COUNT_1">%1$s</xliff:g> imprimantes</item>
</plurals>
<string name="printing_notification_title_template" msgid="295903957762447362">"Impression de \"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>\" en cours…"</string>
diff --git a/packages/PrintSpooler/res/values-it/strings.xml b/packages/PrintSpooler/res/values-it/strings.xml
index 569bbc2..2a64d3d 100644
--- a/packages/PrintSpooler/res/values-it/strings.xml
+++ b/packages/PrintSpooler/res/values-it/strings.xml
@@ -56,7 +56,7 @@
<string name="print_select_printer" msgid="7388760939873368698">"Seleziona stampante"</string>
<string name="print_forget_printer" msgid="5035287497291910766">"Elimina stampante"</string>
<plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
- <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> printers found</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> stampanti trovate</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> stampanti trovate</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$s</xliff:g> stampante trovata</item>
</plurals>
@@ -77,7 +77,7 @@
<string name="disabled_services_title" msgid="7313253167968363211">"Servizi disattivati"</string>
<string name="all_services_title" msgid="5578662754874906455">"Tutti i servizi"</string>
<plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
- <item quantity="many">Install to discover <xliff:g id="COUNT_1">%1$s</xliff:g> printers</item>
+ <item quantity="many">Installa per rilevare <xliff:g id="COUNT_1">%1$s</xliff:g> stampanti</item>
<item quantity="other">Installa per rilevare <xliff:g id="COUNT_1">%1$s</xliff:g> stampanti</item>
<item quantity="one">Installa per rilevare <xliff:g id="COUNT_0">%1$s</xliff:g> stampante</item>
</plurals>
diff --git a/packages/PrintSpooler/res/values-pt-rBR/strings.xml b/packages/PrintSpooler/res/values-pt-rBR/strings.xml
index 3b460a1..855701b 100644
--- a/packages/PrintSpooler/res/values-pt-rBR/strings.xml
+++ b/packages/PrintSpooler/res/values-pt-rBR/strings.xml
@@ -57,7 +57,7 @@
<string name="print_forget_printer" msgid="5035287497291910766">"Esquecer impressora"</string>
<plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
<item quantity="one"><xliff:g id="COUNT_1">%1$s</xliff:g> impressoras encontradas</item>
- <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> printers found</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> impressoras encontradas</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> impressoras encontradas</item>
</plurals>
<string name="printer_extended_description_template" msgid="1366699227703381874">"<xliff:g id="PRINT_SERVICE_LABEL">%1$s</xliff:g> - <xliff:g id="PRINTER_DESCRIPTION">%2$s</xliff:g>"</string>
@@ -78,7 +78,7 @@
<string name="all_services_title" msgid="5578662754874906455">"Todos os serviços"</string>
<plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
<item quantity="one">Instale para encontrar <xliff:g id="COUNT_1">%1$s</xliff:g> impressoras</item>
- <item quantity="many">Install to discover <xliff:g id="COUNT_1">%1$s</xliff:g> printers</item>
+ <item quantity="many">Instale para encontrar <xliff:g id="COUNT_1">%1$s</xliff:g> impressoras</item>
<item quantity="other">Instale para encontrar <xliff:g id="COUNT_1">%1$s</xliff:g> impressoras</item>
</plurals>
<string name="printing_notification_title_template" msgid="295903957762447362">"Imprimindo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/PrintSpooler/res/values-pt-rPT/strings.xml b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
index 8c1087e..c9a52a8 100644
--- a/packages/PrintSpooler/res/values-pt-rPT/strings.xml
+++ b/packages/PrintSpooler/res/values-pt-rPT/strings.xml
@@ -56,7 +56,7 @@
<string name="print_select_printer" msgid="7388760939873368698">"Selecionar impressora"</string>
<string name="print_forget_printer" msgid="5035287497291910766">"Esquecer impressora"</string>
<plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
- <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> printers found</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> impressoras encontradas</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> impressoras encontradas</item>
<item quantity="one"><xliff:g id="COUNT_0">%1$s</xliff:g> impressora encontrada</item>
</plurals>
@@ -77,7 +77,7 @@
<string name="disabled_services_title" msgid="7313253167968363211">"Serviços desativados"</string>
<string name="all_services_title" msgid="5578662754874906455">"Todos os serviços"</string>
<plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
- <item quantity="many">Install to discover <xliff:g id="COUNT_1">%1$s</xliff:g> printers</item>
+ <item quantity="many">Instale para detetar <xliff:g id="COUNT_1">%1$s</xliff:g> impressoras</item>
<item quantity="other">Instale para detetar <xliff:g id="COUNT_1">%1$s</xliff:g> impressoras</item>
<item quantity="one">Instale para detetar <xliff:g id="COUNT_0">%1$s</xliff:g> impressora</item>
</plurals>
diff --git a/packages/PrintSpooler/res/values-pt/strings.xml b/packages/PrintSpooler/res/values-pt/strings.xml
index 3b460a1..855701b 100644
--- a/packages/PrintSpooler/res/values-pt/strings.xml
+++ b/packages/PrintSpooler/res/values-pt/strings.xml
@@ -57,7 +57,7 @@
<string name="print_forget_printer" msgid="5035287497291910766">"Esquecer impressora"</string>
<plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
<item quantity="one"><xliff:g id="COUNT_1">%1$s</xliff:g> impressoras encontradas</item>
- <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> printers found</item>
+ <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> impressoras encontradas</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> impressoras encontradas</item>
</plurals>
<string name="printer_extended_description_template" msgid="1366699227703381874">"<xliff:g id="PRINT_SERVICE_LABEL">%1$s</xliff:g> - <xliff:g id="PRINTER_DESCRIPTION">%2$s</xliff:g>"</string>
@@ -78,7 +78,7 @@
<string name="all_services_title" msgid="5578662754874906455">"Todos os serviços"</string>
<plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
<item quantity="one">Instale para encontrar <xliff:g id="COUNT_1">%1$s</xliff:g> impressoras</item>
- <item quantity="many">Install to discover <xliff:g id="COUNT_1">%1$s</xliff:g> printers</item>
+ <item quantity="many">Instale para encontrar <xliff:g id="COUNT_1">%1$s</xliff:g> impressoras</item>
<item quantity="other">Instale para encontrar <xliff:g id="COUNT_1">%1$s</xliff:g> impressoras</item>
</plurals>
<string name="printing_notification_title_template" msgid="295903957762447362">"Imprimindo <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ca/arrays.xml b/packages/SettingsLib/res/values-ca/arrays.xml
index b6f1590..3062e7d 100644
--- a/packages/SettingsLib/res/values-ca/arrays.xml
+++ b/packages/SettingsLib/res/values-ca/arrays.xml
@@ -232,7 +232,7 @@
<item msgid="8612549335720461635">"4K (segur)"</item>
<item msgid="7322156123728520872">"4K (ampliat)"</item>
<item msgid="7735692090314849188">"4K (ampliat, segur)"</item>
- <item msgid="7346816300608639624">"720p, 1080p (pantalla doble)"</item>
+ <item msgid="7346816300608639624">"720p, 1080p (pantalla dual)"</item>
</string-array>
<string-array name="enable_opengl_traces_entries">
<item msgid="4433736508877934305">"Cap"</item>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index ff04270..fe97f34 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -587,7 +587,7 @@
<string name="user_need_lock_message" msgid="4311424336209509301">"Para poder crear un perfil restringido, debes configurar una pantalla de bloqueo que proteja tus aplicaciones y datos personales."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Establecer bloqueo"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Cambiar a <xliff:g id="USER_NAME">%s</xliff:g>"</string>
- <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario…"</string>
+ <string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creando usuario nuevo…"</string>
<string name="creating_new_guest_dialog_message" msgid="1114905602181350690">"Creando nuevo invitado…"</string>
<string name="add_user_failed" msgid="4809887794313944872">"No se ha podido crear el usuario"</string>
<string name="add_guest_failed" msgid="8074548434469843443">"No se ha podido crear un nuevo invitado"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index aede28a..145dc5d 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -574,7 +574,7 @@
<string name="user_add_user_title" msgid="5457079143694924885">"Vil du legge til en ny bruker?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"Du kan dele denne enheten med andre folk ved å opprette flere brukere. Hver bruker har sin egen plass de kan tilpasse med apper, bakgrunner og annet. Brukere kan også justere enhetsinnstillinger, for eksempel Wifi, som påvirker alle.\n\nNår du legger til en ny bruker, må vedkommende angi innstillinger for plassen sin.\n\nAlle brukere kan oppdatere apper for alle andre brukere. Innstillinger og tjenester for tilgjengelighet overføres kanskje ikke til den nye brukeren."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Når du legger til en ny bruker, må hen konfigurere sitt eget område.\n\nAlle brukere kan oppdatere apper for alle andre brukere."</string>
- <string name="user_setup_dialog_title" msgid="8037342066381939995">"Konfigurere brukeren nå?"</string>
+ <string name="user_setup_dialog_title" msgid="8037342066381939995">"Vil du konfigurere brukeren?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Sørg for at brukeren er tilgjengelig for å konfigurere området sitt på enheten"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Vil du konfigurere profilen nå?"</string>
<string name="user_setup_button_setup_now" msgid="1708269547187760639">"Konfigurer nå"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 8ee274b..f29c7452 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -573,7 +573,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Beperkt profiel"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Nieuwe gebruiker toevoegen?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"Je kunt dit apparaat met anderen delen door extra gebruikers te maken. Elke gebruiker heeft een eigen profiel met zelf gekozen apps, achtergrond, enzovoort. Gebruikers kunnen ook apparaatinstellingen aanpassen die van invloed zijn op alle gebruikers, zoals wifi.\n\nWanneer je een nieuwe gebruiker toevoegt, moet die persoon een eigen profiel instellen.\n\nElke gebruiker kan apps updaten voor alle andere gebruikers. Toegankelijkheidsinstellingen en -services worden mogelijk niet overgezet naar de nieuwe gebruiker."</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"Wanneer je een nieuwe gebruiker toevoegt, moet die persoon diens eigen profiel instellen.\n\nElke gebruiker kan apps updaten voor alle andere gebruikers."</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"Wanneer je een nieuwe gebruiker toevoegt, moet die persoon hun eigen profiel instellen.\n\nElke gebruiker kan apps updaten voor alle andere gebruikers."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Gebruiker nu instellen?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Zorg ervoor dat de persoon het apparaat kan overnemen om een profiel in te stellen"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Profiel nu instellen?"</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/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/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 5aa7769..42a8636 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -33,13 +33,9 @@
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
import android.widget.FrameLayout
-import android.window.OnBackInvokedDispatcher
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CujType
-import com.android.systemui.animation.back.BackAnimationSpec
-import com.android.systemui.animation.back.applyTo
-import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
-import com.android.systemui.animation.back.onBackAnimationCallbackFrom
+import com.android.systemui.util.registerAnimationOnBackInvoked
import java.lang.IllegalArgumentException
import kotlin.math.roundToInt
@@ -798,7 +794,7 @@
if (featureFlags.isPredictiveBackQsDialogAnim) {
// TODO(b/265923095) Improve animations for QS dialogs on configuration change
- registerOnBackInvokedCallback(targetView = dialogContentWithBackground)
+ dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground)
}
// Show the dialog.
@@ -806,35 +802,6 @@
moveSourceDrawingToDialog()
}
- private fun registerOnBackInvokedCallback(targetView: View) {
- val metrics = targetView.resources.displayMetrics
-
- val onBackAnimationCallback =
- onBackAnimationCallbackFrom(
- backAnimationSpec = BackAnimationSpec.floatingSystemSurfacesForSysUi(metrics),
- displayMetrics = metrics, // TODO(b/265060720): We could remove this
- onBackProgressed = { backTransformation -> backTransformation.applyTo(targetView) },
- onBackInvoked = { dialog.dismiss() },
- )
-
- val dispatcher = dialog.onBackInvokedDispatcher
- targetView.addOnAttachStateChangeListener(
- object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View) {
- dispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- onBackAnimationCallback
- )
- }
-
- override fun onViewDetachedFromWindow(v: View) {
- targetView.removeOnAttachStateChangeListener(this)
- dispatcher.unregisterOnBackInvokedCallback(onBackAnimationCallback)
- }
- }
- )
- }
-
private fun moveSourceDrawingToDialog() {
if (decorView.viewRootImpl == null) {
// Make sure that we have access to the dialog view root to move the drawing to the
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index fdab749..a08b598 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -23,6 +23,7 @@
import android.graphics.Canvas
import android.graphics.Typeface
import android.graphics.fonts.Font
+import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.util.SparseArray
@@ -215,13 +216,40 @@
textInterpolator.targetPaint.textSize = textSize
}
if (weight >= 0) {
- // Paint#setFontVariationSettings creates Typeface instance from scratch. To reduce the
- // memory impact, cache the typeface result.
- textInterpolator.targetPaint.typeface =
- typefaceCache.getOrElse(weight) {
- textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
- textInterpolator.targetPaint.typeface
+ val fontVariationArray =
+ FontVariationAxis.fromFontVariationSettings(
+ textInterpolator.targetPaint.fontVariationSettings
+ )
+ if (fontVariationArray.isNullOrEmpty()) {
+ textInterpolator.targetPaint.typeface =
+ typefaceCache.getOrElse(weight) {
+ textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
+ textInterpolator.targetPaint.typeface
+ }
+ } else {
+ val idx = fontVariationArray.indexOfFirst { it.tag == "$TAG_WGHT" }
+ if (idx == -1) {
+ val updatedFontVariation =
+ textInterpolator.targetPaint.fontVariationSettings + ",'$TAG_WGHT' $weight"
+ textInterpolator.targetPaint.typeface =
+ typefaceCache.getOrElse(weight) {
+ textInterpolator.targetPaint.fontVariationSettings =
+ updatedFontVariation
+ textInterpolator.targetPaint.typeface
+ }
+ } else {
+ fontVariationArray[idx] = FontVariationAxis(
+ "$TAG_WGHT", weight.toFloat())
+ val updatedFontVariation =
+ FontVariationAxis.toFontVariationSettings(fontVariationArray)
+ textInterpolator.targetPaint.typeface =
+ typefaceCache.getOrElse(weight) {
+ textInterpolator.targetPaint.fontVariationSettings =
+ updatedFontVariation
+ textInterpolator.targetPaint.typeface
+ }
}
+ }
}
if (color != null) {
textInterpolator.targetPaint.color = color
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
index 33d14b1..8740d14 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtension.kt
@@ -16,15 +16,21 @@
package com.android.systemui.animation.back
+import android.annotation.IntRange
import android.util.DisplayMetrics
+import android.view.View
import android.window.BackEvent
import android.window.OnBackAnimationCallback
+import android.window.OnBackInvokedDispatcher
+import android.window.OnBackInvokedDispatcher.Priority
/**
* Generates an [OnBackAnimationCallback] given a [backAnimationSpec]. [onBackProgressed] will be
* called on each update passing the current [BackTransformation].
*
* Optionally, you can specify [onBackStarted], [onBackInvoked], and [onBackCancelled] callbacks.
+ *
+ * @sample com.android.systemui.util.registerAnimationOnBackInvoked
*/
fun onBackAnimationCallbackFrom(
backAnimationSpec: BackAnimationSpec,
@@ -64,3 +70,34 @@
}
}
}
+
+/**
+ * Register [OnBackAnimationCallback] when View is attached and unregister it when View is detached
+ *
+ * @sample com.android.systemui.util.registerAnimationOnBackInvoked
+ */
+fun View.registerOnBackInvokedCallbackOnViewAttached(
+ onBackInvokedDispatcher: OnBackInvokedDispatcher,
+ onBackAnimationCallback: OnBackAnimationCallback,
+ @Priority @IntRange(from = 0) priority: Int = OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+) {
+ addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ priority,
+ onBackAnimationCallback
+ )
+ }
+
+ override fun onViewDetachedFromWindow(v: View) {
+ removeOnAttachStateChangeListener(this)
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(onBackAnimationCallback)
+ }
+ }
+ )
+
+ if (isAttachedToWindow) {
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(priority, onBackAnimationCallback)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt
index 7bbfec7..a1d9d90 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.common.ui.view
+package com.android.systemui.animation.view
import android.content.Context
import android.util.AttributeSet
@@ -23,6 +23,7 @@
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
+/** An [ImageView] that also implements [LaunchableView]. */
class LaunchableImageView : ImageView, LaunchableView {
private val delegate =
LaunchableViewDelegate(
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
rename to packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
index 2edac52..bce2622 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableLinearLayout.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableLinearLayout.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.common.ui.view
+package com.android.systemui.animation.view
import android.content.Context
import android.util.AttributeSet
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt
similarity index 78%
copy from packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
copy to packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt
index 7bbfec7..286996d 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LaunchableImageView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -15,15 +15,16 @@
*
*/
-package com.android.systemui.common.ui.view
+package com.android.systemui.animation.view
import android.content.Context
import android.util.AttributeSet
-import android.widget.ImageView
+import android.widget.TextView
import com.android.systemui.animation.LaunchableView
import com.android.systemui.animation.LaunchableViewDelegate
-class LaunchableImageView : ImageView, LaunchableView {
+/** A [TextView] that also implements [LaunchableView]. */
+class LaunchableTextView : TextView, LaunchableView {
private val delegate =
LaunchableViewDelegate(
this,
@@ -38,13 +39,6 @@
defStyleAttr: Int,
) : super(context, attrs, defStyleAttr)
- constructor(
- context: Context?,
- attrs: AttributeSet?,
- defStyleAttr: Int,
- defStyleRes: Int,
- ) : super(context, attrs, defStyleAttr, defStyleRes)
-
override fun setShouldBlockVisibilityChanges(block: Boolean) {
delegate.setShouldBlockVisibilityChanges(block)
}
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 74bc910..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,7 +82,7 @@
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;
@@ -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/util/Dialog.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
new file mode 100644
index 0000000..428856d
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
@@ -0,0 +1,51 @@
+/*
+ * 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
+
+import android.app.Dialog
+import android.view.View
+import android.window.OnBackInvokedDispatcher
+import com.android.systemui.animation.back.BackAnimationSpec
+import com.android.systemui.animation.back.BackTransformation
+import com.android.systemui.animation.back.applyTo
+import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
+import com.android.systemui.animation.back.onBackAnimationCallbackFrom
+import com.android.systemui.animation.back.registerOnBackInvokedCallbackOnViewAttached
+
+/**
+ * Register on the Dialog's [OnBackInvokedDispatcher] an animation using the [BackAnimationSpec].
+ * The [BackTransformation] will be applied on the [targetView].
+ */
+@JvmOverloads
+fun Dialog.registerAnimationOnBackInvoked(
+ targetView: View,
+ backAnimationSpec: BackAnimationSpec =
+ BackAnimationSpec.floatingSystemSurfacesForSysUi(
+ displayMetrics = targetView.resources.displayMetrics,
+ ),
+) {
+ targetView.registerOnBackInvokedCallbackOnViewAttached(
+ onBackInvokedDispatcher = onBackInvokedDispatcher,
+ onBackAnimationCallback =
+ onBackAnimationCallbackFrom(
+ backAnimationSpec = backAnimationSpec,
+ displayMetrics = targetView.resources.displayMetrics,
+ onBackProgressed = { backTransformation -> backTransformation.applyTo(targetView) },
+ onBackInvoked = { dismiss() },
+ ),
+ )
+}
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/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/docs/qs-tiles.md b/packages/SystemUI/docs/qs-tiles.md
index 4cb765d..488f8c7 100644
--- a/packages/SystemUI/docs/qs-tiles.md
+++ b/packages/SystemUI/docs/qs-tiles.md
@@ -301,9 +301,13 @@
* Use only `handleUpdateState` to modify the values of the state to the new ones. This can be done by polling controllers or through the `arg` parameter.
* If the controller is not a `CallbackController`, respond to `handleSetListening` by attaching/dettaching from controllers.
* Implement `isAvailable` so the tile will not be created when it's not necessary.
-4. In `QSFactoryImpl`:
- * Inject a `Provider` for the tile created before.
- * Add a case to the `switch` with a unique String spec for the chosen tile.
+4. Either create a new feature module or find an existing related feature module and add the following binding method:
+ * ```kotlin
+ @Binds
+ @IntoMap
+ @StringKey(YourNewTile.TILE_SPEC) // A unique word that will map to YourNewTile
+ fun bindYourNewTile(yourNewTile: YourNewTile): QSTileImpl<*>
+ ```
5. In [SystemUI/res/values/config.xml](/packages/SystemUI/res/values/config.xml), modify `quick_settings_tiles_stock` and add the spec defined in the previous step. If necessary, add it also to `quick_settings_tiles_default`. The first one contains a list of all the tiles that SystemUI knows how to create (to show to the user in the customization screen). The second one contains only the default tiles that the user will experience on a fresh boot or after they reset their tiles.
6. In [SystemUI/res/values/tiles_states_strings.xml](/packages/SystemUI/res/values/tiles_states_strings.xml), add a new array for your tile. The name has to be `tile_states_<spec>`. Use a good description to help the translators.
7. In [`SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt`](/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt), add a new element to the map in `SubtitleArrayMapping` corresponding to the resource created in the previous step.
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 1a67691e..2215857 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -452,12 +452,6 @@
-packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
-packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt
-packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -743,10 +737,6 @@
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/panelstate/ShadeExpansionStateManagerTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt
-packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt
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 4ef525a..1c2f38b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -20,7 +20,6 @@
import com.android.internal.annotations.Keep
import com.android.systemui.plugins.annotations.ProvidesInterface
import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.statusbar.Weather
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
@@ -46,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 */
@@ -187,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)
@@ -204,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/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 2b16999..1d28c63 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -16,9 +16,11 @@
import android.annotation.NonNull;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.metrics.LogMaker;
import android.service.quicksettings.Tile;
+import android.text.TextUtils;
import android.view.View;
import androidx.annotation.Nullable;
@@ -175,6 +177,24 @@
public Drawable sideViewCustomDrawable;
public String spec;
+ /** Get the state text. */
+ public String getStateText(int arrayResId, Resources resources) {
+ if (state == Tile.STATE_UNAVAILABLE || this instanceof QSTile.BooleanState) {
+ String[] array = resources.getStringArray(arrayResId);
+ return array[state];
+ } else {
+ return "";
+ }
+ }
+
+ /** Get the text for secondaryLabel. */
+ public String getSecondaryLabel(String stateText) {
+ if (TextUtils.isEmpty(secondaryLabel)) {
+ return stateText;
+ }
+ return secondaryLabel.toString();
+ }
+
public boolean copyTo(State other) {
if (other == null) throw new IllegalArgumentException();
if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
index fc18132..6fe7d39 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.android.systemui.common.ui.view.LaunchableLinearLayout
+<com.android.systemui.animation.view.LaunchableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="@dimen/qs_security_footer_single_line_height"
@@ -63,4 +63,4 @@
android:src="@*android:drawable/ic_chevron_end"
android:autoMirrored="true"
android:tint="?android:attr/textColorSecondary" />
-</com.android.systemui.common.ui.view.LaunchableLinearLayout>
\ No newline at end of file
+</com.android.systemui.animation.view.LaunchableLinearLayout>
\ No newline at end of file
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/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index fba2c8b1..7888e53 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -53,7 +53,7 @@
<string name="kg_wrong_pattern" msgid="5907301342430102842">"Patró incorrecte"</string>
<string name="kg_wrong_password" msgid="4143127991071670512">"Contrasenya incorrecta"</string>
<string name="kg_wrong_pin" msgid="4160978845968732624">"El PIN no és correcte"</string>
- <string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{Torna-ho a provar d\'aquí a # segon.}many{Try again in # seconds.}other{Torna-ho a provar d\'aquí a # segons.}}"</string>
+ <string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{Torna-ho a provar d\'aquí a # segon.}many{Torna-ho a provar d\'aquí a # segons.}other{Torna-ho a provar d\'aquí a # segons.}}"</string>
<string name="kg_sim_pin_instructions" msgid="1942424305184242951">"Introdueix el PIN de la SIM."</string>
<string name="kg_sim_pin_instructions_multi" msgid="3639863309953109649">"Introdueix el PIN de la SIM de: <xliff:g id="CARRIER">%1$s</xliff:g>."</string>
<string name="kg_sim_lock_esim_instructions" msgid="5577169988158738030">"<xliff:g id="PREVIOUS_MSG">%1$s</xliff:g> Desactiva l\'eSIM per utilitzar el dispositiu sense servei mòbil."</string>
@@ -68,9 +68,9 @@
<string name="kg_too_many_failed_password_attempts_dialog_message" msgid="190984061975729494">"Has escrit la contrasenya <xliff:g id="NUMBER_0">%1$d</xliff:g> vegades de manera incorrecta. \n\nTorna-ho a provar d\'aquí a <xliff:g id="NUMBER_1">%2$d</xliff:g> segons."</string>
<string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"Has dibuixat el patró de desbloqueig <xliff:g id="NUMBER_0">%1$d</xliff:g> vegades de manera incorrecta. \n\nTorna-ho a provar d\'aquí a <xliff:g id="NUMBER_1">%2$d</xliff:g> segons."</string>
<string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"El codi PIN de la SIM no és correcte. Contacta amb l\'operador de telefonia mòbil per desbloquejar el dispositiu."</string>
- <string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{El codi PIN de la SIM no és correcte. Et queda # intent; si no l\'encertes, contacta amb l\'operador per desbloquejar el dispositiu.}many{Incorrect SIM PIN code, you have # remaining attempts. }other{El codi PIN de la SIM no és correcte. Et queden # intents. }}"</string>
+ <string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{El codi PIN de la SIM no és correcte. Et queda # intent; si no l\'encertes, contacta amb l\'operador per desbloquejar el dispositiu.}many{El codi PIN de la SIM no és correcte. Et queden # intents. }other{El codi PIN de la SIM no és correcte. Et queden # intents. }}"</string>
<string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"La SIM no es pot fer servir. Contacta amb l\'operador de telefonia mòbil."</string>
- <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{El codi PUK de la SIM no és correcte. Et queda # intent; si no l\'encertes, la SIM no es podrà tornar a fer servir.}many{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}other{El codi PUK de la SIM no és correcte. Et queden # intents; si no l\'encertes, la SIM no es podrà tornar a fer servir.}}"</string>
+ <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{El codi PUK de la SIM no és correcte. Et queda # intent; si no l\'encertes, la SIM no es podrà tornar a fer servir.}many{El codi PUK de la SIM no és correcte. Et queden # intents; si no l\'encertes, la SIM no es podrà tornar a fer servir.}other{El codi PUK de la SIM no és correcte. Et queden # intents; si no l\'encertes, la SIM no es podrà tornar a fer servir.}}"</string>
<string name="kg_password_pin_failed" msgid="5136259126330604009">"Ha fallat l\'operació del PIN de la SIM"</string>
<string name="kg_password_puk_failed" msgid="6778867411556937118">"No s\'ha pogut desbloquejar la SIM amb el codi PUK."</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Canvia el mètode d\'introducció"</string>
@@ -85,8 +85,8 @@
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositiu s\'ha bloquejat manualment"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"No s\'ha reconegut"</string>
<string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"Desbloqueig facial necessita accés a la càmera"</string>
- <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Introdueix el PIN de la SIM. Et queda # intent; si no l\'encertes, contacta amb l\'operador per desbloquejar el dispositiu.}many{Enter SIM PIN. You have # remaining attempts.}other{Introdueix el PIN de la SIM. Et queden # intents.}}"</string>
- <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{La targeta SIM s\'ha desactivat. Introdueix el codi PUK per continuar. Et queda # intent; si no l\'encertes, la SIM no es podrà tornar a fer servir. Contacta amb l\'operador per obtenir informació.}many{SIM is now disabled. Enter PUK code to continue. You have # remaining attempts before SIM becomes permanently unusable. Contact carrier for details.}other{La targeta SIM s\'ha desactivat. Introdueix el codi PUK per continuar. Et queden # intents; si no l\'encertes, la SIM no es podrà tornar a fer servir. Contacta amb l\'operador per obtenir informació.}}"</string>
+ <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Introdueix el PIN de la SIM. Et queda # intent; si no l\'encertes, contacta amb l\'operador per desbloquejar el dispositiu.}many{Introdueix el PIN de la SIM. Et queden # intents.}other{Introdueix el PIN de la SIM. Et queden # intents.}}"</string>
+ <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{La targeta SIM s\'ha desactivat. Introdueix el codi PUK per continuar. Et queda # intent; si no l\'encertes, la SIM no es podrà tornar a fer servir. Contacta amb l\'operador per obtenir informació.}many{La targeta SIM s\'ha desactivat. Introdueix el codi PUK per continuar. Et queden # intents; si no l\'encertes, la SIM no es podrà tornar a fer servir. Contacta amb l\'operador per obtenir informació.}other{La targeta SIM s\'ha desactivat. Introdueix el codi PUK per continuar. Et queden # intents; si no l\'encertes, la SIM no es podrà tornar a fer servir. Contacta amb l\'operador per obtenir informació.}}"</string>
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminada"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bombolla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analògica"</string>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 3c794c6..d5dba42 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -84,7 +84,7 @@
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratzaileak blokeatu egin du gailua"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Eskuz blokeatu da gailua"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ez da ezagutu"</string>
- <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"Aurpegi bidezko desblokeoak kamera atzitzeko baimena behar du"</string>
+ <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"Aurpegi bidezko desblokeoak kamera erabiltzeko baimena behar du"</string>
<string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Idatzi SIMaren PINa. # saiakera geratzen zaizu gailua desblokeatzeko operadorearekin harremanetan jarri behar izan aurretik.}other{Idatzi SIMaren PINa. # saiakera gelditzen zaizkizu.}}"</string>
<string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{Orain, SIMa desgaituta dago. Aurrera egiteko, idatzi PUK kodea. # saiakera geratzen zaizu SIMa betiko ez-erabilgarri geratu aurretik. Xehetasunak lortzeko, jarri operadorearekin harremanetan.}other{Orain, SIMa desgaituta dago. Aurrera egiteko, idatzi PUK kodea. # saiakera geratzen zaizkizu SIMa betiko ez-erabilgarri geratu aurretik. Xehetasunak lortzeko, jarri operadorearekin harremanetan.}}"</string>
<string name="clock_title_default" msgid="6342735240617459864">"Lehenetsia"</string>
diff --git a/packages/SystemUI/res/drawable/ic_progress_activity.xml b/packages/SystemUI/res/drawable/ic_progress_activity.xml
new file mode 100644
index 0000000..abf0625
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_progress_activity.xml
@@ -0,0 +1,26 @@
+<?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="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M24,44Q19.8,44 16.15,42.45Q12.5,40.9 9.8,38.2Q7.1,35.5 5.55,31.85Q4,28.2 4,24Q4,19.8 5.55,16.15Q7.1,12.5 9.8,9.8Q12.5,7.1 16.15,5.55Q19.8,4 24,4Q24.6,4 25.05,4.45Q25.5,4.9 25.5,5.5Q25.5,6.1 25.05,6.55Q24.6,7 24,7Q16.95,7 11.975,11.975Q7,16.95 7,24Q7,31.05 11.975,36.025Q16.95,41 24,41Q31.05,41 36.025,36.025Q41,31.05 41,24Q41,23.4 41.45,22.95Q41.9,22.5 42.5,22.5Q43.1,22.5 43.55,22.95Q44,23.4 44,24Q44,28.2 42.45,31.85Q40.9,35.5 38.2,38.2Q35.5,40.9 31.85,42.45Q28.2,44 24,44Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_font_scaling.xml b/packages/SystemUI/res/drawable/ic_qs_font_scaling.xml
new file mode 100644
index 0000000..d5b4c9e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_font_scaling.xml
@@ -0,0 +1,25 @@
+<!--
+ 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+<path
+ android:pathData="M7,20L7,7L2,7L2,4h13v3h-5v13ZM16,20v-8h-3L13,9h9v3h-3v8Z"
+ android:fillColor="#041E49"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education.xml b/packages/SystemUI/res/layout/activity_rear_display_education.xml
index f5fc48c..094807e 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_education.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_education.xml
@@ -41,9 +41,10 @@
</androidx.cardview.widget.CardView>
<TextView
+ android:id="@+id/rear_display_title_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/rear_display_fold_bottom_sheet_title"
+ android:text="@string/rear_display_folded_bottom_sheet_title"
android:textAppearance="@style/TextAppearance.Dialog.Title"
android:lineSpacingExtra="2sp"
android:paddingTop="@dimen/rear_display_title_top_padding"
@@ -54,7 +55,7 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/rear_display_bottom_sheet_description"
+ android:text="@string/rear_display_folded_bottom_sheet_description"
android:textAppearance="@style/TextAppearance.Dialog.Body"
android:lineSpacingExtra="2sp"
android:translationY="-1.24sp"
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
index 6de06f7..e970bc5 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
@@ -42,9 +42,10 @@
</androidx.cardview.widget.CardView>
<TextView
+ android:id="@+id/rear_display_title_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/rear_display_unfold_bottom_sheet_title"
+ android:text="@string/rear_display_unfolded_bottom_sheet_title"
android:textAppearance="@style/TextAppearance.Dialog.Title"
android:lineSpacingExtra="2sp"
android:paddingTop="@dimen/rear_display_title_top_padding"
@@ -55,21 +56,11 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/rear_display_bottom_sheet_description"
+ android:text="@string/rear_display_unfolded_bottom_sheet_description"
android:textAppearance="@style/TextAppearance.Dialog.Body"
android:lineSpacingExtra="2sp"
android:translationY="-1.24sp"
android:gravity="center_horizontal|top"
/>
- <TextView
- android:id="@+id/rear_display_warning_text_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/rear_display_bottom_sheet_warning"
- android:textAppearance="@style/TextAppearance.Dialog.Body"
- android:lineSpacingExtra="2sp"
- android:gravity="center_horizontal|top"
- />
-
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index 8cf4f4d..0ff944c 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -60,14 +60,13 @@
/>
<!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
- <ProgressBar
+ <ImageView
android:id="@+id/loading"
- android:indeterminate="true"
android:layout_width="@dimen/media_ttt_status_icon_size"
android:layout_height="@dimen/media_ttt_status_icon_size"
android:layout_marginStart="@dimen/media_ttt_last_item_start_margin"
- android:indeterminateTint="?androidprv:attr/colorAccentPrimaryVariant"
- style="?android:attr/progressBarStyleSmall"
+ android:src="@drawable/ic_progress_activity"
+ android:tint="?androidprv:attr/colorAccentPrimaryVariant"
android:alpha="0.0"
/>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 9b01bd8..297cf2b 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -61,8 +61,6 @@
android:id="@+id/share_chip"/>
<include layout="@layout/overlay_action_chip"
android:id="@+id/remote_copy_chip"/>
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/edit_chip"/>
</LinearLayout>
</HorizontalScrollView>
<View
diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
index 446bb01..fb78b49 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -20,7 +20,7 @@
android:layout_width="wrap_content"
android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
- <com.android.systemui.common.ui.view.LaunchableImageView
+ <com.android.systemui.animation.view.LaunchableImageView
android:id="@+id/home_controls_chip"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/layout/font_scaling_dialog.xml b/packages/SystemUI/res/layout/font_scaling_dialog.xml
new file mode 100644
index 0000000..27c1e9d
--- /dev/null
+++ b/packages/SystemUI/res/layout/font_scaling_dialog.xml
@@ -0,0 +1,27 @@
+<?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.systemui.common.ui.view.SeekBarWithIconButtonsView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/font_scaling_slider"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ app:max="6"
+ app:progress="0"
+ app:iconStartContentDescription="@string/font_scaling_smaller"
+ app:iconEndContentDescription="@string/font_scaling_larger"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 3f95515..2871cdf 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -59,7 +59,7 @@
</LinearLayout>
- <com.android.systemui.common.ui.view.LaunchableImageView
+ <com.android.systemui.animation.view.LaunchableImageView
android:id="@+id/start_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
@@ -72,7 +72,7 @@
android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
android:visibility="gone" />
- <com.android.systemui.common.ui.view.LaunchableImageView
+ <com.android.systemui.animation.view.LaunchableImageView
android:id="@+id/end_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index f2e114b..9d91419 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -106,7 +106,7 @@
app:layout_constrainedWidth="true"
app:layout_constraintWidth_min="@dimen/min_clickable_item_size"
app:layout_constraintHeight_min="@dimen/min_clickable_item_size">
- <com.android.systemui.common.ui.view.LaunchableLinearLayout
+ <com.android.systemui.animation.view.LaunchableLinearLayout
android:id="@+id/media_seamless_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -135,7 +135,7 @@
android:textDirection="locale"
android:textSize="12sp"
android:lineHeight="16sp" />
- </com.android.systemui.common.ui.view.LaunchableLinearLayout>
+ </com.android.systemui.animation.view.LaunchableLinearLayout>
</LinearLayout>
<!-- Song name -->
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/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_call_chip.xml
index 18d231c..238fc84 100644
--- a/packages/SystemUI/res/layout/ongoing_call_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_call_chip.xml
@@ -23,7 +23,7 @@
android:layout_gravity="center_vertical|start"
android:layout_marginStart="5dp"
>
- <com.android.systemui.common.ui.view.LaunchableLinearLayout
+ <com.android.systemui.animation.view.LaunchableLinearLayout
android:id="@+id/ongoing_call_chip_background"
android:layout_width="wrap_content"
android:layout_height="@dimen/ongoing_appops_chip_height"
@@ -55,5 +55,5 @@
android:textColor="?android:attr/colorPrimary"
/>
- </com.android.systemui.common.ui.view.LaunchableLinearLayout>
+ </com.android.systemui.animation.view.LaunchableLinearLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml b/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml
new file mode 100644
index 0000000..52d1d4f
--- /dev/null
+++ b/packages/SystemUI/res/layout/seekbar_with_icon_buttons.xml
@@ -0,0 +1,73 @@
+<?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.
+ -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/seekbar_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ tools:parentTag="android.widget.LinearLayout">
+
+ <FrameLayout
+ android:id="@+id/icon_start_frame"
+ android:layout_width="@dimen/min_clickable_item_size"
+ android:layout_height="@dimen/min_clickable_item_size"
+ android:clipChildren="false"
+ android:focusable="true" >
+ <ImageView
+ android:id="@+id/icon_start"
+ android:layout_width="@dimen/seekbar_icon_size"
+ android:layout_height="@dimen/seekbar_icon_size"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:adjustViewBounds="true"
+ android:focusable="false"
+ android:src="@drawable/ic_remove"
+ android:tint="?android:attr/textColorPrimary"
+ android:tintMode="src_in" />
+ </FrameLayout>
+
+ <SeekBar
+ android:id="@+id/seekbar"
+ style="@android:style/Widget.Material.SeekBar.Discrete"
+ android:layout_width="0dp"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1" />
+
+ <FrameLayout
+ android:id="@+id/icon_end_frame"
+ android:layout_width="@dimen/min_clickable_item_size"
+ android:layout_height="@dimen/min_clickable_item_size"
+ android:clipChildren="false"
+ android:focusable="true" >
+ <ImageView
+ android:id="@+id/icon_end"
+ android:layout_width="@dimen/seekbar_icon_size"
+ android:layout_height="@dimen/seekbar_icon_size"
+ android:layout_gravity="center"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:adjustViewBounds="true"
+ android:focusable="false"
+ android:src="@drawable/ic_add"
+ android:tint="?android:attr/textColorPrimary"
+ android:tintMode="src_in" />
+ </FrameLayout>
+
+</merge>
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 782187a..65c2417 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Iets is fout. Probeer weer."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Laai tans"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Saai jou media uit"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Saai tans <xliff:g id="APP_LABEL">%1$s</xliff:g> uit"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Onaktief, gaan program na"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nie gekry nie"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrole is nie beskikbaar nie"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 57e160e..d2d7e64 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"የሆነ ችግር ተፈጥሯል። እንደገና ይሞክሩ።"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"በመጫን ላይ"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ጡባዊ"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"የእርስዎን ሚዲያ cast በማድረግ ላይ"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"<xliff:g id="APP_LABEL">%1$s</xliff:g>ን Cast በማድረግ ላይ"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"ንቁ ያልኾነ፣ መተግበሪያን ይፈትሹ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"አልተገኘም"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"መቆጣጠሪያ አይገኝም"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index d638d52..66279d3 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -794,11 +791,11 @@
<string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"تبديل"</string>
<string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"انقر لفتح ميزات تسهيل الاستخدام. يمكنك تخصيص هذا الزر أو استبداله من الإعدادات.\n\n"<annotation id="link">"عرض الإعدادات"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"يمكنك نقل الزر إلى الحافة لإخفائه مؤقتًا."</string>
- <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"نقل إلى أعلى يمين الشاشة"</string>
- <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"نقل إلى أعلى يسار الشاشة"</string>
- <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"نقل إلى أسفل يمين الشاشة"</string>
- <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"نقل إلى أسفل يسار الشاشة"</string>
- <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"نقله إلى الحافة وإخفاؤه"</string>
+ <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"النقل إلى أعلى يمين الشاشة"</string>
+ <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"النقل إلى أعلى يسار الشاشة"</string>
+ <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"النقل إلى أسفل يمين الشاشة"</string>
+ <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"النقل إلى أسفل يسار الشاشة"</string>
+ <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"النقل إلى الحافة والإخفاء"</string>
<string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"نقله إلى خارج الحافة وإظهاره"</string>
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"إيقاف/تفعيل"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"التحكم بالجهاز"</string>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"حدث خطأ. يُرجى إعادة المحاولة."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"جارٍ التحميل"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"جهاز لوحي"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"غير نشط، تحقّق من التطبيق."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"لم يتم العثور عليه."</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"عنصر التحكّم غير متوفّر"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 01148d5..688025b 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"কিবা ভুল হ’ল। পুনৰ চেষ্টা কৰক।"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"ল’ড হৈ আছে"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"টেবলেট"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"সক্ৰিয় নহয়, এপ্টো পৰীক্ষা কৰক"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"বিচাৰি পোৱা নগ’ল"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"নিয়ন্ত্ৰণটো উপলব্ধ নহয়"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index dc8e7da..77bf65b 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -180,7 +180,7 @@
<string name="accessibility_bluetooth_name" msgid="7300973230214067678">"<xliff:g id="BLUETOOTH">%s</xliff:g> üzərindən qoşuldu."</string>
<string name="accessibility_cast_name" msgid="7344437925388773685">"<xliff:g id="CAST">%s</xliff:g> cihazına qoşulub."</string>
<string name="accessibility_not_connected" msgid="4061305616351042142">"Qoşulu deyil."</string>
- <string name="data_connection_roaming" msgid="375650836665414797">"Rominq"</string>
+ <string name="data_connection_roaming" msgid="375650836665414797">"Rouminq"</string>
<string name="cell_data_off" msgid="4886198950247099526">"Deaktiv"</string>
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Uçuş rejimi"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN aktivdir."</string>
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Xəta oldu. Yenə cəhd edin."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Yüklənir"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"planşet"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Aktiv deyil, tətbiqi yoxlayın"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Tapılmadı"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Nəzarət əlçatan deyil"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 5b11dbc..78812ae 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Došlo je do greške. Probajte ponovo."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Učitava se"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno. Vidite aplikaciju"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 22b1c24..b539e99 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Нешта пайшло не так. Паўтарыце спробу."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Ідзе загрузка"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшэт"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Неактыўна, праверце праграму"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Не знойдзена"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Кіраванне недаступнае"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 1adb80b..a45ca4c 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Нещо се обърка. Опитайте отново."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Зарежда се"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"таблет"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, проверете прилож."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Не е намерено"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Контролата не е налице"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index bdda1a3..08b7ca4 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"কোনও সমস্যা হয়েছে। আবার চেষ্টা করুন।"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"লোড করা হচ্ছে"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ট্যাবলেট"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"বন্ধ আছে, অ্যাপ চেক করুন"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"খুঁজে পাওয়া যায়নি"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"কন্ট্রোল উপলভ্য নেই"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index fe947df..e7a6006 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Nešto nije uredu. Pokušajte ponovo."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Učitavanje"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Emitiranje medijskih sadržaja"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Emitiranje aplikacije <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, vidite aplikaciju"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index b1e0ddc..109aeb7 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"S\'ha desat al perfil de treball de <xliff:g id="APP">%1$s</xliff:g>"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ha detectat aquesta captura de pantalla."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> i altres aplicacions obertes han detectat aquesta captura de pantalla."</string>
<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>
@@ -221,7 +218,7 @@
<string name="accessibility_sensors_off_active" msgid="2619725434618911551">"Sensors desactivats"</string>
<string name="accessibility_clear_all" msgid="970525598287244592">"Esborra totes les notificacions."</string>
<string name="notification_group_overflow_indicator" msgid="7605120293801012648">"+ <xliff:g id="NUMBER">%s</xliff:g>"</string>
- <string name="notification_group_overflow_description" msgid="7176322877233433278">"{count,plural, =1{# notificació més a l\'interior.}many{# more notifications inside.}other{# notificacions més a l\'interior.}}"</string>
+ <string name="notification_group_overflow_description" msgid="7176322877233433278">"{count,plural, =1{# notificació més a l\'interior.}many{# notificacions més a l\'interior.}other{# notificacions més a l\'interior.}}"</string>
<string name="accessibility_rotation_lock_on_landscape" msgid="936972553861524360">"La pantalla està bloquejada en orientació horitzontal."</string>
<string name="accessibility_rotation_lock_on_portrait" msgid="2356633398683813837">"La pantalla està bloquejada en orientació vertical."</string>
<string name="dessert_case" msgid="9104973640704357717">"Capsa de postres"</string>
@@ -269,7 +266,7 @@
<string name="quick_settings_hotspot_label" msgid="1199196300038363424">"Punt d\'accés Wi-Fi"</string>
<string name="quick_settings_hotspot_secondary_label_transient" msgid="7585604088079160564">"S\'està activant…"</string>
<string name="quick_settings_hotspot_secondary_label_data_saver_enabled" msgid="1280433136266439372">"Estalvi dades activat"</string>
- <string name="quick_settings_hotspot_secondary_label_num_devices" msgid="7536823087501239457">"{count,plural, =1{# dispositiu}many{# devices}other{# dispositius}}"</string>
+ <string name="quick_settings_hotspot_secondary_label_num_devices" msgid="7536823087501239457">"{count,plural, =1{# dispositiu}many{# dispositius}other{# dispositius}}"</string>
<string name="quick_settings_flashlight_label" msgid="4904634272006284185">"Llanterna"</string>
<string name="quick_settings_flashlight_camera_in_use" msgid="4820591564526512571">"Càmera en ús"</string>
<string name="quick_settings_cellular_detail_title" msgid="792977203299358893">"Dades mòbils"</string>
@@ -370,7 +367,7 @@
<string name="guest_notification_session_active" msgid="5567273684713471450">"Estàs en mode de convidat"</string>
<string name="user_add_user_message_guest_remove" msgid="5589286604543355007">\n\n"En afegir un usuari nou, se sortirà del mode de convidat i se suprimiran totes les aplicacions i dades de la sessió de convidat actual."</string>
<string name="user_limit_reached_title" msgid="2429229448830346057">"S\'ha assolit el límit d\'usuaris"</string>
- <string name="user_limit_reached_message" msgid="1070703858915935796">"{count,plural, =1{Només es pot crear 1 usuari.}many{You can add up to # users.}other{Pots afegir fins a # usuaris.}}"</string>
+ <string name="user_limit_reached_message" msgid="1070703858915935796">"{count,plural, =1{Només es pot crear 1 usuari.}many{Pots afegir fins a # usuaris.}other{Pots afegir fins a # usuaris.}}"</string>
<string name="user_remove_user_title" msgid="9124124694835811874">"Vols suprimir l\'usuari?"</string>
<string name="user_remove_user_message" msgid="6702834122128031833">"Totes les aplicacions i les dades d\'aquest usuari se suprimiran."</string>
<string name="user_remove_user_remove" msgid="8387386066949061256">"Suprimeix"</string>
@@ -573,8 +570,8 @@
<string name="notification_menu_snooze_action" msgid="5415729610393475019">"Recorda-m\'ho"</string>
<string name="snooze_undo" msgid="2738844148845992103">"Desfés"</string>
<string name="snoozed_for_time" msgid="7586689374860469469">"S\'ha posposat <xliff:g id="TIME_AMOUNT">%1$s</xliff:g>"</string>
- <string name="snoozeHourOptions" msgid="2332819756222425558">"{count,plural, =1{# hora}=2{# hores}many{# hours}other{# hores}}"</string>
- <string name="snoozeMinuteOptions" msgid="2222082405822030979">"{count,plural, =1{# minut}many{# minutes}other{# minuts}}"</string>
+ <string name="snoozeHourOptions" msgid="2332819756222425558">"{count,plural, =1{# hora}=2{# hores}many{# hores}other{# hores}}"</string>
+ <string name="snoozeMinuteOptions" msgid="2222082405822030979">"{count,plural, =1{# minut}many{# minuts}other{# minuts}}"</string>
<string name="battery_detail_switch_title" msgid="6940976502957380405">"Estalvi de bateria"</string>
<string name="keyboard_key_button_template" msgid="8005673627272051429">"Botó <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="keyboard_key_home" msgid="3734400625170020657">"Inici"</string>
@@ -803,7 +800,7 @@
<string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"commuta"</string>
<string name="quick_controls_title" msgid="6839108006171302273">"Controls de dispositius"</string>
<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_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{S\'ha afegit # control.}many{S\'han afegit # controls.}other{S\'han afegit # controls.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Suprimit"</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"S\'ha produït un error. Torna-ho a provar."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"S\'està carregant"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tauleta"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactiu; comprova l\'aplicació"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"No s\'ha trobat"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"El control no està disponible"</string>
@@ -972,7 +973,7 @@
<string name="qs_tile_request_dialog_add" msgid="4888460910694986304">"Afegeix la icona"</string>
<string name="qs_tile_request_dialog_not_add" msgid="4168716573114067296">"No afegeixis la icona"</string>
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"Selecciona un usuari"</string>
- <string name="fgs_manager_footer_label" msgid="8276763570622288231">"{count,plural, =1{# aplicació està activa}many{# apps are active}other{# aplicacions estan actives}}"</string>
+ <string name="fgs_manager_footer_label" msgid="8276763570622288231">"{count,plural, =1{# aplicació està activa}many{# aplicacions estan actives}other{# aplicacions estan actives}}"</string>
<string name="fgs_dot_content_description" msgid="2865071539464777240">"Informació nova"</string>
<string name="fgs_manager_dialog_title" msgid="5879184257257718677">"Aplicacions actives"</string>
<string name="fgs_manager_dialog_message" msgid="2670045017200730076">"Aquestes aplicacions estan actives i executant-se, fins i tot quan no les utilitzes. Això en millora la funcionalitat, però també pot afectar la durada de la bateria."</string>
@@ -1002,7 +1003,7 @@
<string name="dream_overlay_status_bar_camera_off" msgid="5273073778969890823">"La càmera està desactivada"</string>
<string name="dream_overlay_status_bar_mic_off" msgid="8366534415013819396">"El micròfon està desactivat"</string>
<string name="dream_overlay_status_bar_camera_mic_off" msgid="3199425257833773569">"Càmera i micròfon desactivats"</string>
- <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificació}many{# notifications}other{# notificacions}}"</string>
+ <string name="dream_overlay_status_bar_notification_indicator" msgid="8091389255691081711">"{count,plural, =1{# notificació}many{# notificacions}other{# notificacions}}"</string>
<string name="dream_overlay_weather_complication_desc" msgid="824503662089783824">"<xliff:g id="WEATHER_CONDITION">%1$s</xliff:g>, <xliff:g id="TEMPERATURE">%2$s</xliff:g>"</string>
<string name="note_task_button_label" msgid="8718616095800343136">"Presa de notes"</string>
<string name="broadcasting_description_is_broadcasting" msgid="765627502786404290">"S\'està emetent"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index e0da110..de48119 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Uloženo v aplikaci <xliff:g id="APP">%1$s</xliff:g> v pracovním profilu"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"Aplikace <xliff:g id="APPNAME">%1$s</xliff:g> objevila tento snímek obrazovky."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> a ostatní otevřené aplikace objevily tento snímek obrazovky."</string>
<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>
@@ -865,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Došlo k chybě. Zkuste to znovu."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Načítání"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Odesílání médií"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Odesílání aplikace <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktivní, zkontrolujte aplikaci"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nenalezeno"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Ovládání není k dispozici"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index fa9b96a..73ec8f5 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Noget gik galt. Prøv igen."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Indlæser"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Tjek appen"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ikke fundet"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Styringselement ikke tilgængeligt"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 9521e71..c3fb287e 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"In <xliff:g id="APP">%1$s</xliff:g> im Arbeitsprofil gespeichert"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> hat diesen Screenshot erkannt."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> und andere geöffnete Apps haben diesen Screenshot erkannt."</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Ein Fehler ist aufgetreten. Versuch es noch einmal."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Wird geladen"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"Tablet"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv – sieh in der App nach"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nicht gefunden"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Steuerelement nicht verfügbar"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 9f6a1c3..dacd2f0 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Παρουσιάστηκε κάποιο πρόβλημα. Δοκιμάστε ξανά."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Φόρτωση"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Μετάδοση των μέσων σας"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Μετάδοση <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Ανενεργό, έλεγχος εφαρμογής"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Δεν βρέθηκε."</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Μη διαθέσιμο στοιχείο ελέγχου"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index c11b6c4..5f6a481 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Casting your media"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Casting <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 9b60f22..bd3fa83 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Casting your media"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Casting <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index c11b6c4..5f6a481 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Casting your media"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Casting <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index c11b6c4..5f6a481 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Casting your media"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Casting <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index cb95bb0..93d48ef 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Something went wrong. Try again."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Loading"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Casting your media"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Casting <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Inactive, check app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Not found"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control is unavailable"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 7fd8a6f..2e179de 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Se produjo un error. Vuelve a intentarlo."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Cargando"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Verifica la app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"No se encontró"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"El control no está disponible"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 48e3ba9..2d8f098 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Se ha guardado en el perfil de trabajo de <xliff:g id="APP">%1$s</xliff:g>"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ha detectado esta captura de pantalla."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> y otras aplicaciones abiertas han detectado esta captura de pantalla."</string>
<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>
@@ -865,6 +862,10 @@
<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>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactivo, comprobar aplicación"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"No se ha encontrado"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Control no disponible"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 6bc736f..a983a7a 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salvestati tööprofiilil rakendusse <xliff:g id="APP">%1$s</xliff:g>"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> tuvastas selle ekraanipildi."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ja muud avatud rakendused tuvastasid selle ekraanipildi."</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Midagi läks valesti. Proovige uuesti."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Laadimine"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tahvelarvuti"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Passiivne, vaadake rakendust"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ei leitud"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Juhtelement pole saadaval"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 4b1931b..2cd0ced 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -32,13 +32,13 @@
<string name="battery_saver_start_action" msgid="8353766979886287140">"Aktibatu"</string>
<string name="battery_saver_dismiss_action" msgid="7199342621040014738">"Ez, eskerrik asko"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Biratu pantaila automatikoki"</string>
- <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> atzitzeko baimena eman nahi diozu <xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioari?"</string>
+ <string name="usb_device_permission_prompt" msgid="4414719028369181772">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> erabiltzeko baimena eman nahi diozu <xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioari?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> erabiltzeko baimena eman nahi diozu <xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioari?\nAplikazioak ez du grabatzeko baimenik, baina baliteke USB bidezko gailu horren bidez audioa grabatzea."</string>
- <string name="usb_audio_device_permission_prompt_title" msgid="4221351137250093451">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> atzitzeko baimena eman nahi diozu <xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioari?"</string>
+ <string name="usb_audio_device_permission_prompt_title" msgid="4221351137250093451">"<xliff:g id="USB_DEVICE">%2$s</xliff:g> erabiltzeko baimena eman nahi diozu <xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioari?"</string>
<string name="usb_audio_device_confirm_prompt_title" msgid="8828406516732985696">"<xliff:g id="APPLICATION">%1$s</xliff:g> ireki nahi duzu <xliff:g id="USB_DEVICE">%2$s</xliff:g> kudeatzeko?"</string>
<string name="usb_audio_device_prompt_warn" msgid="2504972133361130335">"Aplikazioak ez du grabatzeko baimenik, baina baliteke USB bidezko gailu honen bidez audioa grabatzea. <xliff:g id="APPLICATION">%1$s</xliff:g> gailu honekin erabiliz gero, baliteke deiak, jakinarazpenak eta alarmak ez entzutea."</string>
<string name="usb_audio_device_prompt" msgid="7944987408206252949">"<xliff:g id="APPLICATION">%1$s</xliff:g> gailu honekin erabiliz gero, baliteke deiak, jakinarazpenak eta alarmak ez entzutea."</string>
- <string name="usb_accessory_permission_prompt" msgid="717963550388312123">"<xliff:g id="USB_ACCESSORY">%2$s</xliff:g> atzitzeko baimena eman nahi diozu <xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioari?"</string>
+ <string name="usb_accessory_permission_prompt" msgid="717963550388312123">"<xliff:g id="USB_ACCESSORY">%2$s</xliff:g> erabiltzeko baimena eman nahi diozu <xliff:g id="APPLICATION">%1$s</xliff:g> aplikazioari?"</string>
<string name="usb_device_confirm_prompt" msgid="4091711472439910809">"<xliff:g id="APPLICATION">%1$s</xliff:g> ireki nahi duzu <xliff:g id="USB_DEVICE">%2$s</xliff:g> kudeatzeko?"</string>
<string name="usb_device_confirm_prompt_warn" msgid="990208659736311769">"<xliff:g id="APPLICATION">%1$s</xliff:g> ireki nahi duzu <xliff:g id="USB_DEVICE">%2$s</xliff:g> erabiltzeko?\nAplikazioak ez du grabatzeko baimenik, baina baliteke audioa grabatzea USB bidezko gailu horren bidez."</string>
<string name="usb_accessory_confirm_prompt" msgid="5728408382798643421">"<xliff:g id="APPLICATION">%1$s</xliff:g> ireki nahi duzu <xliff:g id="USB_ACCESSORY">%2$s</xliff:g> kudeatzeko?"</string>
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Laneko profilaren <xliff:g id="APP">%1$s</xliff:g> aplikazioan gorde da"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioak pantaila-argazkia hauteman du."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioak eta irekitako beste aplikazio batzuek pantaila-argazkia hauteman dute."</string>
<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>
@@ -303,9 +300,9 @@
<string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Gailuaren mikrofonoa desblokeatu nahi duzu?"</string>
<string name="sensor_privacy_start_use_camera_dialog_title" msgid="8807639852654305227">"Gailuaren kamera desblokeatu nahi duzu?"</string>
<string name="sensor_privacy_start_use_mic_camera_dialog_title" msgid="4316471859905020023">"Gailuaren kamera eta mikrofonoa desblokeatu nahi dituzu?"</string>
- <string name="sensor_privacy_start_use_mic_dialog_content" msgid="1624701280680913717">"Mikrofonoa atzitzeko baimena duten aplikazio eta zerbitzu guztiek erabili ahalko dute."</string>
- <string name="sensor_privacy_start_use_camera_dialog_content" msgid="4704948062372435963">"Kamera atzitzeko baimena duten aplikazio eta zerbitzu guztiek erabili ahalko dute."</string>
- <string name="sensor_privacy_start_use_mic_camera_dialog_content" msgid="3577642558418404919">"Kamera edo mikrofonoa atzitzeko baimena duten aplikazio eta zerbitzu guztiek erabili ahalko dituzte."</string>
+ <string name="sensor_privacy_start_use_mic_dialog_content" msgid="1624701280680913717">"Mikrofonoa erabiltzeko baimena duten aplikazio eta zerbitzu guztiek erabili ahalko dute."</string>
+ <string name="sensor_privacy_start_use_camera_dialog_content" msgid="4704948062372435963">"Kamera erabiltzeko baimena duten aplikazio eta zerbitzu guztiek erabili ahalko dute."</string>
+ <string name="sensor_privacy_start_use_mic_camera_dialog_content" msgid="3577642558418404919">"Kamera edo mikrofonoa erabiltzeko baimena duten aplikazio eta zerbitzu guztiek erabili ahalko dituzte."</string>
<string name="sensor_privacy_start_use_mic_blocked_dialog_title" msgid="2640140287496469689">"Blokeatuta dago mikrofonoa"</string>
<string name="sensor_privacy_start_use_camera_blocked_dialog_title" msgid="7398084286822440384">"Blokeatuta dago kamera"</string>
<string name="sensor_privacy_start_use_mic_camera_blocked_dialog_title" msgid="195236134743281973">"Blokeatuta daude mikrofonoa eta kamera"</string>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Arazoren bat izan da. Saiatu berriro."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Kargatzen"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tableta"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktibo; egiaztatu aplikazioa"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ez da aurkitu"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Ez dago erabilgarri kontrolatzeko aukera"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index df98a7e..3285150 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"مشکلی پیش آمد. دوباره امتحان کنید."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"درحال بار کردن"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"رایانه لوحی"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"غیرفعال، برنامه را بررسی کنید"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"پیدا نشد"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"کنترل دردسترس نیست"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index c93c3fa..70af7ab 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Tallennettu työprofiiliin tässä sovelluksessa: <xliff:g id="APP">%1$s</xliff:g>"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> havaitsi tämän kuvakaappauksen."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ja jotkin muut sovellukset havaitsivat tämän kuvakaappauksen."</string>
<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>
@@ -865,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Jotain meni pieleen. Yritä uudelleen."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Latautuminen"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tabletti"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Striimataan mediaa"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Striimataan <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Epäaktiivinen, tarkista sovellus"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ei löydy"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Ohjain ei ole käytettävissä"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index f1e5fbc..e42f24b 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Enregistré dans <xliff:g id="APP">%1$s</xliff:g> dans le profil professionnel"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> a détecté cette capture d\'écran."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> et d\'autres applications ouvertes ont détecté cette capture d\'écran."</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Une erreur s\'est produite. Réessayez."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Chargement en cours…"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablette"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifiez l\'appli"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"La commande n\'est pas accessible"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index a667728..371396a 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Enregistré dans <xliff:g id="APP">%1$s</xliff:g>, dans le profil professionnel"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> a détecté cette capture d\'écran."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> et d\'autres applis ouvertes ont détecté cette capture d\'écran."</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Un problème est survenu. Réessayez."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Chargement…"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablette"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Délai expiré, vérifier l\'appli"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Introuvable"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Commande indisponible"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 260be9b..9bea0ca 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Captura de pantalla gardada na aplicación <xliff:g id="APP">%1$s</xliff:g> do perfil de traballo"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detectou esta captura de pantalla."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e outras aplicacións abertas detectaron esta captura de pantalla."</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Produciuse un erro. Téntao de novo."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Cargando"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tableta"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactivo. Comproba a app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Non se atopou"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"O control non está dispoñible"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 86b4bc5..fa55ea6 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"કંઈક ખોટું થયું. ફરી પ્રયાસ કરો."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"લોડ થઈ રહ્યું છે"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ટૅબ્લેટ"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"નિષ્ક્રિય, ઍપને ચેક કરો"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"મળ્યું નથી"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"નિયંત્રણ ઉપલબ્ધ નથી"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 649c783..c7b9fec 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"कोई गड़बड़ी हुई. फिर से कोशिश करें."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"लोड हो रहा है"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"टैबलेट"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"काम नहीं कर रहा, ऐप जांचें"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"कंट्रोल नहीं है"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"कंट्रोल मौजूद नहीं है"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 53ad4b8..72bdbce 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Nešto nije u redu. Pokušajte ponovo."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Učitavanje"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Emitiranje medijskih sadržaja"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Emitiranje aplikacije <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, provjerite aplik."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nije pronađeno"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrola nije dostupna"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 69656ed..0ad27c4 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Hiba történt. Próbálkozzon újra."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Betöltés…"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"táblagép"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"A médiatartalom átküldése folyamatban van"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"<xliff:g id="APP_LABEL">%1$s</xliff:g> átküldése"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktív, ellenőrizze az appot"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nem található"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Nem hozzáférhető vezérlő"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index d4c1399..ff8f19a 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Սխալ առաջացավ։ Նորից փորձեք։"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Բեռնվում է"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"պլանշետ"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Մեդիա բովանդակության հեռարձակում"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"<xliff:g id="APP_LABEL">%1$s</xliff:g> հավելվածի հեռարձակում"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Ակտիվ չէ, ստուգեք հավելվածը"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Չի գտնվել"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Կառավարման տարրը հասանելի չէ"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index dca9565..12495c1 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Terjadi error. Coba lagi."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Memuat"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Nonaktif, periksa aplikasi"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrol tidak tersedia"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index ad94cdc..87b8a73 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Vistað á vinnusniði í <xliff:g id="APP">%1$s</xliff:g>"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> greindi skjámyndina."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> og önnur opin forrit greindu skjámyndina."</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Eitthvað fór úrskeiðis. Reyndu aftur."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Hleður"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"spjaldtölva"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Óvirkt, athugaðu forrit"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Fannst ekki"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Stýring er ekki tiltæk"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 389522f..bc89cbb 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Si è verificato un errore. Riprova."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Caricamento in corso…"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Trasmissione di contenuti multimediali"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Trasmissione di <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Inattivo, controlla l\'app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Controllo non trovato"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Il controllo non è disponibile"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 95871fc..8633127 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"משהו השתבש. יש לנסות שוב."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"בטעינה"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"טאבלט"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"העברה (cast) של מדיה"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"מתבצעת העברה (cast) של <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"לא פעיל, יש לבדוק את האפליקציה"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"לא נמצא"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"הפקד לא זמין"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 92e2c78..9aef2a9 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"エラーが発生しました。もう一度お試しください。"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"読み込んでいます"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"タブレット"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"無効: アプリをご確認ください"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"見つかりませんでした"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"コントロールを使用できません"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 3d8acb3..62efabe 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"რაღაც შეცდომა მოხდა. ცადეთ ხელახლა."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"იტვირთება"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ტაბლეტი"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"მედიის ტრანსლირება"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"მიმდინარეობს <xliff:g id="APP_LABEL">%1$s</xliff:g>-ის ტრანსლირება"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"არააქტიურია, გადაამოწმეთ აპი"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ვერ მოიძებნა"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"კონტროლი მიუწვდომელია"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 0f9dc95..56d8b91 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Бірдеңе дұрыс болмады. Қайталап көріңіз."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Жүктеліп жатыр"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Өшірулі. Қолданба тексеріңіз."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Табылмады"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Басқару виджеті қолжетімсіз"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 9f80e33..3a04488 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"មានអ្វីមួយខុសប្រក្រតី។ សូមព្យាយាមម្ដងទៀត។"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"កំពុងផ្ទុក"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ថេប្លេត"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"អសកម្ម ពិនិត្យមើលកម្មវិធី"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"រកមិនឃើញទេ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"មិនអាចគ្រប់គ្រងបានទេ"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 308707a..dbc7e15 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"ಏನೋ ತಪ್ಪಾಗಿದೆ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"ಲೋಡ್ ಆಗುತ್ತಿದೆ"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ಟ್ಯಾಬ್ಲೆಟ್"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"ನಿಮ್ಮ ಮಾಧ್ಯಮವನ್ನು ಬಿತ್ತರಿಸಲಾಗುತ್ತಿದೆ"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ಅನ್ನು ಬಿತ್ತರಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"ನಿಷ್ಕ್ರಿಯ, ಆ್ಯಪ್ ಪರಿಶೀಲಿಸಿ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ಕಂಡುಬಂದಿಲ್ಲ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ನಿಯಂತ್ರಣ ಲಭ್ಯವಿಲ್ಲ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 2e4aca7..1595de3 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"문제가 발생했습니다. 다시 시도해 주세요."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"로드 중"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"태블릿"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"미디어 전송"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"<xliff:g id="APP_LABEL">%1$s</xliff:g> 전송 중"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"비활성. 앱을 확인하세요."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"찾을 수 없음"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"컨트롤을 사용할 수 없음"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index e4662ed..653292c1 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Бир жерден ката кетти. Кайра аракет кылыңыз."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Жүктөлүүдө"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Медиа тышкы экранга чыгарылууда"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"<xliff:g id="APP_LABEL">%1$s</xliff:g> түзмөгүнө чыгарылууда"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Жигерсиз. Колдонмону текшериңиз"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Табылган жок"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Башкара албайсыз"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 8b99ac3..ce929e9 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"ມີບາງຢ່າງຜິດພາດເກີດຂຶ້ນ. ກະລຸນາລອງໃໝ່."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"ກຳລັງໂຫຼດ"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ແທັບເລັດ"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"ການກຳນົດບົດບາດສື່ຂອງທ່ານ"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"ການກຳນົດບົດບາດ <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"ບໍ່ເຮັດວຽກ, ກະລຸນາກວດສອບແອັບ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ບໍ່ພົບ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ບໍ່ສາມາດໃຊ້ການຄວບຄຸມໄດ້"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 2c4d55b..e153fe0 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Išsaugota programoje „<xliff:g id="APP">%1$s</xliff:g>“ darbo profilyje"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"„<xliff:g id="APPNAME">%1$s</xliff:g>“ aptiko šią ekrano kopiją."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"„<xliff:g id="APPNAME">%1$s</xliff:g>“ ir kitos atidarytos programos aptiko šią ekrano kopiją."</string>
<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>
@@ -865,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Kažkas ne taip. Bandykite dar kartą."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Įkeliama"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"planšetinis kompiuteris"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Perduodama medija"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Perduodama programa „<xliff:g id="APP_LABEL">%1$s</xliff:g>“"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktyvu, patikrinkite progr."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nerasta"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Valdiklis nepasiekiamas"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 412bfe8..2878f7d 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saglabāts lietotnē <xliff:g id="APP">%1$s</xliff:g> darba profilā"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> konstatēja, ka tika veikts ekrānuzņēmums."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> un citas atvērtas lietotnes konstatēja, ka tika veikts ekrānuzņēmums."</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Radās kļūda. Mēģiniet vēlreiz."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Notiek ielāde"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"planšetdators"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktīva, pārbaudiet lietotni"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Netika atrasta"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Vadīkla nav pieejama"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 613dff3..b7d69cf 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Нешто не е во ред. Обидете се повторно."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Се вчитува"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"таблет"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Неактивна, провери апликација"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Не е најдено"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Контролата не е достапна"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index f3848d53..46258cb 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"എന്തോ കുഴപ്പമുണ്ടായി. വീണ്ടും ശ്രമിക്കുക."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"ലോഡ് ചെയ്യുന്നു"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ടാബ്ലെറ്റ്"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"നിങ്ങളുടെ മീഡിയ കാസ്റ്റ് ചെയ്യുന്നു"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"<xliff:g id="APP_LABEL">%1$s</xliff:g> കാസ്റ്റ് ചെയ്യുന്നു"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"നിഷ്ക്രിയം, ആപ്പ് പരിശോധിക്കൂ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"കണ്ടെത്തിയില്ല"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"നിയന്ത്രണം ലഭ്യമല്ല"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 0b108a2..2ef8216 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Алдаа гарлаа. Дахин оролдоно уу."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Ачаалж байна"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"таблет"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Идэвхгүй байна, аппыг шалгана уу"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Олдсонгүй"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Хяналт боломжгүй байна"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 214c92f..c4fd4aa 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"काहीतरी चूक झाली. पुन्हा प्रयत्न करा."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"लोड करत आहे"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"टॅबलेट"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय, ॲप तपासा"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"आढळले नाही"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"नियंत्रण उपलब्ध नाही"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index ff9897d..aa3626c 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Kesilapan telah berlaku. Cuba lagi."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Memuatkan"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Menghantar media anda"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Menghantar <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Tidak aktif, semak apl"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Tidak ditemukan"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kawalan tidak tersedia"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 8abb23c..6338abf 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"တစ်ခုခုမှားသွားသည်။ ထပ်စမ်းကြည့်ပါ။"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"ဖွင့်နေသည်"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"တက်ဘလက်"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"သင့်မီဒီယာကို ကာစ်လုပ်နေသည်"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ကို ကာစ်လုပ်နေသည်"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"ရပ်နေသည်၊ အက်ပ်ကို စစ်ဆေးပါ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"မတွေ့ပါ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ထိန်းချုပ်မှု မရနိုင်ပါ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 4a129e8..9a9ca35 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Lagret i <xliff:g id="APP">%1$s</xliff:g> i jobbprofilen"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> har registrert denne skjermdumpen."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> og andre åpne apper har registrert denne skjermdumpen."</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Noe gikk galt. Prøv på nytt."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Laster inn"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"nettbrett"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv. Sjekk appen"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ikke funnet"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrollen er utilgjengelig"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index e6292fc..e460e26 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"केही चिज गडबड भयो। फेरि प्रयास गर्नुहोस्।"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"लोड हुँदै छ"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ट्याब्लेट"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"तपाईंको मिडिया कास्ट गरिँदै छ"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"<xliff:g id="APP_LABEL">%1$s</xliff:g> कास्ट गरिँदै छ"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"निष्क्रिय छ, एप जाँच गर्नु…"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"फेला परेन"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"नियन्त्रण उपलब्ध छैन"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 9fcc945..8d67715 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Opgeslagen in <xliff:g id="APP">%1$s</xliff:g> in het werkprofiel."</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> heeft dit screenshot waargenomen."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> en andere geopende apps hebben dit screenshot waargenomen."</string>
<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>
@@ -865,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Er is iets misgegaan. Probeer het opnieuw."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Laden"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Je media casten"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"<xliff:g id="APP_LABEL">%1$s</xliff:g> casten"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Inactief, check de app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Niet gevonden"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Beheeroptie niet beschikbaar"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 15a7ea7..992cd86 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"କିଛି ତ୍ରୁଟି ହୋଇଛି। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"ଲୋଡ ହେଉଛି"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ଟାବଲେଟ"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"ନିଷ୍କ୍ରିୟ ଅଛି, ଆପ ଯାଞ୍ଚ କରନ୍ତୁ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ମିଳିଲା ନାହିଁ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ନିୟନ୍ତ୍ରଣ ଉପଲବ୍ଧ ନାହିଁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 03350a9..c814159 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"ਕੋਈ ਗੜਬੜ ਹੋ ਗਈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"ਲੋਡ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ਟੈਬਲੈੱਟ"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"ਅਕਿਰਿਆਸ਼ੀਲ, ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ਨਹੀਂ ਮਿਲਿਆ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ਕੰਟਰੋਲ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 68c540f..025dd66 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Zapisano w aplikacji <xliff:g id="APP">%1$s</xliff:g> w profilu służbowym"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"Aplikacja <xliff:g id="APPNAME">%1$s</xliff:g> wykryła ten zrzut ekranu."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Aplikacja <xliff:g id="APPNAME">%1$s</xliff:g> i inne aplikacje wykryły ten zrzut ekranu."</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Coś poszło nie tak. Spróbuj ponownie."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Wczytuję"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Nieaktywny, sprawdź aplikację"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nie znaleziono"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Element jest niedostępny"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index accb35c..39feb08 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Algo deu errado. Tente novamente."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Carregando"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Transmitindo sua mídia"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Transmitindo o app <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"O controle está indisponível"</string>
@@ -1016,7 +1018,7 @@
<string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• Um app de câmera está instalado"</string>
<string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• O app está disponível"</string>
<string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Pelo menos um dispositivo está disponível"</string>
- <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Mantenha o atalho pressionado"</string>
+ <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Toque e pressione o atalho"</string>
<string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Cancelar"</string>
<string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Virar agora"</string>
<string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Abra o smartphone para tirar uma selfie melhor"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 9ee8afc..98f063f 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Algo correu mal. Tente novamente."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"A carregar"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"A transmitir o conteúdo multimédia"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"A transmitir a app <xliff:g id="APP_LABEL">%1$s</xliff:g>…"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Inativa. Consulte a app."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado."</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"O controlo está indisponível"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index accb35c..39feb08 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Algo deu errado. Tente novamente."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Carregando"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Transmitindo sua mídia"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Transmitindo o app <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Inativo, verifique o app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Não encontrado"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"O controle está indisponível"</string>
@@ -1016,7 +1018,7 @@
<string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction" msgid="5355839079232119791">"• Um app de câmera está instalado"</string>
<string name="keyguard_affordance_enablement_dialog_home_instruction_1" msgid="8438311171750568633">"• O app está disponível"</string>
<string name="keyguard_affordance_enablement_dialog_home_instruction_2" msgid="8308525385889021652">"• Pelo menos um dispositivo está disponível"</string>
- <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Mantenha o atalho pressionado"</string>
+ <string name="keyguard_affordance_press_too_short" msgid="8145437175134998864">"Toque e pressione o atalho"</string>
<string name="rear_display_bottom_sheet_cancel" msgid="3461468855493357248">"Cancelar"</string>
<string name="rear_display_bottom_sheet_confirm" msgid="4383356544661421206">"Virar agora"</string>
<string name="rear_display_fold_bottom_sheet_title" msgid="6081542277622721548">"Abra o smartphone para tirar uma selfie melhor"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index a144755..bf26016 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"A apărut o eroare. Încearcă din nou."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Se încarcă"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tabletă"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inactiv, verifică aplicația"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nu s-a găsit"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Comanda este indisponibilă"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 8ac0b14..1a10a79 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Произошла ошибка. Повторите попытку."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Загрузка…"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Трансляция медиаконтента"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Трансляция: <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Нет ответа. Проверьте приложение."</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Не найдено."</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Управление недоступно"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 5e78e73..217a6e3 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"යම් දෙයක් වැරදිණි. නැවත උත්සාහ කරන්න."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"පූරණය වේ"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ටැබ්ලටය"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"අක්රියයි, යෙදුම පරීක්ෂා කරන්න"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"හමු නොවිණි"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"පාලනය ලබා ගත නොහැකිය"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 884848b..f6f7b37 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Uložená v aplikácii <xliff:g id="APP">%1$s</xliff:g> v pracovnom profile"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"Aplikácia <xliff:g id="APPNAME">%1$s</xliff:g> zaznamenala túto snímku obrazovky."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> a ďalšie otvorené aplikácie zaznamenali túto snímku obrazovky."</string>
<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>
@@ -865,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Niečo sa pokazilo. Skúste to znova."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Načítava sa"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Prenášajú sa médiá"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Prenáša sa <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktívne, preverte aplikáciu"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nenájdené"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Ovládač nie je k dispozícii"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 6b04eab..c188d45 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Prišlo je do napake. Poskusite znova."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Nalaganje"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablični računalnik"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Neaktivno, poglejte aplikacijo"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ni mogoče najti"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrolnik ni na voljo"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index b0fdbe2..c9789a9 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Ndodhi një gabim. Provo përsëri."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Po ngarkohet"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Po transmeton median tënde"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Po transmeton <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Joaktive, kontrollo aplikacionin"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Nuk u gjet"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrolli është i padisponueshëm"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 35de274..d756401 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Дошло је до грешке. Пробајте поново."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Учитава се"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"таблет"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Неактивно. Видите апликацију"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Није пронађено"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Контрола није доступна"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 356c674..d12c25f 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Något gick fel. Försök igen."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Läser in"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"surfplatta"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Inaktiv, kolla appen"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Hittades inte"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Styrning är inte tillgänglig"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index abaad50..564de43 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Imehifadhiwa kwenye <xliff:g id="APP">%1$s</xliff:g> katika wasifu wa kazini"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> imetambua picha hii ya skrini."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> na zingine zinazotumika zimetambua picha hii ya skrini."</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Hitilafu fulani imetokea. Jaribu tena."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Inapakia"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"kompyuta kibao"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Haitumiki, angalia programu"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Hakipatikani"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kidhibiti hakipatikani"</string>
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 4f24d83..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>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 2b88e55..9ed9360 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -22,8 +22,6 @@
<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-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index e3dcccb..b20bd37 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"ஏதோ தவறாகிவிட்டது. மீண்டும் முயலவும்."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"ஏற்றுகிறது"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"டேப்லெட்"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"செயலில் இல்லை , சரிபார்க்கவும்"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"இல்லை"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"கட்டுப்பாடு இல்லை"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index d50b1cf..0719671 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"ఏదో తప్పు జరిగింది. మళ్లీ ట్రై చేయండి."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"లోడ్ అవుతోంది"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"టాబ్లెట్"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"మీ మీడియా ప్రసారం అవుతోంది"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"<xliff:g id="APP_LABEL">%1$s</xliff:g> ప్రసారం అవుతోంది"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"ఇన్యాక్టివ్, యాప్ చెక్ చేయండి"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"కనుగొనబడలేదు"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"కంట్రోల్ అందుబాటులో లేదు"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 930123f..90617ca 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"เกิดข้อผิดพลาด โปรดลองอีกครั้ง"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"กำลังโหลด"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"แท็บเล็ต"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"กำลังแคสต์สื่อ"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"กำลังแคสต์ <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"ไม่มีการใช้งาน โปรดตรวจสอบแอป"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"ไม่พบ"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"ใช้การควบคุมไม่ได้"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 45697d9..4eaa89c 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Nagkaproblema. Subukan ulit."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Naglo-load"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Hindi aktibo, tingnan ang app"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Hindi nahanap"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Hindi available ang kontrol"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index a145f7b..2e53e64 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"İş profilinde <xliff:g id="APP">%1$s</xliff:g> uygulamasına kaydedildi"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> bu ekran görüntüsünü algıladı."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ve diğer açık uygulamalar bu ekran görüntüsünü algıladı."</string>
<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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Bir hata oluştu. Tekrar deneyin."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Yükleme"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"tablet"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Devre dışı, uygulamaya bakın"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Bulunamadı"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Kontrol kullanılamıyor"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 5cb17d1..8b5f7b6 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Сталася помилка. Повторіть спробу."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Завантаження"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"планшет"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Неактивно, перейдіть у додаток"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Не знайдено"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Елемент керування недоступний"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 48ef8c4..c1bca22 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -865,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"کچھ غلط ہوگیا۔ پھر کوشش کریں۔"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"لوڈ ہو رہا ہے"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ٹیبلیٹ"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"غیر فعال، ایپ چیک کریں"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"نہیں ملا"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"کنٹرول دستیاب نہیں ہے"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 573b33a..284193b 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -91,13 +91,10 @@
<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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Ish profilidagi <xliff:g id="APP">%1$s</xliff:g> ilovasiga saqlandi"</string>
<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="screenshot_detected_template" msgid="7940376642921719915">"Bu skrinshotda <xliff:g id="APPNAME">%1$s</xliff:g> aniqlandi."</string>
+ <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Bu skrinshotda <xliff:g id="APPNAME">%1$s</xliff:g> va boshqa ochiq ilovalar aniqlandi"</string>
<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>
@@ -865,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Xatolik yuz berdi. Qayta urining."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Yuklanmoqda"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"planshet"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Mediani translatsiya qilish"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Translatsiya qilinmoqda: <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Nofaol. Ilovani tekshiring"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Topilmadi"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Boshqarish imkonsiz"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 2d5f598..fe77c10 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Đã xảy ra lỗi. Hãy thử lại."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Đang tải"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"máy tính bảng"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"Không hoạt động, hãy kiểm tra ứng dụng"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Không tìm thấy"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Không có chức năng điều khiển"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index b3aef64..3cd6a69 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -91,13 +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>
- <!-- no translation found for screenshot_work_profile_notification (203041724052970693) -->
- <skip />
+ <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>
- <!-- no translation found for screenshot_detected_template (7940376642921719915) -->
- <skip />
- <!-- no translation found for screenshot_detected_multiple_template (7644827792093819241) -->
- <skip />
+ <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>
@@ -655,7 +652,7 @@
<string name="right_keycode" msgid="2480715509844798438">"向右键码"</string>
<string name="left_icon" msgid="5036278531966897006">"向左图标"</string>
<string name="right_icon" msgid="1103955040645237425">"向右图标"</string>
- <string name="drag_to_add_tiles" msgid="8933270127508303672">"按住并拖动即可添加图块"</string>
+ <string name="drag_to_add_tiles" msgid="8933270127508303672">"按住并拖动即可添加功能块"</string>
<string name="drag_to_rearrange_tiles" msgid="2143204300089638620">"按住并拖动即可重新排列图块"</string>
<string name="drag_to_remove_tiles" msgid="4682194717573850385">"拖动到此处即可移除"</string>
<string name="drag_to_remove_disabled" msgid="933046987838658850">"您至少需要 <xliff:g id="MIN_NUM_TILES">%1$d</xliff:g> 个卡片"</string>
@@ -673,15 +670,15 @@
</string-array>
<string name="tuner_low_priority" msgid="8412666814123009820">"显示低优先级的通知图标"</string>
<string name="other" msgid="429768510980739978">"其他"</string>
- <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"移除图块"</string>
- <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"将图块添加到末尾"</string>
- <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"移动图块"</string>
- <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"添加图块"</string>
+ <string name="accessibility_qs_edit_remove_tile_action" msgid="775511891457193480">"移除功能块"</string>
+ <string name="accessibility_qs_edit_tile_add_action" msgid="5051211910345301833">"将功能块添加到末尾"</string>
+ <string name="accessibility_qs_edit_tile_start_move" msgid="2009373939914517817">"移动功能块"</string>
+ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"添加功能块"</string>
<string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"移至 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"添加到位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
<string name="accessibility_qs_edit_position" msgid="4509277359815711830">"位置 <xliff:g id="POSITION">%1$d</xliff:g>"</string>
- <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"已添加卡片"</string>
- <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"已移除卡片"</string>
+ <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"已添加功能块"</string>
+ <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"已移除功能块"</string>
<string name="accessibility_desc_quick_settings_edit" msgid="741658939453595297">"快捷设置编辑器。"</string>
<string name="accessibility_desc_notification_icon" msgid="7331265967584178674">"<xliff:g id="ID_1">%1$s</xliff:g>通知:<xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="accessibility_quick_settings_settings" msgid="7098489591715844713">"打开设置。"</string>
@@ -865,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"出了点问题,请重试。"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"正在加载"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"平板电脑"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"投放您的媒体"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"投放 <xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"无效,请检查应用"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"未找到"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"控件不可用"</string>
@@ -968,9 +967,9 @@
<string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"如要切换网络,请断开以太网连接"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"为了提升设备的使用体验,即使 WLAN 已关闭,应用和服务仍可以随时扫描 WLAN 网络。您可以在 WLAN 扫描设置中更改此设置。"<annotation id="link">"更改"</annotation></string>
<string name="turn_off_airplane_mode" msgid="8425587763226548579">"关闭飞行模式"</string>
- <string name="qs_tile_request_dialog_text" msgid="3501359944139877694">"“<xliff:g id="APPNAME">%1$s</xliff:g>”希望将以下图块添加到“快捷设置”"</string>
- <string name="qs_tile_request_dialog_add" msgid="4888460910694986304">"添加图块"</string>
- <string name="qs_tile_request_dialog_not_add" msgid="4168716573114067296">"不添加图块"</string>
+ <string name="qs_tile_request_dialog_text" msgid="3501359944139877694">"“<xliff:g id="APPNAME">%1$s</xliff:g>”希望将以下功能块添加到“快捷设置”"</string>
+ <string name="qs_tile_request_dialog_add" msgid="4888460910694986304">"添加功能块"</string>
+ <string name="qs_tile_request_dialog_not_add" msgid="4168716573114067296">"不添加功能块"</string>
<string name="qs_user_switch_dialog_title" msgid="3045189293587781366">"选择用户"</string>
<string name="fgs_manager_footer_label" msgid="8276763570622288231">"{count,plural, =1{# 个应用处于活动状态}other{# 个应用处于活动状态}}"</string>
<string name="fgs_dot_content_description" msgid="2865071539464777240">"新信息"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index f13dbf05a..e499fe5 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -91,10 +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="203041724052970693">"已儲存在工作資料夾的「<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="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>
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"發生錯誤,請再試一次。"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"正在載入"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"平板電腦"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"已停用,請檢查應用程式"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"找不到"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"無法使用控制功能"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 0ca96fb..26c60e0 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -862,6 +862,10 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"發生錯誤,請再試一次。"</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"載入中"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"平板電腦"</string>
+ <!-- no translation found for media_transfer_receiver_content_description_unknown_app (7381771464846263667) -->
+ <skip />
+ <!-- no translation found for media_transfer_receiver_content_description_with_app_name (8555975056850659389) -->
+ <skip />
<string name="controls_error_timeout" msgid="794197289772728958">"無效,請查看應用程式"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"找不到控制項"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"無法使用控制項"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 29fb0e9..b585b6c 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -862,6 +862,8 @@
<string name="media_transfer_failed" msgid="7955354964610603723">"Kukhona okungahambanga kahle. Zama futhi."</string>
<string name="media_transfer_loading" msgid="5544017127027152422">"Iyalayisha"</string>
<string name="media_ttt_default_device_type" msgid="4457646436153370169">"ithebulethi"</string>
+ <string name="media_transfer_receiver_content_description_unknown_app" msgid="7381771464846263667">"Isakaza imidiya yakho"</string>
+ <string name="media_transfer_receiver_content_description_with_app_name" msgid="8555975056850659389">"Isakaza i-<xliff:g id="APP_LABEL">%1$s</xliff:g>"</string>
<string name="controls_error_timeout" msgid="794197289772728958">"Akusebenzi, hlola uhlelo lokusebenza"</string>
<string name="controls_error_removed" msgid="6675638069846014366">"Ayitholakali"</string>
<string name="controls_error_removed_title" msgid="1207794911208047818">"Ukulawula akutholakali"</string>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index f46266b..e346fe4 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -214,5 +214,12 @@
<attr name="biometricsEnrollProgressHelp" format="reference|color" />
<attr name="biometricsEnrollProgressHelpWithTalkback" format="reference|color" />
</declare-styleable>
+
+ <declare-styleable name="SeekBarWithIconButtonsView_Layout">
+ <attr name="max" format="integer" />
+ <attr name="progress" format="integer" />
+ <attr name="iconStartContentDescription" format="reference" />
+ <attr name="iconEndContentDescription" format="reference" />
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 371f001..031d40e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -81,7 +81,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream
+ internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling
</string>
<!-- The tiles to display in QuickSettings -->
@@ -654,7 +654,7 @@
<item>26</item> <!-- MOUTH_COVERING_DETECTED -->
</integer-array>
- <!-- Which device wake-ups will trigger face auth. These values correspond with
+ <!-- Which device wake-ups will trigger passive auth. These values correspond with
PowerManager#WakeReason. -->
<integer-array name="config_face_auth_wake_up_triggers">
<item>1</item> <!-- WAKE_REASON_POWER_BUTTON -->
@@ -663,6 +663,7 @@
<item>7</item> <!-- WAKE_REASON_WAKE_MOTION -->
<item>9</item> <!-- WAKE_REASON_LID -->
<item>10</item> <!-- WAKE_REASON_DISPLAY_GROUP_ADDED -->
+ <item>12</item> <!-- WAKE_REASON_UNFOLD_DEVICE -->
<item>15</item> <!-- WAKE_REASON_TAP -->
<item>16</item> <!-- WAKE_REASON_LIFT -->
<item>17</item> <!-- WAKE_REASON_BIOMETRIC -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d492e53c..d48ea214 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -184,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>
@@ -1400,6 +1409,9 @@
<dimen name="padding_above_predefined_icon_for_small">4dp</dimen>
<dimen name="padding_between_suppressed_layout_items">8dp</dimen>
+ <!-- Seekbar with icon buttons -->
+ <dimen name="seekbar_icon_size">24dp</dimen>
+
<!-- Accessibility floating menu -->
<dimen name="accessibility_floating_menu_elevation">3dp</dimen>
<dimen name="accessibility_floating_menu_stroke_width">1dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7d5b66e..e6ac59e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -660,6 +660,8 @@
<string name="quick_settings_inversion_label">Color inversion</string>
<!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
<string name="quick_settings_color_correction_label">Color correction</string>
+ <!-- QuickSettings: Label for font size scaling. [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_font_scaling_label">Font size</string>
<!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] -->
<string name="quick_settings_more_user_settings">Manage users</string>
<!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
@@ -2181,6 +2183,14 @@
<!-- Title of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=25] -->
<string name="inattentive_sleep_warning_title">Standby</string>
+ <!-- Font scaling -->
+ <!-- Font scaling: Quick Settings dialog title [CHAR LIMIT=30] -->
+ <string name="font_scaling_dialog_title">Font Size</string>
+ <!-- Content Description for the icon button to make fonts smaller. [CHAR LIMIT=30] -->
+ <string name="font_scaling_smaller">Make smaller</string>
+ <!-- Content Description for the icon button to make fonts larger. [CHAR LIMIT=30] -->
+ <string name="font_scaling_larger">Make larger</string>
+
<!-- Window Magnification strings -->
<!-- Title for Magnification Window [CHAR LIMIT=NONE] -->
<string name="magnification_window_title">Magnification Window</string>
@@ -2464,7 +2474,10 @@
<string name="media_output_broadcast_update_error">Can\u2019t save. Try again.</string>
<!-- The error message when Broadcast name/code update failed and can't change again[CHAR LIMIT=60] -->
<string name="media_output_broadcast_last_update_error">Can\u2019t save.</string>
-
+ <!-- The hint message when Broadcast code is less than 4 characters [CHAR LIMIT=60] -->
+ <string name="media_output_broadcast_code_hint_no_less_than_min">Use at least 4 characters</string>
+ <!-- The hint message when Broadcast code is more than 16 characters [CHAR LIMIT=60] -->
+ <string name="media_output_broadcast_code_hint_no_more_than_max">Use fewer than 16 characters</string>
<!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]-->
<string name="build_number_clip_data_label">Build number</string>
@@ -2780,15 +2793,15 @@
<!-- Text for education page of cancel button to hide the page. [CHAR_LIMIT=NONE] -->
<string name="rear_display_bottom_sheet_cancel">Cancel</string>
<!-- Text for the user to confirm they flipped the device around. [CHAR_LIMIT=NONE] -->
- <string name="rear_display_bottom_sheet_confirm">Flip now</string>
+ <string name="rear_display_bottom_sheet_confirm">Switch screens now</string>
<!-- Text for education page title to guide user to unfold phone. [CHAR_LIMIT=50] -->
- <string name="rear_display_fold_bottom_sheet_title">Unfold phone for a better selfie</string>
- <!-- Text for education page title to guide user to flip to the front display. [CHAR_LIMIT=50] -->
- <string name="rear_display_unfold_bottom_sheet_title">Flip to front display for a better selfie?</string>
+ <string name="rear_display_folded_bottom_sheet_title">Unfold phone</string>
+ <!-- Text for education page title to guide user to switch to the front display. [CHAR_LIMIT=50] -->
+ <string name="rear_display_unfolded_bottom_sheet_title">Switch screens?</string>
<!-- Text for education page description to suggest user to use rear selfie capture. [CHAR_LIMIT=NONE] -->
- <string name="rear_display_bottom_sheet_description">Use the rear-facing camera for a wider photo with higher resolution.</string>
- <!-- Text for education page description to warn user that the display will turn off if the button is clicked. [CHAR_LIMIT=NONE] -->
- <string name="rear_display_bottom_sheet_warning"><b>✱ This screen will turn off</b></string>
+ <string name="rear_display_folded_bottom_sheet_description">For higher resolution, use the rear camera</string>
+ <!-- Text for unfolded education page description to suggest user to use rear selfie capture. [CHAR_LIMIT=NONE] -->
+ <string name="rear_display_unfolded_bottom_sheet_description">For higher resolution, flip the phone</string>
<!-- Text for education page content description for folded animation. [CHAR_LIMIT=NONE] -->
<string name="rear_display_accessibility_folded_animation">Foldable device being unfolded</string>
<!-- Text for education page content description for unfolded animation. [CHAR_LIMIT=NONE] -->
@@ -2829,4 +2842,19 @@
[CHAR LIMIT=32]
-->
<string name="lock_screen_settings">Lock screen settings</string>
+
+ <!-- Content description for Wi-Fi not available icon on dream [CHAR LIMIT=NONE]-->
+ <string name="wifi_unavailable_dream_overlay_content_description">Wi-Fi not available</string>
+
+ <!-- Content description for camera blocked icon on dream [CHAR LIMIT=NONE] -->
+ <string name="camera_blocked_dream_overlay_content_description">Camera blocked</string>
+
+ <!-- Content description for camera and microphone blocked icon on dream [CHAR LIMIT=NONE] -->
+ <string name="camera_and_microphone_blocked_dream_overlay_content_description">Camera and microphone blocked</string>
+
+ <!-- Content description for camera and microphone disabled icon on dream [CHAR LIMIT=NONE] -->
+ <string name="microphone_blocked_dream_overlay_content_description">Microphone blocked</string>
+
+ <!-- Content description for priority mode icon on dream [CHAR LIMIT=NONE] -->
+ <string name="priority_mode_dream_overlay_content_description">Priority mode on</string>
</resources>
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index c809551..7020d54 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -318,4 +318,14 @@
<item>Off</item>
<item>On</item>
</string-array>
+
+ <!-- State names for font scaling tile: unavailable, off, on.
+ This subtitle is shown when the tile is in that particular state but does not set its own
+ subtitle, so some of these may never appear on screen. They should still be translated as
+ if they could appear. [CHAR LIMIT=32] -->
+ <string-array name="tile_states_font_scaling">
+ <item>Unavailable</item>
+ <item>Off</item>
+ <item>On</item>
+ </string-array>
</resources>
\ No newline at end of file
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/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/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 037a71e..65dedc6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -43,6 +43,7 @@
public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
+ public static final String KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER = "extra_unfold_animation";
// See ISysuiUnlockAnimationController.aidl
public static final String KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER = "unlock_animation";
diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
index 54ae84f9..ead1a10 100644
--- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt
@@ -303,9 +303,18 @@
pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup")
pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent")
pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail")
- pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=${
- onUnlockIntentWhenBiometricEnrolled.map { BiometricType.values()[it] }
- }")
+
+ val onUnlockIntentWhenBiometricEnrolledString =
+ onUnlockIntentWhenBiometricEnrolled.map {
+ for (biometricType in BiometricType.values()) {
+ if (biometricType.intValue == it) {
+ return@map biometricType.name
+ }
+ }
+ return@map "UNKNOWN"
+ }
+ pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=" +
+ "$onUnlockIntentWhenBiometricEnrolledString")
pw.println(" requestActiveUnlockOnFaceError=$faceErrorsToTriggerBiometricFailOn")
pw.println(" requestActiveUnlockOnFaceAcquireInfo=" +
"$faceAcquireInfoToTriggerBiometricFailOn")
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index ea079a9..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,35 +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.statusbar.Weather
+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,
@@ -115,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)
@@ -189,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
@@ -224,64 +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))
- }
-
- override fun onWeatherDataChanged(data: Weather?) {
- if (data != null) {
- clock?.events?.onWeatherDataChanged(data)
- }
- }
- }
-
fun registerListeners(parent: View) {
if (isRegistered) {
return
@@ -295,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)
}
@@ -344,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) {
@@ -359,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
@@ -432,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)
@@ -442,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 879a95c..baaeb2a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -404,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;
}
@@ -421,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;
}
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/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 6139403..77f6318 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -207,6 +207,13 @@
}
/**
+ * Reinflate the view flipper child view.
+ */
+ public void reinflateViewFlipper() {
+ mKeyguardSecurityContainerController.reinflateViewFlipper();
+ }
+
+ /**
* Dismisses the keyguard by going to the next screen or making it gone.
* @param targetUserId a user that needs to be the foreground user at the dismissal completion.
* @return True if the keyguard is done.
@@ -232,23 +239,19 @@
/**
* Starts the animation when the Keyguard gets shown.
*/
- public void appear(int statusBarHeight) {
+ public void appear() {
// We might still be collapsed and the view didn't have time to layout yet or still
// be small, let's wait on the predraw to do the animation in that case.
- if (mView.getHeight() != 0 && mView.getHeight() != statusBarHeight) {
- mKeyguardSecurityContainerController.startAppearAnimation();
- } else {
- mView.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- mView.getViewTreeObserver().removeOnPreDrawListener(this);
- mKeyguardSecurityContainerController.startAppearAnimation();
- return true;
- }
- });
- mView.requestLayout();
- }
+ mView.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mKeyguardSecurityContainerController.startAppearAnimation();
+ return true;
+ }
+ });
+ mView.requestLayout();
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 9fcacce..d9b8895 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -33,6 +33,7 @@
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.BiometricSourceType;
import android.metrics.LogMaker;
import android.os.UserHandle;
@@ -379,7 +380,8 @@
+ "isUnlockingWithFpAllowed=" + isUnlockingWithFpAllowed);
}
if (toShow) {
- mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
} else {
mSideFpsController.get().hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
}
@@ -743,17 +745,20 @@
}
private void reloadColors() {
- resetViewFlipper();
+ reinflateViewFlipper();
mView.reloadColors();
}
/** Handles density or font scale changes. */
private void onDensityOrFontScaleChanged() {
- resetViewFlipper();
+ reinflateViewFlipper();
mView.onDensityOrFontScaleChanged();
}
- private void resetViewFlipper() {
+ /**
+ * Reinflate the view flipper child view.
+ */
+ public void reinflateViewFlipper() {
mSecurityViewFlipperController.clearViews();
mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
mKeyguardSecurityCallback);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index cd5b7c1..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;
@@ -147,12 +148,12 @@
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;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.Weather;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -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());
@@ -1741,7 +1744,7 @@
public void onAuthenticationFailed() {
String reason =
mKeyguardBypassController.canBypass() ? "bypass"
- : mUdfpsBouncerShowing ? "udfpsBouncer"
+ : mAlternateBouncerShowing ? "alternateBouncer"
: mPrimaryBouncerFullyShown ? "bouncer"
: "udfpsFpDown";
requestActiveUnlock(
@@ -2600,7 +2603,7 @@
requestActiveUnlock(
requestOrigin,
extraReason, canFaceBypass
- || mUdfpsBouncerShowing
+ || mAlternateBouncerShowing
|| mPrimaryBouncerFullyShown
|| mAuthController.isUdfpsFingerDown());
}
@@ -2618,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.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
- "udfpsBouncer");
+ "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);
@@ -2818,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 =
@@ -2828,11 +2830,11 @@
|| awakeKeyguard
|| shouldListenForFaceAssistant
|| isUdfpsFingerDown
- || mUdfpsBouncerShowing)
+ || mAlternateBouncerShowing)
&& !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded
&& !mKeyguardGoingAway && biometricEnabledForUser
&& faceAuthAllowedOrDetectionIsNeeded && mIsPrimaryUser
- && (!mSecureCameraLaunched || mOccludingAppRequestingFace)
+ && (!mSecureCameraLaunched || mAlternateBouncerShowing)
&& faceAndFpNotAuthenticated
&& !mGoingToSleep
&& isPostureAllowedForFaceAuth;
@@ -2843,6 +2845,7 @@
System.currentTimeMillis(),
user,
shouldListen,
+ mAlternateBouncerShowing,
mAuthInterruptActive,
biometricEnabledForUser,
mPrimaryBouncerFullyShown,
@@ -2860,7 +2863,6 @@
mSecureCameraLaunched,
supportsDetect,
mSwitchingUser,
- mUdfpsBouncerShowing,
isUdfpsFingerDown,
userNotTrustedOrDetectionIsNeeded));
@@ -2948,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
@@ -2981,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) {
@@ -3954,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 4a7dd24..0da799e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -23,8 +23,8 @@
import androidx.annotation.Nullable;
import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.plugins.Weather;
import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.Weather;
import java.util.TimeZone;
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8071a5d..0887b22 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -466,6 +466,17 @@
}
}
+ /**
+ * @return whether the userUnlockedWithBiometric state changed
+ */
+ private boolean updateUserUnlockedWithBiometric() {
+ final boolean wasUserUnlockedWithBiometric = mUserUnlockedWithBiometric;
+ mUserUnlockedWithBiometric =
+ mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
+ KeyguardUpdateMonitor.getCurrentUser());
+ return wasUserUnlockedWithBiometric != mUserUnlockedWithBiometric;
+ }
+
private StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
@@ -503,11 +514,7 @@
@Override
public void onBiometricsCleared() {
- final boolean wasUserUnlockedWithBiometric = mUserUnlockedWithBiometric;
- mUserUnlockedWithBiometric =
- mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
- KeyguardUpdateMonitor.getCurrentUser());
- if (wasUserUnlockedWithBiometric != mUserUnlockedWithBiometric) {
+ if (updateUserUnlockedWithBiometric()) {
updateVisibility();
}
}
@@ -516,10 +523,8 @@
public void onBiometricRunningStateChanged(boolean running,
BiometricSourceType biometricSourceType) {
final boolean wasRunningFps = mRunningFPS;
- final boolean wasUserUnlockedWithBiometric = mUserUnlockedWithBiometric;
- mUserUnlockedWithBiometric =
- mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
- KeyguardUpdateMonitor.getCurrentUser());
+ final boolean userUnlockedWithBiometricChanged =
+ updateUserUnlockedWithBiometric();
if (biometricSourceType == FINGERPRINT) {
mRunningFPS = running;
@@ -537,8 +542,7 @@
}
}
- if (wasUserUnlockedWithBiometric != mUserUnlockedWithBiometric
- || wasRunningFps != mRunningFPS) {
+ if (userUnlockedWithBiometricChanged || wasRunningFps != mRunningFPS) {
updateVisibility();
}
}
@@ -549,6 +553,7 @@
@Override
public void onUnlockedChanged() {
mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
+ updateUserUnlockedWithBiometric();
updateKeyguardShowing();
updateVisibility();
}
@@ -566,9 +571,7 @@
updateKeyguardShowing();
if (mIsKeyguardShowing) {
- mUserUnlockedWithBiometric =
- mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
- KeyguardUpdateMonitor.getCurrentUser());
+ updateUserUnlockedWithBiometric();
}
updateVisibility();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index 41111e3..b30a0e0 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -51,6 +51,7 @@
private float mStartRadius;
private float mEndRadius;
private int mHeight;
+ private boolean mInitialized;
private static final int EXPAND_ANIMATION_MS = 100;
private static final int EXPAND_COLOR_ANIMATION_MS = 50;
@@ -95,9 +96,13 @@
mHeight = height;
mStartRadius = height / 2f;
mEndRadius = height / 4f;
- mBackground.setCornerRadius(mStartRadius);
mExpandAnimator.setFloatValues(mStartRadius, mEndRadius);
mContractAnimator.setFloatValues(mEndRadius, mStartRadius);
+ // Set initial corner radius.
+ if (!mInitialized) {
+ mBackground.setCornerRadius(mStartRadius);
+ mInitialized = true;
+ }
}
/**
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/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
new file mode 100644
index 0000000..799a4d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.accessibility
+
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.DreamTile
+import com.android.systemui.qs.tiles.FontScalingTile
+import com.android.systemui.qs.tiles.NightDisplayTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface AccessibilityModule {
+
+ /** Inject ColorInversionTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(ColorInversionTile.TILE_SPEC)
+ fun bindColorInversionTile(colorInversionTile: ColorInversionTile): QSTileImpl<*>
+
+ /** Inject NightDisplayTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(NightDisplayTile.TILE_SPEC)
+ fun bindNightDisplayTile(nightDisplayTile: NightDisplayTile): QSTileImpl<*>
+
+ /** Inject ReduceBrightColorsTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(ReduceBrightColorsTile.TILE_SPEC)
+ fun bindReduceBrightColorsTile(reduceBrightColorsTile: ReduceBrightColorsTile): QSTileImpl<*>
+
+ /** Inject OneHandedModeTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(OneHandedModeTile.TILE_SPEC)
+ fun bindOneHandedModeTile(oneHandedModeTile: OneHandedModeTile): QSTileImpl<*>
+
+ /** Inject ColorCorrectionTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(ColorCorrectionTile.TILE_SPEC)
+ fun bindColorCorrectionTile(colorCorrectionTile: ColorCorrectionTile): QSTileImpl<*>
+
+ /** Inject DreamTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(DreamTile.TILE_SPEC)
+ fun bindDreamTile(dreamTile: DreamTile): QSTileImpl<*>
+
+ /** Inject FontScalingTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(FontScalingTile.TILE_SPEC)
+ fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
new file mode 100644
index 0000000..54f933a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.accessibility.fontscaling
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
+import android.os.Bundle
+import android.provider.Settings
+import android.view.LayoutInflater
+import android.widget.Button
+import android.widget.SeekBar
+import android.widget.SeekBar.OnSeekBarChangeListener
+import android.widget.TextView
+import com.android.systemui.R
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.settings.SystemSettings
+
+/** The Dialog that contains a seekbar for changing the font size. */
+class FontScalingDialog(context: Context, private val systemSettings: SystemSettings) :
+ SystemUIDialog(context) {
+ private val strEntryValues: Array<String> =
+ context.resources.getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
+ private lateinit var title: TextView
+ private lateinit var doneButton: Button
+ private lateinit var seekBarWithIconButtonsView: SeekBarWithIconButtonsView
+
+ private val configuration: Configuration =
+ Configuration(context.getResources().getConfiguration())
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setTitle(R.string.font_scaling_dialog_title)
+ setView(LayoutInflater.from(context).inflate(R.layout.font_scaling_dialog, null))
+ setPositiveButton(
+ R.string.quick_settings_done,
+ /* onClick = */ null,
+ /* dismissOnClick = */ true
+ )
+ super.onCreate(savedInstanceState)
+
+ title = requireViewById(com.android.internal.R.id.alertTitle)
+ doneButton = requireViewById(com.android.internal.R.id.button1)
+ seekBarWithIconButtonsView = requireViewById(R.id.font_scaling_slider)
+
+ seekBarWithIconButtonsView.setMax((strEntryValues).size - 1)
+
+ val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f)
+ seekBarWithIconButtonsView.setProgress(fontSizeValueToIndex(currentScale))
+
+ seekBarWithIconButtonsView.setOnSeekBarChangeListener(
+ object : OnSeekBarChangeListener {
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ systemSettings.putString(Settings.System.FONT_SCALE, strEntryValues[progress])
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar) {
+ // Do nothing
+ }
+
+ override fun onStopTrackingTouch(seekBar: SeekBar) {
+ // Do nothing
+ }
+ }
+ )
+ doneButton.setOnClickListener { dismiss() }
+ }
+
+ private fun fontSizeValueToIndex(value: Float): Int {
+ var lastValue = strEntryValues[0].toFloat()
+ for (i in 1 until strEntryValues.size) {
+ val thisValue = strEntryValues[i].toFloat()
+ if (value < lastValue + (thisValue - lastValue) * .5f) {
+ return i - 1
+ }
+ lastValue = thisValue
+ }
+ return strEntryValues.size - 1
+ }
+
+ override fun onConfigurationChanged(configuration: Configuration) {
+ super.onConfigurationChanged(configuration)
+
+ val configDiff = configuration.diff(this.configuration)
+ this.configuration.setTo(configuration)
+
+ if (configDiff and ActivityInfo.CONFIG_FONT_SCALE != 0) {
+ title.post {
+ title.setTextAppearance(R.style.TextAppearance_Dialog_Title)
+ doneButton.setTextAppearance(R.style.Widget_Dialog_Button)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
new file mode 100644
index 0000000..4173790
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.battery
+
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.BatterySaverTile
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface BatterySaverModule {
+
+ /** Inject BatterySaverTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(BatterySaverTile.TILE_SPEC)
+ fun bindBatterySaverTile(batterySaverTile: BatterySaverTile): QSTileImpl<*>
+}
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/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index c799e91..6c49078 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -19,6 +19,8 @@
import android.animation.AnimatorListenerAdapter
import android.app.ActivityTaskManager
import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
@@ -90,7 +92,7 @@
private val featureFlags: FeatureFlags,
dumpManager: DumpManager
) : Dumpable {
- val requests: HashSet<SideFpsUiRequestSource> = HashSet()
+ private val requests: HashSet<SideFpsUiRequestSource> = HashSet()
@VisibleForTesting
val sensorProps: FingerprintSensorPropertiesInternal =
@@ -98,13 +100,17 @@
?: throw IllegalStateException("no side fingerprint sensor")
@VisibleForTesting
- val orientationListener =
- BiometricDisplayListener(
+ val orientationReasonListener =
+ OrientationReasonListener(
context,
displayManager,
handler,
- BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
- ) { onOrientationChanged() }
+ sensorProps,
+ { reason -> onOrientationChanged(reason) },
+ BiometricOverlayConstants.REASON_UNKNOWN
+ )
+
+ @VisibleForTesting val orientationListener = orientationReasonListener.orientationListener
@VisibleForTesting
val overviewProxyListener =
@@ -168,7 +174,7 @@
@BiometricOverlayConstants.ShowReason reason: Int
) =
if (reason.isReasonToAutoShow(activityTaskManager)) {
- show(SideFpsUiRequestSource.AUTO_SHOW)
+ show(SideFpsUiRequestSource.AUTO_SHOW, reason)
} else {
hide(SideFpsUiRequestSource.AUTO_SHOW)
}
@@ -198,11 +204,14 @@
}
/** Shows the side fps overlay if not already shown. */
- fun show(request: SideFpsUiRequestSource) {
+ fun show(
+ request: SideFpsUiRequestSource,
+ @BiometricOverlayConstants.ShowReason reason: Int = BiometricOverlayConstants.REASON_UNKNOWN
+ ) {
requests.add(request)
mainExecutor.execute {
if (overlayView == null) {
- createOverlayForDisplay()
+ createOverlayForDisplay(reason)
} else {
Log.v(TAG, "overlay already shown")
}
@@ -226,13 +235,13 @@
}
}
- private fun onOrientationChanged() {
+ private fun onOrientationChanged(@BiometricOverlayConstants.ShowReason reason: Int) {
if (overlayView != null) {
- createOverlayForDisplay()
+ createOverlayForDisplay(reason)
}
}
- private fun createOverlayForDisplay() {
+ private fun createOverlayForDisplay(@BiometricOverlayConstants.ShowReason reason: Int) {
val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
overlayView = view
val display = context.display!!
@@ -263,7 +272,8 @@
updateOverlayParams(display, it.bounds)
}
}
- lottie.addOverlayDynamicColor(context)
+ orientationReasonListener.reason = reason
+ lottie.addOverlayDynamicColor(context, reason)
/**
* Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
@@ -418,17 +428,36 @@
private fun WindowInsets.hasBigNavigationBar(): Boolean =
getInsets(WindowInsets.Type.navigationBars()).bottom >= 70
-private fun LottieAnimationView.addOverlayDynamicColor(context: Context) {
+private fun LottieAnimationView.addOverlayDynamicColor(
+ context: Context,
+ @BiometricOverlayConstants.ShowReason reason: Int
+) {
fun update() {
val c = context.getColor(R.color.biometric_dialog_accent)
val chevronFill = context.getColor(R.color.sfps_chevron_fill)
- for (key in listOf(".blue600", ".blue400")) {
- addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP)
+ val isKeyguard = reason == REASON_AUTH_KEYGUARD
+ if (isKeyguard) {
+ for (key in listOf(".blue600", ".blue400")) {
+ addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP)
+ }
}
- }
- addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
+ addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
+ }
+ } else if (!isDarkMode(context)) {
+ addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
+ }
+ } else if (isDarkMode(context)) {
+ for (key in listOf(".blue600", ".blue400")) {
+ addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(
+ context.getColor(R.color.settingslib_color_blue400),
+ PorterDuff.Mode.SRC_ATOP
+ )
+ }
+ }
}
}
@@ -439,6 +468,29 @@
}
}
+private fun isDarkMode(context: Context): Boolean {
+ val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+ return darkMode == Configuration.UI_MODE_NIGHT_YES
+}
+
+@VisibleForTesting
+class OrientationReasonListener(
+ context: Context,
+ displayManager: DisplayManager,
+ handler: Handler,
+ sensorProps: FingerprintSensorPropertiesInternal,
+ onOrientationChanged: (reason: Int) -> Unit,
+ @BiometricOverlayConstants.ShowReason var reason: Int
+) {
+ val orientationListener =
+ BiometricDisplayListener(
+ context,
+ displayManager,
+ handler,
+ BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
+ ) { onOrientationChanged(reason) }
+}
+
/**
* The source of a request to show the side fps visual indicator. This is distinct from
* [BiometricOverlayConstants] which corrresponds with the reason fingerprint authentication is
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 82bb723..edda8752 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -97,8 +97,9 @@
return;
}
- if (!isUserSetupComplete()) {
- // just show a toast, user should not access intents from this state
+ if (!isUserSetupComplete() // user should not access intents from this state
+ || clipData == null // shouldn't happen, but just in case
+ || clipData.getItemCount() == 0) {
if (shouldShowToast(clipData)) {
mUiEventLogger.log(CLIPBOARD_TOAST_SHOWN, 0, clipSource);
mClipboardToast.showCopiedToast();
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
index c7aaf09..789833c 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
@@ -19,19 +19,23 @@
import android.content.ClipDescription.EXTRA_IS_SENSITIVE
import android.content.Context
import android.graphics.Bitmap
+import android.net.Uri
import android.text.TextUtils
import android.util.Log
import android.util.Size
+import android.view.textclassifier.TextLinks
import com.android.systemui.R
import java.io.IOException
data class ClipboardModel(
- val clipData: ClipData?,
+ val clipData: ClipData,
val source: String,
- val type: Type = Type.OTHER,
- val item: ClipData.Item? = null,
- val isSensitive: Boolean = false,
- val isRemote: Boolean = false,
+ val type: Type,
+ val text: CharSequence?,
+ val textLinks: TextLinks?,
+ val uri: Uri?,
+ val isSensitive: Boolean,
+ val isRemote: Boolean,
) {
private var _bitmap: Bitmap? = null
@@ -41,17 +45,16 @@
}
return source == other.source &&
type == other.type &&
- item?.text == other.item?.text &&
- item?.uri == other.item?.uri &&
+ text == other.text &&
+ uri == other.uri &&
isSensitive == other.isSensitive
}
fun loadThumbnail(context: Context): Bitmap? {
- if (_bitmap == null && type == Type.IMAGE && item?.uri != null) {
+ if (_bitmap == null && type == Type.IMAGE && uri != null) {
try {
val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
- _bitmap =
- context.contentResolver.loadThumbnail(item.uri, Size(size, size * 4), null)
+ _bitmap = context.contentResolver.loadThumbnail(uri, Size(size, size * 4), null)
} catch (e: IOException) {
Log.e(TAG, "Thumbnail loading failed!", e)
}
@@ -66,27 +69,34 @@
fun fromClipData(
context: Context,
utils: ClipboardOverlayUtils,
- clipData: ClipData?,
+ clipData: ClipData,
source: String
): ClipboardModel {
- if (clipData == null || clipData.itemCount == 0) {
- return ClipboardModel(clipData, source)
- }
val sensitive = clipData.description?.extras?.getBoolean(EXTRA_IS_SENSITIVE) ?: false
val item = clipData.getItemAt(0)!!
val type = getType(context, item)
val remote = utils.isRemoteCopy(context, clipData, source)
- return ClipboardModel(clipData, source, type, item, sensitive, remote)
+ return ClipboardModel(
+ clipData,
+ source,
+ type,
+ item.text,
+ item.textLinks,
+ item.uri,
+ sensitive,
+ remote
+ )
}
private fun getType(context: Context, item: ClipData.Item): Type {
return if (!TextUtils.isEmpty(item.text)) {
Type.TEXT
- } else if (
- item.uri != null &&
- context.contentResolver.getType(item.uri)?.startsWith("image") == true
- ) {
- Type.IMAGE
+ } else if (item.uri != null) {
+ if (context.contentResolver.getType(item.uri)?.startsWith("image") == true) {
+ Type.IMAGE
+ } else {
+ Type.URI
+ }
} else {
Type.OTHER
}
@@ -96,6 +106,7 @@
enum class Type {
TEXT,
IMAGE,
+ URI,
OTHER
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index b41f308..c214f53 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -19,7 +19,6 @@
import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -103,7 +102,6 @@
private Runnable mOnSessionCompleteListener;
private Runnable mOnRemoteCopyTapped;
private Runnable mOnShareTapped;
- private Runnable mOnEditTapped;
private Runnable mOnPreviewTapped;
private InputMonitor mInputMonitor;
@@ -155,13 +153,6 @@
}
@Override
- public void onEditButtonTapped() {
- if (mOnEditTapped != null) {
- mOnEditTapped.run();
- }
- }
-
- @Override
public void onRemoteCopyButtonTapped() {
if (mOnRemoteCopyTapped != null) {
mOnRemoteCopyTapped.run();
@@ -309,14 +300,14 @@
if ((mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) && model.isRemote())
|| DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
- if (model.getItem().getTextLinks() != null) {
+ if (model.getTextLinks() != null) {
classifyText(model);
}
}
if (model.isSensitive()) {
mView.showTextPreview(mContext.getString(R.string.clipboard_asterisks), true);
} else {
- mView.showTextPreview(model.getItem().getText(), false);
+ mView.showTextPreview(model.getText(), false);
}
mView.setEditAccessibilityAction(true);
mOnPreviewTapped = this::editText;
@@ -326,12 +317,13 @@
mView.showImagePreview(
model.isSensitive() ? null : model.loadThumbnail(mContext));
mView.setEditAccessibilityAction(true);
- mOnPreviewTapped = () -> editImage(model.getItem().getUri());
+ mOnPreviewTapped = () -> editImage(model.getUri());
} else {
// image loading failed
mView.showDefaultTextPreview();
}
break;
+ case URI:
case OTHER:
mView.showDefaultTextPreview();
break;
@@ -371,8 +363,8 @@
private void classifyText(ClipboardModel model) {
mBgExecutor.execute(() -> {
- Optional<RemoteAction> remoteAction =
- mClipboardUtils.getAction(model.getItem(), model.getSource());
+ Optional<RemoteAction> remoteAction = mClipboardUtils.getAction(
+ model.getText(), model.getTextLinks(), model.getSource());
if (model.equals(mClipboardModel)) {
remoteAction.ifPresent(action -> {
mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
@@ -419,10 +411,10 @@
accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
} else if (clipData.getItemAt(0).getUri() != null) {
if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
- mOnShareTapped = () -> shareContent(clipData);
- mView.showShareChip();
accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
}
+ mOnShareTapped = () -> shareContent(clipData);
+ mView.showShareChip();
} else {
mView.showDefaultTextPreview();
}
@@ -522,11 +514,6 @@
mView.showTextPreview(text, hidden);
mView.setEditAccessibilityAction(true);
mOnPreviewTapped = this::editText;
- if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
- mOnEditTapped = this::editText;
- mView.showEditChip(mContext.getString(R.string.clipboard_edit_text_description));
- }
}
private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
@@ -557,10 +544,6 @@
} else {
mView.showDefaultTextPreview();
}
- if (isEditableImage && DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_EDIT_BUTTON, false)) {
- mView.showEditChip(mContext.getString(R.string.clipboard_edit_image_description));
- }
return isEditableImage;
}
@@ -636,7 +619,6 @@
private void reset() {
mOnRemoteCopyTapped = null;
mOnShareTapped = null;
- mOnEditTapped = null;
mOnPreviewTapped = null;
mView.reset();
mTimeoutHandler.cancelTimeout();
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
index 785e4a0..a85f8b9 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
@@ -65,6 +65,23 @@
return false;
}
+ public Optional<RemoteAction> getAction(CharSequence text, TextLinks textLinks, String source) {
+ return getActions(text, textLinks).stream().filter(remoteAction -> {
+ ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
+ return component != null && !TextUtils.equals(source, component.getPackageName());
+ }).findFirst();
+ }
+
+ private ArrayList<RemoteAction> getActions(CharSequence text, TextLinks textLinks) {
+ ArrayList<RemoteAction> actions = new ArrayList<>();
+ for (TextLinks.TextLink link : textLinks.getLinks()) {
+ TextClassification classification = mTextClassifier.classifyText(
+ text, link.getStart(), link.getEnd(), null);
+ actions.addAll(classification.getActions());
+ }
+ return actions;
+ }
+
public Optional<RemoteAction> getAction(ClipData.Item item, String source) {
return getActions(item).stream().filter(remoteAction -> {
ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index c9e01ce..f372bb4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -70,8 +70,6 @@
void onRemoteCopyButtonTapped();
- void onEditButtonTapped();
-
void onShareButtonTapped();
void onPreviewTapped();
@@ -94,7 +92,6 @@
private TextView mHiddenPreview;
private LinearLayout mMinimizedPreview;
private View mPreviewBorder;
- private OverlayActionChip mEditChip;
private OverlayActionChip mShareChip;
private OverlayActionChip mRemoteCopyChip;
private View mActionContainerBackground;
@@ -126,18 +123,14 @@
mTextPreview = requireViewById(R.id.text_preview);
mHiddenPreview = requireViewById(R.id.hidden_preview);
mMinimizedPreview = requireViewById(R.id.minimized_preview);
- mEditChip = requireViewById(R.id.edit_chip);
mShareChip = requireViewById(R.id.share_chip);
mRemoteCopyChip = requireViewById(R.id.remote_copy_chip);
mDismissButton = requireViewById(R.id.dismiss_button);
- mEditChip.setAlpha(1);
mShareChip.setAlpha(1);
mRemoteCopyChip.setAlpha(1);
mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share));
- mEditChip.setIcon(
- Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
mRemoteCopyChip.setIcon(
Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true);
mShareChip.setIcon(
@@ -159,7 +152,6 @@
public void setCallbacks(SwipeDismissCallbacks callbacks) {
super.setCallbacks(callbacks);
ClipboardOverlayCallbacks clipboardCallbacks = (ClipboardOverlayCallbacks) callbacks;
- mEditChip.setOnClickListener(v -> clipboardCallbacks.onEditButtonTapped());
mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped());
mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped());
mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped());
@@ -259,7 +251,6 @@
updateTextSize(text, textView);
}
});
- mEditChip.setVisibility(View.GONE);
}
void showImagePreview(@Nullable Bitmap thumbnail) {
@@ -272,12 +263,6 @@
}
}
- void showEditChip(String contentDescription) {
- mEditChip.setVisibility(View.VISIBLE);
- mActionContainerBackground.setVisibility(View.VISIBLE);
- mEditChip.setContentDescription(contentDescription);
- }
-
void showShareChip() {
mShareChip.setVisibility(View.VISIBLE);
mActionContainerBackground.setVisibility(View.VISIBLE);
@@ -289,7 +274,6 @@
mActionContainerBackground.setVisibility(View.GONE);
mDismissButton.setVisibility(View.GONE);
mShareChip.setVisibility(View.GONE);
- mEditChip.setVisibility(View.GONE);
mRemoteCopyChip.setVisibility(View.GONE);
setEditAccessibilityAction(false);
resetActionChips();
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
new file mode 100644
index 0000000..8262539
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -0,0 +1,202 @@
+/*
+ * 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.ui.view;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+
+import com.android.systemui.R;
+
+/**
+ * The layout contains a seekbar whose progress could be modified
+ * through the icons on two ends of the seekbar.
+ */
+public class SeekBarWithIconButtonsView extends LinearLayout {
+
+ private static final int DEFAULT_SEEKBAR_MAX = 6;
+ private static final int DEFAULT_SEEKBAR_PROGRESS = 0;
+
+ private ViewGroup mIconStartFrame;
+ private ViewGroup mIconEndFrame;
+ private ImageView mIconStart;
+ private ImageView mIconEnd;
+ private SeekBar mSeekbar;
+
+ private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
+
+ public SeekBarWithIconButtonsView(Context context) {
+ this(context, null);
+ }
+
+ public SeekBarWithIconButtonsView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public SeekBarWithIconButtonsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SeekBarWithIconButtonsView(Context context,
+ AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ LayoutInflater.from(context).inflate(
+ R.layout.seekbar_with_icon_buttons, this, /* attachToRoot= */ true);
+
+ mIconStartFrame = findViewById(R.id.icon_start_frame);
+ mIconEndFrame = findViewById(R.id.icon_end_frame);
+ mIconStart = findViewById(R.id.icon_start);
+ mIconEnd = findViewById(R.id.icon_end);
+ mSeekbar = findViewById(R.id.seekbar);
+
+ if (attrs != null) {
+ TypedArray typedArray = context.obtainStyledAttributes(
+ attrs,
+ R.styleable.SeekBarWithIconButtonsView_Layout,
+ defStyleAttr, defStyleRes
+ );
+ int max = typedArray.getInt(
+ R.styleable.SeekBarWithIconButtonsView_Layout_max, DEFAULT_SEEKBAR_MAX);
+ int progress = typedArray.getInt(
+ R.styleable.SeekBarWithIconButtonsView_Layout_progress,
+ DEFAULT_SEEKBAR_PROGRESS);
+ mSeekbar.setMax(max);
+ setProgress(progress);
+
+ int iconStartFrameContentDescriptionId = typedArray.getResourceId(
+ R.styleable.SeekBarWithIconButtonsView_Layout_iconStartContentDescription,
+ /* defValue= */ 0);
+ int iconEndFrameContentDescriptionId = typedArray.getResourceId(
+ R.styleable.SeekBarWithIconButtonsView_Layout_iconEndContentDescription,
+ /* defValue= */ 0);
+ if (iconStartFrameContentDescriptionId != 0) {
+ final String contentDescription =
+ context.getString(iconStartFrameContentDescriptionId);
+ mIconStartFrame.setContentDescription(contentDescription);
+ }
+ if (iconEndFrameContentDescriptionId != 0) {
+ final String contentDescription =
+ context.getString(iconEndFrameContentDescriptionId);
+ mIconEndFrame.setContentDescription(contentDescription);
+ }
+
+ typedArray.recycle();
+ } else {
+ mSeekbar.setMax(DEFAULT_SEEKBAR_MAX);
+ setProgress(DEFAULT_SEEKBAR_PROGRESS);
+ }
+
+ mSeekbar.setOnSeekBarChangeListener(mSeekBarListener);
+
+ mIconStart.setOnClickListener((view) -> {
+ final int progress = mSeekbar.getProgress();
+ if (progress > 0) {
+ mSeekbar.setProgress(progress - 1);
+ setIconViewAndFrameEnabled(mIconStart, mSeekbar.getProgress() > 0);
+ }
+ });
+
+ mIconEnd.setOnClickListener((view) -> {
+ final int progress = mSeekbar.getProgress();
+ if (progress < mSeekbar.getMax()) {
+ mSeekbar.setProgress(progress + 1);
+ setIconViewAndFrameEnabled(mIconEnd, mSeekbar.getProgress() < mSeekbar.getMax());
+ }
+ });
+ }
+
+ private static void setIconViewAndFrameEnabled(View iconView, boolean enabled) {
+ iconView.setEnabled(enabled);
+ final ViewGroup iconFrame = (ViewGroup) iconView.getParent();
+ iconFrame.setEnabled(enabled);
+ }
+
+ /**
+ * Sets a onSeekbarChangeListener to the seekbar in the layout.
+ * We update the Start Icon and End Icon if needed when the seekbar progress is changed.
+ */
+ public void setOnSeekBarChangeListener(
+ @Nullable SeekBar.OnSeekBarChangeListener onSeekBarChangeListener) {
+ mSeekBarListener.setOnSeekBarChangeListener(onSeekBarChangeListener);
+ }
+
+ /**
+ * Start and End icons might need to be updated when there is a change in seekbar progress.
+ * Icon Start will need to be enabled when the seekbar progress is larger than 0.
+ * Icon End will need to be enabled when the seekbar progress is less than Max.
+ */
+ private void updateIconViewIfNeeded(int progress) {
+ setIconViewAndFrameEnabled(mIconStart, progress > 0);
+ setIconViewAndFrameEnabled(mIconEnd, progress < mSeekbar.getMax());
+ }
+
+ /**
+ * Sets max to the seekbar in the layout.
+ */
+ public void setMax(int max) {
+ mSeekbar.setMax(max);
+ }
+
+ /**
+ * Sets progress to the seekbar in the layout.
+ * If the progress is smaller than or equals to 0, the IconStart will be disabled. If the
+ * progress is larger than or equals to Max, the IconEnd will be disabled. The seekbar progress
+ * will be constrained in {@link SeekBar}.
+ */
+ public void setProgress(int progress) {
+ mSeekbar.setProgress(progress);
+ updateIconViewIfNeeded(progress);
+ }
+
+ private class SeekBarChangeListener implements SeekBar.OnSeekBarChangeListener {
+ private SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = null;
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mOnSeekBarChangeListener != null) {
+ mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
+ }
+ updateIconViewIfNeeded(progress);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (mOnSeekBarChangeListener != null) {
+ mOnSeekBarChangeListener.onStartTrackingTouch(seekBar);
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (mOnSeekBarChangeListener != null) {
+ mOnSeekBarChangeListener.onStopTrackingTouch(seekBar);
+ }
+ }
+
+ void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener listener) {
+ mOnSeekBarChangeListener = listener;
+ }
+ }
+}
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/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 6af8e73..d949d11 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -44,12 +44,15 @@
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.controls.ui.ControlsUiControllerImpl
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.DeviceControlsTile
import dagger.Binds
import dagger.BindsOptionalOf
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
/**
* Module for injecting classes in `com.android.systemui.controls`-
@@ -149,4 +152,9 @@
@IntoMap
@ClassKey(ControlsActivity::class)
abstract fun provideControlsActivity(activity: ControlsActivity): Activity
+
+ @Binds
+ @IntoMap
+ @StringKey(DeviceControlsTile.TILE_SPEC)
+ abstract fun bindDeviceControlsTile(controlsTile: DeviceControlsTile): QSTileImpl<*>
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index fd690df..03a1dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -27,6 +27,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardViewController;
+import com.android.systemui.battery.BatterySaverModule;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
@@ -40,6 +41,7 @@
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
+import com.android.systemui.rotationlock.RotationLockModule;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeController;
@@ -92,11 +94,13 @@
*/
@Module(includes = {
AospPolicyModule.class,
+ BatterySaverModule.class,
GestureModule.class,
MediaModule.class,
PowerModule.class,
QSModule.class,
ReferenceScreenshotModule.class,
+ RotationLockModule.class,
StartCentralSurfacesModule.class,
VolumeModule.class
})
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 68f4dbe..625a028 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -35,6 +35,8 @@
import com.android.systemui.unfold.FoldStateLoggingProvider;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.unfold.UnfoldLatencyTracker;
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
+import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.back.BackAnimation;
@@ -137,6 +139,10 @@
getUnfoldLatencyTracker().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
+ getUnfoldTransitionProgressProvider().ifPresent((progressProvider) ->
+ getUnfoldTransitionProgressForwarder().ifPresent((forwarder) ->
+ progressProvider.addCallback(forwarder)
+ ));
}
/**
@@ -164,6 +170,18 @@
UnfoldLatencyTracker getUnfoldLatencyTracker();
/**
+ * Creates a UnfoldTransitionProgressProvider.
+ */
+ @SysUISingleton
+ Optional<UnfoldTransitionProgressProvider> getUnfoldTransitionProgressProvider();
+
+ /**
+ * Creates a UnfoldTransitionProgressForwarder.
+ */
+ @SysUISingleton
+ Optional<UnfoldTransitionProgressForwarder> getUnfoldTransitionProgressForwarder();
+
+ /**
* Creates a FoldStateLoggingProvider.
*/
@SysUISingleton
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/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index cb7c765..bddd8a7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -55,7 +55,6 @@
import com.android.systemui.toast.ToastUI
import com.android.systemui.usb.StorageNotification
import com.android.systemui.util.NotificationChannels
-import com.android.systemui.util.leak.GarbageMonitor
import com.android.systemui.volume.VolumeUI
import com.android.systemui.wmshell.WMShell
import dagger.Binds
@@ -107,12 +106,6 @@
@ClassKey(FsiChromeViewBinder::class)
abstract fun bindFsiChromeWindowBinder(sysui: FsiChromeViewBinder): CoreStartable
- /** Inject into GarbageMonitor.Service. */
- @Binds
- @IntoMap
- @ClassKey(GarbageMonitor::class)
- abstract fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable
-
/** Inject into GlobalActionsComponent. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 6274a26..60fccef 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -28,6 +28,7 @@
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
+import com.android.systemui.accessibility.AccessibilityModule;
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
@@ -57,10 +58,12 @@
import com.android.systemui.plugins.BcSmartspaceConfigPlugin;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.privacy.PrivacyModule;
+import com.android.systemui.qrcodescanner.dagger.QRCodeScannerModule;
import com.android.systemui.qs.FgsManagerController;
import com.android.systemui.qs.FgsManagerControllerImpl;
import com.android.systemui.qs.footer.dagger.FooterActionsModule;
import com.android.systemui.recents.Recents;
+import com.android.systemui.screenrecord.ScreenRecordModule;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.security.data.repository.SecurityRepositoryModule;
import com.android.systemui.settings.DisplayTracker;
@@ -70,6 +73,7 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.connectivity.ConnectivityModule;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
@@ -85,6 +89,7 @@
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.PolicyModule;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
@@ -97,6 +102,7 @@
import com.android.systemui.util.concurrency.SysUIConcurrencyModule;
import com.android.systemui.util.dagger.UtilModule;
import com.android.systemui.util.kotlin.CoroutinesModule;
+import com.android.systemui.util.leak.GarbageMonitorModule;
import com.android.systemui.util.sensors.SensorModule;
import com.android.systemui.util.settings.SettingsUtilModule;
import com.android.systemui.util.time.SystemClock;
@@ -126,6 +132,7 @@
* may not appreciate that.
*/
@Module(includes = {
+ AccessibilityModule.class,
AppOpsModule.class,
AssistModule.class,
BiometricsModule.class,
@@ -133,24 +140,30 @@
ClipboardOverlayModule.class,
ClockInfoModule.class,
ClockRegistryModule.class,
+ ConnectivityModule.class,
CoroutinesModule.class,
DreamModule.class,
ControlsModule.class,
DemoModeModule.class,
FalsingModule.class,
FlagsModule.class,
+ SystemPropertiesFlagsModule.class,
FooterActionsModule.class,
+ GarbageMonitorModule.class,
LogModule.class,
MediaProjectionModule.class,
MotionToolModule.class,
PeopleHubModule.class,
PeopleModule.class,
PluginModule.class,
+ PolicyModule.class,
PrivacyModule.class,
+ QRCodeScannerModule.class,
ScreenshotModule.class,
SensorModule.class,
MultiUserUtilsModule.class,
SecurityRepositoryModule.class,
+ ScreenRecordModule.class,
SettingsUtilModule.class,
SmartRepliesInflationModule.class,
SmartspaceModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
index 102f208..055cd52 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java
@@ -16,19 +16,23 @@
package com.android.systemui.dreams;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
+
import android.util.Log;
import com.android.systemui.CoreStartable;
import com.android.systemui.dreams.callbacks.DreamStatusBarStateCallback;
import com.android.systemui.dreams.conditions.DreamCondition;
import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
import javax.inject.Inject;
+import javax.inject.Named;
/**
* A {@link CoreStartable} to retain a monitor for tracking dreaming.
*/
-public class DreamMonitor implements CoreStartable {
+public class DreamMonitor extends ConditionalCoreStartable {
private static final String TAG = "DreamMonitor";
// We retain a reference to the monitor so it is not garbage-collected.
@@ -39,14 +43,17 @@
@Inject
public DreamMonitor(Monitor monitor, DreamCondition dreamCondition,
+ @Named(DREAM_PRETEXT_MONITOR) Monitor pretextMonitor,
DreamStatusBarStateCallback callback) {
+ super(pretextMonitor);
mConditionMonitor = monitor;
mDreamCondition = dreamCondition;
mCallback = callback;
}
+
@Override
- public void start() {
+ protected void onStart() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "started");
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
index 87c5f51..a2dcdf5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
@@ -17,6 +17,7 @@
package com.android.systemui.dreams;
import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_SERVICE_COMPONENT;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -33,8 +34,9 @@
import android.service.dreams.IDreamManager;
import android.util.Log;
-import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
import javax.inject.Inject;
import javax.inject.Named;
@@ -43,7 +45,7 @@
* {@link DreamOverlayRegistrant} is responsible for telling system server that SystemUI should be
* the designated dream overlay component.
*/
-public class DreamOverlayRegistrant implements CoreStartable {
+public class DreamOverlayRegistrant extends ConditionalCoreStartable {
private static final String TAG = "DreamOverlayRegistrant";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final IDreamManager mDreamManager;
@@ -102,7 +104,9 @@
@Inject
public DreamOverlayRegistrant(Context context, @Main Resources resources,
- @Named(DREAM_OVERLAY_SERVICE_COMPONENT) ComponentName dreamOverlayServiceComponent) {
+ @Named(DREAM_OVERLAY_SERVICE_COMPONENT) ComponentName dreamOverlayServiceComponent,
+ @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) {
+ super(monitor);
mContext = context;
mResources = resources;
mDreamManager = IDreamManager.Stub.asInterface(
@@ -111,7 +115,7 @@
}
@Override
- public void start() {
+ protected void onStart() {
final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
filter.addDataSchemeSpecificPart(mOverlayServiceComponent.getPackageName(),
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index dd01be0..5aebc32 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -243,6 +243,8 @@
*/
private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
mWindow = new PhoneWindow(mContext);
+ // Default to SystemUI name for TalkBack.
+ mWindow.setTitle("");
mWindow.setAttributes(layoutParams);
mWindow.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index 90c440c..7394e236 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -257,7 +257,8 @@
mConnectivityManager.getActiveNetwork());
final boolean available = capabilities != null
&& capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
- showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available);
+ showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available,
+ R.string.wifi_unavailable_dream_overlay_content_description);
}
private void updateAlarmStatusIcon() {
@@ -294,13 +295,16 @@
@DreamOverlayStatusBarView.StatusIconType int iconType = Resources.ID_NULL;
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED,
- !micBlocked && cameraBlocked);
+ !micBlocked && cameraBlocked,
+ R.string.camera_blocked_dream_overlay_content_description);
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED,
- micBlocked && !cameraBlocked);
+ micBlocked && !cameraBlocked,
+ R.string.microphone_blocked_dream_overlay_content_description);
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED,
- micBlocked && cameraBlocked);
+ micBlocked && cameraBlocked,
+ R.string.camera_and_microphone_blocked_dream_overlay_content_description);
}
private String buildNotificationsContentDescription(int notificationCount) {
@@ -313,11 +317,13 @@
private void updatePriorityModeStatusIcon() {
showIcon(
DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON,
- mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF);
+ mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF,
+ R.string.priority_mode_dream_overlay_content_description);
}
- private void showIcon(@DreamOverlayStatusBarView.StatusIconType int iconType, boolean show) {
- showIcon(iconType, show, null);
+ private void showIcon(@DreamOverlayStatusBarView.StatusIconType int iconType, boolean show,
+ int contentDescriptionResId) {
+ showIcon(iconType, show, mResources.getString(contentDescriptionResId));
}
private void showIcon(
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
index ee2f1af..244212b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationTypesUpdater.java
@@ -16,27 +16,31 @@
package com.android.systemui.dreams.complication;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
+
import android.database.ContentObserver;
import android.os.UserHandle;
import android.provider.Settings;
import com.android.settingslib.dream.DreamBackend;
-import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
import com.android.systemui.util.settings.SecureSettings;
import java.util.concurrent.Executor;
import javax.inject.Inject;
+import javax.inject.Named;
/**
* {@link ComplicationTypesUpdater} observes the state of available complication types set by the
* user, and pushes updates to {@link DreamOverlayStateController}.
*/
@SysUISingleton
-public class ComplicationTypesUpdater implements CoreStartable {
+public class ComplicationTypesUpdater extends ConditionalCoreStartable {
private final DreamBackend mDreamBackend;
private final Executor mExecutor;
private final SecureSettings mSecureSettings;
@@ -48,7 +52,9 @@
DreamBackend dreamBackend,
@Main Executor executor,
SecureSettings secureSettings,
- DreamOverlayStateController dreamOverlayStateController) {
+ DreamOverlayStateController dreamOverlayStateController,
+ @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) {
+ super(monitor);
mDreamBackend = dreamBackend;
mExecutor = executor;
mSecureSettings = secureSettings;
@@ -56,7 +62,7 @@
}
@Override
- public void start() {
+ public void onStart() {
final ContentObserver settingsObserver = new ContentObserver(null /*handler*/) {
@Override
public void onChange(boolean selfChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
index 77e1fc9..bb1e6e2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamClockTimeComplication.java
@@ -18,11 +18,14 @@
import static com.android.systemui.dreams.complication.dagger.DreamClockTimeComplicationModule.DREAM_CLOCK_TIME_COMPLICATION_VIEW;
import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_CLOCK_TIME_COMPLICATION_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
import android.view.View;
import com.android.systemui.CoreStartable;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
import javax.inject.Inject;
import javax.inject.Named;
@@ -60,7 +63,7 @@
* {@link CoreStartable} responsible for registering {@link DreamClockTimeComplication} with
* SystemUI.
*/
- public static class Registrant implements CoreStartable {
+ public static class Registrant extends ConditionalCoreStartable {
private final DreamOverlayStateController mDreamOverlayStateController;
private final DreamClockTimeComplication mComplication;
@@ -70,13 +73,15 @@
@Inject
public Registrant(
DreamOverlayStateController dreamOverlayStateController,
- DreamClockTimeComplication dreamClockTimeComplication) {
+ DreamClockTimeComplication dreamClockTimeComplication,
+ @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) {
+ super(monitor);
mDreamOverlayStateController = dreamOverlayStateController;
mComplication = dreamClockTimeComplication;
}
@Override
- public void start() {
+ public void onStart() {
mDreamOverlayStateController.addComplication(mComplication);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
index 1065b94..7f395d8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -21,6 +21,7 @@
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE;
import static com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW;
import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
import android.content.Context;
import android.content.Intent;
@@ -42,7 +43,9 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.shared.condition.Monitor;
import com.android.systemui.util.ViewController;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
import java.util.List;
@@ -75,7 +78,7 @@
/**
* {@link CoreStartable} for registering the complication with SystemUI on startup.
*/
- public static class Registrant implements CoreStartable {
+ public static class Registrant extends ConditionalCoreStartable {
private final DreamHomeControlsComplication mComplication;
private final DreamOverlayStateController mDreamOverlayStateController;
private final ControlsComponent mControlsComponent;
@@ -105,14 +108,16 @@
@Inject
public Registrant(DreamHomeControlsComplication complication,
DreamOverlayStateController dreamOverlayStateController,
- ControlsComponent controlsComponent) {
+ ControlsComponent controlsComponent,
+ @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) {
+ super(monitor);
mComplication = complication;
mControlsComponent = controlsComponent;
mDreamOverlayStateController = dreamOverlayStateController;
}
@Override
- public void start() {
+ public void onStart() {
mControlsComponent.getControlsListingController().ifPresent(
c -> c.addCallback(mControlsCallback));
mDreamOverlayStateController.addCallback(mOverlayStateCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
index c3aaf0c..e39073b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java
@@ -17,6 +17,7 @@
package com.android.systemui.dreams.complication;
import static com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule.DREAM_SMARTSPACE_LAYOUT_PARAMS;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_PRETEXT_MONITOR;
import android.content.Context;
import android.os.Parcelable;
@@ -28,6 +29,8 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.condition.ConditionalCoreStartable;
import java.util.List;
@@ -61,7 +64,7 @@
* {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with
* SystemUI.
*/
- public static class Registrant implements CoreStartable {
+ public static class Registrant extends ConditionalCoreStartable {
private final DreamSmartspaceController mSmartSpaceController;
private final DreamOverlayStateController mDreamOverlayStateController;
private final SmartSpaceComplication mComplication;
@@ -81,14 +84,16 @@
public Registrant(
DreamOverlayStateController dreamOverlayStateController,
SmartSpaceComplication smartSpaceComplication,
- DreamSmartspaceController smartSpaceController) {
+ DreamSmartspaceController smartSpaceController,
+ @Named(DREAM_PRETEXT_MONITOR) Monitor monitor) {
+ super(monitor);
mDreamOverlayStateController = dreamOverlayStateController;
mComplication = smartSpaceComplication;
mSmartSpaceController = smartSpaceController;
}
@Override
- public void start() {
+ public void onStart() {
mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
@Override
public void onStateChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index 0ab8c8e..88c02b8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -29,13 +29,20 @@
import com.android.systemui.dreams.DreamOverlayService;
import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule;
+import com.android.systemui.process.condition.UserProcessCondition;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Executor;
import javax.inject.Named;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.IntoSet;
/**
* Dagger Module providing Dream-related functionality.
@@ -54,6 +61,8 @@
String DREAM_OVERLAY_ENABLED = "dream_overlay_enabled";
String DREAM_SUPPORTED = "dream_supported";
+ String DREAM_PRETEXT_CONDITIONS = "dream_pretext_conditions";
+ String DREAM_PRETEXT_MONITOR = "dream_prtext_monitor";
/**
* Provides the dream component
@@ -112,4 +121,19 @@
static boolean providesDreamSupported(@Main Resources resources) {
return resources.getBoolean(com.android.internal.R.bool.config_dreamsSupported);
}
+
+ /** */
+ @Binds
+ @IntoSet
+ @Named(DREAM_PRETEXT_CONDITIONS)
+ Condition bindsUserProcessCondition(UserProcessCondition condition);
+
+ /** */
+ @Provides
+ @Named(DREAM_PRETEXT_MONITOR)
+ static Monitor providesDockerPretextMonitor(
+ @Main Executor executor,
+ @Named(DREAM_PRETEXT_CONDITIONS) Set<Condition> pretextConditions) {
+ return new Monitor(executor, pretextConditions);
+ }
}
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 0078928..343fcb0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -100,7 +100,7 @@
// TODO(b/260335638): Tracking Bug
@JvmField
val NOTIFICATION_INLINE_REPLY_ANIMATION =
- unreleasedFlag(174148361, "notification_inline_reply_animation", teamfood = true)
+ releasedFlag(174148361, "notification_inline_reply_animation")
val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
releasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
@@ -515,6 +515,11 @@
@JvmField
val SCREENSHOT_DETECTION = unreleasedFlag(1303, "screenshot_detection", teamfood = true)
+ // TODO(b/268484562): Tracking bug
+ @JvmField
+ val SCREENSHOT_METADATA_REFACTOR =
+ unreleasedFlag(1305, "screenshot_metadata_refactor", teamfood = true)
+
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
@@ -551,7 +556,8 @@
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
// TODO(b/267162944): Tracking bug
- @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = unreleasedFlag(1702, "clipboard_data_model")
+ @JvmField
+ val CLIPBOARD_MINIMIZED_LAYOUT = unreleasedFlag(1702, "clipboard_data_model", teamfood = true)
// 1800 - shade container
@JvmField
@@ -573,6 +579,12 @@
val CONTROLS_MANAGEMENT_NEW_FLOWS =
unreleasedFlag(2002, "controls_management_new_flows", teamfood = true)
+ // Enables removing app from Home control panel as a part of a new flow
+ // TODO(b/269132640): Tracking Bug
+ @JvmField
+ val APP_PANELS_REMOVE_APPS_ALLOWED =
+ unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = false)
+
// 2100 - Falsing Manager
@JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
@@ -608,7 +620,7 @@
// TODO(b/20911786): Tracking Bug
@JvmField
val OUTPUT_SWITCHER_SHOW_API_ENABLED =
- unreleasedFlag(2503, "output_switcher_show_api_enabled", teamfood = true)
+ releasedFlag(2503, "output_switcher_show_api_enabled", teamfood = true)
// 2700 - unfold transitions
// TODO(b/265764985): Tracking Bug
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/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index 5a9f775..c9f645d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.app.StatusBarManager
+import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.pm.PackageManager
import com.android.systemui.R
@@ -27,10 +28,14 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.withContext
@SysUISingleton
class CameraQuickAffordanceConfig
@@ -39,6 +44,9 @@
@Application private val context: Context,
private val packageManager: PackageManager,
private val cameraGestureHelper: Lazy<CameraGestureHelper>,
+ private val userTracker: UserTracker,
+ private val devicePolicyManager: DevicePolicyManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : KeyguardQuickAffordanceConfig {
override val key: String
@@ -79,7 +87,12 @@
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
- private fun isLaunchable(): Boolean {
- return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
+ private suspend fun isLaunchable(): Boolean {
+ return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) &&
+ withContext(backgroundDispatcher) {
+ !devicePolicyManager.getCameraDisabled(null, userTracker.userId) &&
+ devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId) and
+ DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA == 0
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
index d9ec3b1..6f821a2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.app.StatusBarManager
+import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
import com.android.systemui.ActivityIntentHelper
@@ -29,10 +30,13 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.withContext
@SysUISingleton
class VideoCameraQuickAffordanceConfig
@@ -42,6 +46,8 @@
private val cameraIntents: CameraIntentsWrapper,
private val activityIntentHelper: ActivityIntentHelper,
private val userTracker: UserTracker,
+ private val devicePolicyManager: DevicePolicyManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) : KeyguardQuickAffordanceConfig {
private val intent: Intent by lazy {
@@ -63,8 +69,8 @@
get() = R.drawable.ic_videocam
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
- get() =
- flowOf(
+ get() = flow {
+ emit(
if (isLaunchable()) {
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon =
@@ -77,6 +83,7 @@
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
}
)
+ }
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
return if (isLaunchable()) {
@@ -95,11 +102,14 @@
)
}
- private fun isLaunchable(): Boolean {
+ private suspend fun isLaunchable(): Boolean {
return activityIntentHelper.getTargetActivityInfo(
intent,
userTracker.userId,
true,
- ) != null
+ ) != null &&
+ withContext(backgroundDispatcher) {
+ !devicePolicyManager.getCameraDisabled(null, userTracker.userId)
+ }
}
}
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/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/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 9f09d53..b0c0dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -22,7 +22,6 @@
import android.window.OnBackAnimationCallback
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.internal.policy.SystemBarUtils
import com.android.keyguard.KeyguardHostViewController
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -98,14 +97,14 @@
viewModel.setBouncerViewDelegate(delegate)
launch {
viewModel.show.collect {
+ // Reset Security Container entirely.
+ hostViewController.reinflateViewFlipper()
hostViewController.showPromptReason(it.promptReason)
it.errorMessage?.let { errorMessage ->
hostViewController.showErrorMessage(errorMessage)
}
hostViewController.showPrimarySecurityScreen()
- hostViewController.appear(
- SystemBarUtils.getStatusBarHeight(view.context)
- )
+ hostViewController.appear()
hostViewController.onResume()
}
}
@@ -160,15 +159,6 @@
}
launch {
- viewModel.isBouncerVisible
- .filter { !it }
- .collect {
- // Remove existing input for security reasons.
- hostViewController.resetSecurityContainer()
- }
- }
-
- launch {
viewModel.keyguardPosition.collect { position ->
hostViewController.updateKeyguardPosition(position)
}
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/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index db77777..f5558a2 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
@@ -177,6 +178,7 @@
private val mediaFlags: MediaFlags,
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -241,6 +243,7 @@
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : this(
context,
backgroundExecutor,
@@ -264,6 +267,7 @@
mediaFlags,
logger,
smartspaceManager,
+ keyguardUpdateMonitor,
)
private val appChangeReceiver =
@@ -1336,10 +1340,12 @@
Assert.isMainThread()
val removed = mediaEntries.remove(key) ?: return
- if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) {
- convertToResumePlayer(removed)
+ if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) {
+ logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
+ } else if (useMediaResumption && removed.resumeAction != null && removed.isLocalSession()) {
+ convertToResumePlayer(key, removed)
} else if (mediaFlags.isRetainingPlayersEnabled()) {
- handlePossibleRemoval(removed, notificationRemoved = true)
+ handlePossibleRemoval(key, removed, notificationRemoved = true)
} else {
notifyMediaDataRemoved(key)
logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
@@ -1353,7 +1359,7 @@
val entry = mediaEntries.remove(key) ?: return
// Clear token since the session is no longer valid
val updated = entry.copy(token = null)
- handlePossibleRemoval(updated)
+ handlePossibleRemoval(key, updated)
}
/**
@@ -1362,8 +1368,11 @@
* if it was removed before becoming inactive. (Assumes that [removed] was removed from
* [mediaEntries] before this function was called)
*/
- private fun handlePossibleRemoval(removed: MediaData, notificationRemoved: Boolean = false) {
- val key = removed.notificationKey!!
+ private fun handlePossibleRemoval(
+ key: String,
+ removed: MediaData,
+ notificationRemoved: Boolean = false
+ ) {
val hasSession = removed.token != null
if (hasSession && removed.semanticActions != null) {
// The app was using session actions, and the session is still valid: keep player
@@ -1389,13 +1398,12 @@
"($hasSession) gone for inactive player $key"
)
}
- convertToResumePlayer(removed)
+ convertToResumePlayer(key, removed)
}
}
/** Set the given [MediaData] as a resume state player and notify listeners */
- private fun convertToResumePlayer(data: MediaData) {
- val key = data.notificationKey!!
+ private fun convertToResumePlayer(key: String, data: MediaData) {
if (DEBUG) Log.d(TAG, "Converting $key to resume")
// Move to resume key (aka package name) if that key doesn't already exist.
val resumeAction = data.resumeAction?.let { getResumeMediaAction(it) }
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..68d2c5c 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
@@ -151,13 +164,13 @@
mediaCarouselScrollHandler.scrollToStart()
}
}
- private var currentlyExpanded = true
+
+ @VisibleForTesting
+ var currentlyExpanded = true
set(value) {
if (field != value) {
field = value
- for (player in MediaPlayerData.players()) {
- player.setListening(field)
- }
+ updateSeekbarListening(mediaCarouselScrollHandler.visibleToUser)
}
}
@@ -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.
@@ -235,6 +259,7 @@
executor,
this::onSwipeToDismiss,
this::updatePageIndicatorLocation,
+ this::updateSeekbarListening,
this::closeGuts,
falsingCollector,
falsingManager,
@@ -487,6 +512,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 +548,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
@@ -542,6 +591,17 @@
?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
}
}
+ // Check postcondition: mediaContent should have the same number of children as there
+ // are
+ // elements in mediaPlayers.
+ if (MediaPlayerData.players().size != mediaContent.childCount) {
+ Log.e(
+ TAG,
+ "Size of players list and number of views in carousel are out of sync. " +
+ "Players size is ${MediaPlayerData.players().size}. " +
+ "View count is ${mediaContent.childCount}."
+ )
+ }
}
// Returns true if new player is added
@@ -570,7 +630,9 @@
)
newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
newPlayer.bindPlayer(data, key)
- newPlayer.setListening(currentlyExpanded)
+ newPlayer.setListening(
+ mediaCarouselScrollHandler.visibleToUser && currentlyExpanded
+ )
MediaPlayerData.addMediaPlayer(
key,
data,
@@ -617,17 +679,6 @@
updatePageIndicator()
mediaCarouselScrollHandler.onPlayersChanged()
mediaFrame.requiresRemeasuring = true
- // Check postcondition: mediaContent should have the same number of children as there
- // are
- // elements in mediaPlayers.
- if (MediaPlayerData.players().size != mediaContent.childCount) {
- Log.e(
- TAG,
- "Size of players list and number of views in carousel are out of sync. " +
- "Players size is ${MediaPlayerData.players().size}. " +
- "View count is ${mediaContent.childCount}."
- )
- }
return existingPlayer == null
}
@@ -866,6 +917,13 @@
.toFloat()
}
+ /** Update listening to seekbar. */
+ private fun updateSeekbarListening(visibleToUser: Boolean) {
+ for (player in MediaPlayerData.players()) {
+ player.setListening(visibleToUser && currentlyExpanded)
+ }
+ }
+
/** Update the dimension of this carousel. */
private fun updateCarouselDimensions() {
var width = 0
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 36b2eda..1ace316 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -57,6 +57,7 @@
private val mainExecutor: DelayableExecutor,
val dismissCallback: () -> Unit,
private var translationChangedListener: () -> Unit,
+ private var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit,
private val closeGuts: (immediate: Boolean) -> Unit,
private val falsingCollector: FalsingCollector,
private val falsingManager: FalsingManager,
@@ -177,6 +178,12 @@
/** Whether the media card is visible to user if any */
var visibleToUser: Boolean = false
+ set(value) {
+ if (field != value) {
+ field = value
+ seekBarUpdateListener.invoke(field)
+ }
+ }
/** Whether the quick setting is expanded or not */
var qsExpanded: Boolean = false
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..7677062 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
@@ -346,6 +346,11 @@
mSeekBarViewModel.setListening(listening);
}
+ @VisibleForTesting
+ public boolean getListening() {
+ return mSeekBarViewModel.getListening();
+ }
+
/** Sets whether the user is touching the seek bar to change the track position. */
private void setIsScrubbing(boolean isScrubbing) {
if (mMediaData == null || mMediaData.getSemanticActions() == null) {
@@ -1136,8 +1141,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/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 8000cd8..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,
@@ -203,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)
}
}
@@ -252,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)
@@ -270,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 997370b..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
@@ -119,13 +119,19 @@
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
- centerFillFadeParams.fadeOutStart = RippleShader.DEFAULT_CENTER_FILL_FADE_OUT_START
- centerFillFadeParams.fadeOutEnd = RippleShader.DEFAULT_CENTER_FILL_FADE_OUT_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
}
}
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 1678c6e..3088d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -46,10 +46,10 @@
import dagger.Subcomponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
import javax.inject.Qualifier
import javax.inject.Scope
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class MediaProjectionAppSelector
@@ -110,6 +110,12 @@
@Provides
@MediaProjectionAppSelector
@MediaProjectionAppSelectorScope
+ fun provideCallerPackageName(activity: MediaProjectionAppSelectorActivity): String? =
+ activity.callingPackage
+
+ @Provides
+ @MediaProjectionAppSelector
+ @MediaProjectionAppSelectorScope
fun bindConfigurationController(
activity: MediaProjectionAppSelectorActivity
): ConfigurationController = ConfigurationControllerImpl(activity)
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/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
new file mode 100644
index 0000000..245cf89
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import javax.inject.Inject;
+
+/**
+ * A simple wrapper that provides access to process-related details. This facilitates testing by
+ * providing a mockable target around these details.
+ */
+public class ProcessWrapper {
+ @Inject
+ public 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/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
new file mode 100644
index 0000000..62c99da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.qrcodescanner.dagger
+
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.QRCodeScannerTile
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface QRCodeScannerModule {
+
+ /**
+ */
+ @Binds
+ @IntoMap
+ @StringKey(QRCodeScannerTile.TILE_SPEC)
+ fun bindQRCodeScannerTile(qrCodeScannerTile: QRCodeScannerTile): QSTileImpl<*>
+}
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..27ae171 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;
@@ -29,6 +30,7 @@
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.qs.external.QSExternalModule;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.policy.CastController;
@@ -39,11 +41,14 @@
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.util.settings.SecureSettings;
+import java.util.Map;
+
import javax.inject.Named;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.Multibinds;
/**
* Module for QS dependencies
@@ -52,7 +57,13 @@
includes = {MediaModule.class, QSExternalModule.class, QSFlagsModule.class})
public interface QSModule {
+ /** A map of internal QS tiles. Ensures that this can be injected even if
+ * it is empty */
+ @Multibinds
+ Map<String, QSTileImpl<?>> tileMap();
+
@Provides
+ @SysUISingleton
static AutoTileManager provideAutoTileManager(
Context context,
AutoAddTracker.Builder autoAddTrackerBuilder,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 24a4f60b..6b23f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -27,74 +27,32 @@
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.tiles.AirplaneModeTile;
-import com.android.systemui.qs.tiles.AlarmTile;
-import com.android.systemui.qs.tiles.BatterySaverTile;
-import com.android.systemui.qs.tiles.BluetoothTile;
-import com.android.systemui.qs.tiles.CameraToggleTile;
-import com.android.systemui.qs.tiles.CastTile;
-import com.android.systemui.qs.tiles.ColorCorrectionTile;
-import com.android.systemui.qs.tiles.ColorInversionTile;
-import com.android.systemui.qs.tiles.DataSaverTile;
-import com.android.systemui.qs.tiles.DeviceControlsTile;
-import com.android.systemui.qs.tiles.DndTile;
-import com.android.systemui.qs.tiles.DreamTile;
-import com.android.systemui.qs.tiles.FlashlightTile;
-import com.android.systemui.qs.tiles.HotspotTile;
-import com.android.systemui.qs.tiles.InternetTile;
-import com.android.systemui.qs.tiles.LocationTile;
-import com.android.systemui.qs.tiles.MicrophoneToggleTile;
-import com.android.systemui.qs.tiles.NfcTile;
-import com.android.systemui.qs.tiles.NightDisplayTile;
-import com.android.systemui.qs.tiles.OneHandedModeTile;
-import com.android.systemui.qs.tiles.QRCodeScannerTile;
-import com.android.systemui.qs.tiles.QuickAccessWalletTile;
-import com.android.systemui.qs.tiles.ReduceBrightColorsTile;
-import com.android.systemui.qs.tiles.RotationLockTile;
-import com.android.systemui.qs.tiles.ScreenRecordTile;
-import com.android.systemui.qs.tiles.UiModeNightTile;
-import com.android.systemui.qs.tiles.WorkModeTile;
import com.android.systemui.util.leak.GarbageMonitor;
+import java.util.Map;
+
import javax.inject.Inject;
import javax.inject.Provider;
import dagger.Lazy;
+/**
+ * A factory that creates Quick Settings tiles based on a tileSpec
+ *
+ * To create a new tile within SystemUI, the tile class should extend {@link QSTileImpl} and have
+ * a public static final TILE_SPEC field which serves as a unique key for this tile. (e.g. {@link
+ * com.android.systemui.qs.tiles.DreamTile#TILE_SPEC})
+ *
+ * After, create or find an existing Module class to house the tile's binding method (e.g. {@link
+ * com.android.systemui.accessibility.AccessibilityModule}). If creating a new module, add your
+ * module to the SystemUI dagger graph by including it in an appropriate module.
+ */
@SysUISingleton
public class QSFactoryImpl implements QSFactory {
private static final String TAG = "QSFactory";
- private final Provider<InternetTile> mInternetTileProvider;
- private final Provider<BluetoothTile> mBluetoothTileProvider;
- private final Provider<DndTile> mDndTileProvider;
- private final Provider<ColorCorrectionTile> mColorCorrectionTileProvider;
- private final Provider<ColorInversionTile> mColorInversionTileProvider;
- private final Provider<AirplaneModeTile> mAirplaneModeTileProvider;
- private final Provider<WorkModeTile> mWorkModeTileProvider;
- private final Provider<RotationLockTile> mRotationLockTileProvider;
- private final Provider<FlashlightTile> mFlashlightTileProvider;
- private final Provider<LocationTile> mLocationTileProvider;
- private final Provider<CastTile> mCastTileProvider;
- private final Provider<HotspotTile> mHotspotTileProvider;
- private final Provider<BatterySaverTile> mBatterySaverTileProvider;
- private final Provider<DataSaverTile> mDataSaverTileProvider;
- private final Provider<NightDisplayTile> mNightDisplayTileProvider;
- private final Provider<NfcTile> mNfcTileProvider;
- private final Provider<GarbageMonitor.MemoryTile> mMemoryTileProvider;
- private final Provider<UiModeNightTile> mUiModeNightTileProvider;
- private final Provider<ScreenRecordTile> mScreenRecordTileProvider;
- private final Provider<ReduceBrightColorsTile> mReduceBrightColorsTileProvider;
- private final Provider<CameraToggleTile> mCameraToggleTileProvider;
- private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
- private final Provider<DeviceControlsTile> mDeviceControlsTileProvider;
- private final Provider<AlarmTile> mAlarmTileProvider;
- private final Provider<QuickAccessWalletTile> mQuickAccessWalletTileProvider;
- private final Provider<QRCodeScannerTile> mQRCodeScannerTileProvider;
- private final Provider<OneHandedModeTile> mOneHandedModeTileProvider;
- private final Provider<DreamTile> mDreamTileProvider;
-
+ protected final Map<String, Provider<QSTileImpl<?>>> mTileMap;
private final Lazy<QSHost> mQsHostLazy;
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -102,65 +60,10 @@
public QSFactoryImpl(
Lazy<QSHost> qsHostLazy,
Provider<CustomTile.Builder> customTileBuilderProvider,
- Provider<InternetTile> internetTileProvider,
- Provider<BluetoothTile> bluetoothTileProvider,
- Provider<DndTile> dndTileProvider,
- Provider<ColorInversionTile> colorInversionTileProvider,
- Provider<AirplaneModeTile> airplaneModeTileProvider,
- Provider<WorkModeTile> workModeTileProvider,
- Provider<RotationLockTile> rotationLockTileProvider,
- Provider<FlashlightTile> flashlightTileProvider,
- Provider<LocationTile> locationTileProvider,
- Provider<CastTile> castTileProvider,
- Provider<HotspotTile> hotspotTileProvider,
- Provider<BatterySaverTile> batterySaverTileProvider,
- Provider<DataSaverTile> dataSaverTileProvider,
- Provider<NightDisplayTile> nightDisplayTileProvider,
- Provider<NfcTile> nfcTileProvider,
- Provider<GarbageMonitor.MemoryTile> memoryTileProvider,
- Provider<UiModeNightTile> uiModeNightTileProvider,
- Provider<ScreenRecordTile> screenRecordTileProvider,
- Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider,
- Provider<CameraToggleTile> cameraToggleTileProvider,
- Provider<MicrophoneToggleTile> microphoneToggleTileProvider,
- Provider<DeviceControlsTile> deviceControlsTileProvider,
- Provider<AlarmTile> alarmTileProvider,
- Provider<QuickAccessWalletTile> quickAccessWalletTileProvider,
- Provider<QRCodeScannerTile> qrCodeScannerTileProvider,
- Provider<OneHandedModeTile> oneHandedModeTileProvider,
- Provider<ColorCorrectionTile> colorCorrectionTileProvider,
- Provider<DreamTile> dreamTileProvider) {
+ Map<String, Provider<QSTileImpl<?>>> tileMap) {
mQsHostLazy = qsHostLazy;
mCustomTileBuilderProvider = customTileBuilderProvider;
-
- mInternetTileProvider = internetTileProvider;
- mBluetoothTileProvider = bluetoothTileProvider;
- mDndTileProvider = dndTileProvider;
- mColorInversionTileProvider = colorInversionTileProvider;
- mAirplaneModeTileProvider = airplaneModeTileProvider;
- mWorkModeTileProvider = workModeTileProvider;
- mRotationLockTileProvider = rotationLockTileProvider;
- mFlashlightTileProvider = flashlightTileProvider;
- mLocationTileProvider = locationTileProvider;
- mCastTileProvider = castTileProvider;
- mHotspotTileProvider = hotspotTileProvider;
- mBatterySaverTileProvider = batterySaverTileProvider;
- mDataSaverTileProvider = dataSaverTileProvider;
- mNightDisplayTileProvider = nightDisplayTileProvider;
- mNfcTileProvider = nfcTileProvider;
- mMemoryTileProvider = memoryTileProvider;
- mUiModeNightTileProvider = uiModeNightTileProvider;
- mScreenRecordTileProvider = screenRecordTileProvider;
- mReduceBrightColorsTileProvider = reduceBrightColorsTileProvider;
- mCameraToggleTileProvider = cameraToggleTileProvider;
- mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
- mDeviceControlsTileProvider = deviceControlsTileProvider;
- mAlarmTileProvider = alarmTileProvider;
- mQuickAccessWalletTileProvider = quickAccessWalletTileProvider;
- mQRCodeScannerTileProvider = qrCodeScannerTileProvider;
- mOneHandedModeTileProvider = oneHandedModeTileProvider;
- mColorCorrectionTileProvider = colorCorrectionTileProvider;
- mDreamTileProvider = dreamTileProvider;
+ mTileMap = tileMap;
}
/** Creates a tile with a type based on {@code tileSpec} */
@@ -177,61 +80,10 @@
@Nullable
protected QSTileImpl createTileInternal(String tileSpec) {
// Stock tiles.
- switch (tileSpec) {
- case "internet":
- return mInternetTileProvider.get();
- case "bt":
- return mBluetoothTileProvider.get();
- case "dnd":
- return mDndTileProvider.get();
- case "inversion":
- return mColorInversionTileProvider.get();
- case "airplane":
- return mAirplaneModeTileProvider.get();
- case "work":
- return mWorkModeTileProvider.get();
- case "rotation":
- return mRotationLockTileProvider.get();
- case "flashlight":
- return mFlashlightTileProvider.get();
- case "location":
- return mLocationTileProvider.get();
- case "cast":
- return mCastTileProvider.get();
- case "hotspot":
- return mHotspotTileProvider.get();
- case "battery":
- return mBatterySaverTileProvider.get();
- case "saver":
- return mDataSaverTileProvider.get();
- case "night":
- return mNightDisplayTileProvider.get();
- case "nfc":
- return mNfcTileProvider.get();
- case "dark":
- return mUiModeNightTileProvider.get();
- case "screenrecord":
- return mScreenRecordTileProvider.get();
- case "reduce_brightness":
- return mReduceBrightColorsTileProvider.get();
- case "cameratoggle":
- return mCameraToggleTileProvider.get();
- case "mictoggle":
- return mMicrophoneToggleTileProvider.get();
- case "controls":
- return mDeviceControlsTileProvider.get();
- case "alarm":
- return mAlarmTileProvider.get();
- case "wallet":
- return mQuickAccessWalletTileProvider.get();
- case "qr_code_scanner":
- return mQRCodeScannerTileProvider.get();
- case "onehanded":
- return mOneHandedModeTileProvider.get();
- case "color_correction":
- return mColorCorrectionTileProvider.get();
- case "dream":
- return mDreamTileProvider.get();
+ if (mTileMap.containsKey(tileSpec)
+ // We should not return a Garbage Monitory Tile if the build is not Debuggable
+ && (!tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC) || Build.IS_DEBUGGABLE)) {
+ return mTileMap.get(tileSpec).get();
}
// Custom tiles
@@ -240,13 +92,6 @@
mCustomTileBuilderProvider.get(), tileSpec, mQsHostLazy.get().getUserContext());
}
- // Debug tiles.
- if (Build.IS_DEBUGGABLE) {
- if (tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC)) {
- return mMemoryTileProvider.get();
- }
- }
-
// Broken tiles.
Log.w(TAG, "No stock tile spec: " + tileSpec);
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 2cffe89..49ba508 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -712,6 +712,10 @@
return context.getDrawable(mResId);
}
+ public int getResId() {
+ return mResId;
+ }
+
@Override
public boolean equals(Object o) {
return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 29d7fb0..de1137e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -440,12 +440,11 @@
// State handling and description
val stateDescription = StringBuilder()
- val stateText = getStateText(state)
+ val arrayResId = SubtitleArrayMapping.getSubtitleId(state.spec)
+ val stateText = state.getStateText(arrayResId, resources)
+ state.secondaryLabel = state.getSecondaryLabel(stateText)
if (!TextUtils.isEmpty(stateText)) {
stateDescription.append(stateText)
- if (TextUtils.isEmpty(state.secondaryLabel)) {
- state.secondaryLabel = stateText
- }
}
if (state.disabledByPolicy && state.state != Tile.STATE_UNAVAILABLE) {
stateDescription.append(", ")
@@ -591,16 +590,6 @@
return resources.getStringArray(arrayResId)[Tile.STATE_UNAVAILABLE]
}
- private fun getStateText(state: QSTile.State): String {
- return if (state.state == Tile.STATE_UNAVAILABLE || state is BooleanState) {
- val arrayResId = SubtitleArrayMapping.getSubtitleId(state.spec)
- val array = resources.getStringArray(arrayResId)
- array[state.state]
- } else {
- ""
- }
- }
-
/*
* The view should not be animated if it's not on screen and no part of it is visible.
*/
@@ -663,45 +652,6 @@
)
}
-@VisibleForTesting
-internal object SubtitleArrayMapping {
- private val subtitleIdsMap = mapOf<String?, Int>(
- "internet" to R.array.tile_states_internet,
- "wifi" to R.array.tile_states_wifi,
- "cell" to R.array.tile_states_cell,
- "battery" to R.array.tile_states_battery,
- "dnd" to R.array.tile_states_dnd,
- "flashlight" to R.array.tile_states_flashlight,
- "rotation" to R.array.tile_states_rotation,
- "bt" to R.array.tile_states_bt,
- "airplane" to R.array.tile_states_airplane,
- "location" to R.array.tile_states_location,
- "hotspot" to R.array.tile_states_hotspot,
- "inversion" to R.array.tile_states_inversion,
- "saver" to R.array.tile_states_saver,
- "dark" to R.array.tile_states_dark,
- "work" to R.array.tile_states_work,
- "cast" to R.array.tile_states_cast,
- "night" to R.array.tile_states_night,
- "screenrecord" to R.array.tile_states_screenrecord,
- "reverse" to R.array.tile_states_reverse,
- "reduce_brightness" to R.array.tile_states_reduce_brightness,
- "cameratoggle" to R.array.tile_states_cameratoggle,
- "mictoggle" to R.array.tile_states_mictoggle,
- "controls" to R.array.tile_states_controls,
- "wallet" to R.array.tile_states_wallet,
- "qr_code_scanner" to R.array.tile_states_qr_code_scanner,
- "alarm" to R.array.tile_states_alarm,
- "onehanded" to R.array.tile_states_onehanded,
- "color_correction" to R.array.tile_states_color_correction,
- "dream" to R.array.tile_states_dream
- )
-
- fun getSubtitleId(spec: String?): Int {
- return subtitleIdsMap.getOrDefault(spec, R.array.tile_states_default)
- }
-}
-
fun constrainSquishiness(squish: Float): Float {
return 0.1f + squish * 0.9f
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
new file mode 100644
index 0000000..f672e51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.qs.tileimpl
+
+import com.android.systemui.R
+
+/** Return the subtitle resource Id of the given tile. */
+object SubtitleArrayMapping {
+ private val subtitleIdsMap: HashMap<String, Int> = HashMap()
+ init {
+ subtitleIdsMap["internet"] = R.array.tile_states_internet
+ subtitleIdsMap["wifi"] = R.array.tile_states_wifi
+ subtitleIdsMap["cell"] = R.array.tile_states_cell
+ subtitleIdsMap["battery"] = R.array.tile_states_battery
+ subtitleIdsMap["dnd"] = R.array.tile_states_dnd
+ subtitleIdsMap["flashlight"] = R.array.tile_states_flashlight
+ subtitleIdsMap["rotation"] = R.array.tile_states_rotation
+ subtitleIdsMap["bt"] = R.array.tile_states_bt
+ subtitleIdsMap["airplane"] = R.array.tile_states_airplane
+ subtitleIdsMap["location"] = R.array.tile_states_location
+ subtitleIdsMap["hotspot"] = R.array.tile_states_hotspot
+ subtitleIdsMap["inversion"] = R.array.tile_states_inversion
+ subtitleIdsMap["saver"] = R.array.tile_states_saver
+ subtitleIdsMap["dark"] = R.array.tile_states_dark
+ subtitleIdsMap["work"] = R.array.tile_states_work
+ subtitleIdsMap["cast"] = R.array.tile_states_cast
+ subtitleIdsMap["night"] = R.array.tile_states_night
+ subtitleIdsMap["screenrecord"] = R.array.tile_states_screenrecord
+ subtitleIdsMap["reverse"] = R.array.tile_states_reverse
+ subtitleIdsMap["reduce_brightness"] = R.array.tile_states_reduce_brightness
+ subtitleIdsMap["cameratoggle"] = R.array.tile_states_cameratoggle
+ subtitleIdsMap["mictoggle"] = R.array.tile_states_mictoggle
+ subtitleIdsMap["controls"] = R.array.tile_states_controls
+ subtitleIdsMap["wallet"] = R.array.tile_states_wallet
+ subtitleIdsMap["qr_code_scanner"] = R.array.tile_states_qr_code_scanner
+ subtitleIdsMap["alarm"] = R.array.tile_states_alarm
+ subtitleIdsMap["onehanded"] = R.array.tile_states_onehanded
+ subtitleIdsMap["color_correction"] = R.array.tile_states_color_correction
+ subtitleIdsMap["dream"] = R.array.tile_states_dream
+ subtitleIdsMap["font_scaling"] = R.array.tile_states_font_scaling
+ }
+
+ /** Get the subtitle resource id of the given tile */
+ fun getSubtitleId(spec: String?): Int {
+ return if (spec == null) {
+ R.array.tile_states_default
+ } else subtitleIdsMap[spec] ?: R.array.tile_states_default
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 033dbe0..92a83bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -57,6 +57,9 @@
/** Quick settings tile: Airplane mode **/
public class AirplaneModeTile extends QSTileImpl<BooleanState> {
+
+ public static final String TILE_SPEC = "airplane";
+
private final SettingObserver mSetting;
private final BroadcastDispatcher mBroadcastDispatcher;
private final Lazy<ConnectivityManager> mLazyConnectivityManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index 14e0f70..2ca452e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -118,4 +118,8 @@
override fun getLongClickIntent(): Intent? {
return null
}
-}
\ No newline at end of file
+
+ companion object {
+ const val TILE_SPEC = "alarm"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index ee49b29..027a464 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -48,6 +48,8 @@
public class BatterySaverTile extends QSTileImpl<BooleanState> implements
BatteryController.BatteryStateChangeCallback {
+ public static final String TILE_SPEC = "battery";
+
private final BatteryController mBatteryController;
@VisibleForTesting
protected final SettingObserver mSetting;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 9a0d0d9..df1c8df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -55,6 +55,9 @@
/** Quick settings tile: Bluetooth **/
public class BluetoothTile extends QSTileImpl<BooleanState> {
+
+ public static final String TILE_SPEC = "bt";
+
private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
private final BluetoothController mController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
index ee41f1d..93e5f1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
@@ -45,6 +45,8 @@
public class CameraToggleTile extends SensorPrivacyToggleTile {
+ public static final String TILE_SPEC = "cameratoggle";
+
@Inject
protected CameraToggleTile(QSHost host,
@Background Looper backgroundLooper,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index dce137f..8d98481 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -66,7 +66,9 @@
/** Quick settings tile: Cast **/
public class CastTile extends QSTileImpl<BooleanState> {
- private static final String INTERACTION_JANK_TAG = "cast";
+ public static final String TILE_SPEC = "cast";
+
+ private static final String INTERACTION_JANK_TAG = TILE_SPEC;
private static final Intent CAST_SETTINGS =
new Intent(Settings.ACTION_CAST_SETTINGS);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
index 6dfcf5c..b6205d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
@@ -48,6 +48,8 @@
/** Quick settings tile: Color correction **/
public class ColorCorrectionTile extends QSTileImpl<BooleanState> {
+ public static final String TILE_SPEC = "color_correction";
+
private final Icon mIcon = ResourceIcon.get(drawable.ic_qs_color_correction);
private final SettingObserver mSetting;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index a31500c..9a44e83 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -49,6 +49,7 @@
/** Quick settings tile: Invert colors **/
public class ColorInversionTile extends QSTileImpl<BooleanState> {
+ public static final String TILE_SPEC = "inversion";
private final SettingObserver mSetting;
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 2fc99f3..add517e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -48,6 +48,8 @@
public class DataSaverTile extends QSTileImpl<BooleanState> implements
DataSaverController.Listener{
+ public static final String TILE_SPEC = "saver";
+
private static final String INTERACTION_JANK_TAG = "start_data_saver";
private final DataSaverController mDataSaverController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 41d8549..01164fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -159,4 +159,8 @@
override fun getTileLabel(): CharSequence {
return mContext.getText(controlsComponent.getTileTitleId())
}
+
+ companion object {
+ const val TILE_SPEC = "controls"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 8b7f53f..434fe45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -67,6 +67,8 @@
/** Quick settings tile: Do not disturb **/
public class DndTile extends QSTileImpl<BooleanState> {
+ public static final String TILE_SPEC = "dnd";
+
private static final Intent ZEN_SETTINGS =
new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index 5bc209a..53774e8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -60,6 +60,8 @@
/** Quick settings tile: Screensaver (dream) **/
public class DreamTile extends QSTileImpl<QSTile.BooleanState> {
+ public static final String TILE_SPEC = "dream";
+
private static final String LOG_TAG = "QSDream";
// TODO: consider 1 animated icon instead
private final Icon mIconDocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index a747926..e091a75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -49,6 +49,7 @@
public class FlashlightTile extends QSTileImpl<BooleanState> implements
FlashlightController.FlashlightListener {
+ public static final String TILE_SPEC = "flashlight";
private final FlashlightController mFlashlightController;
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
new file mode 100644
index 0000000..721046d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.qs.tiles
+
+import android.content.Intent
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.R
+import com.android.systemui.accessibility.fontscaling.FontScalingDialog
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.settings.SystemSettings
+import javax.inject.Inject
+
+class FontScalingTile
+@Inject
+constructor(
+ host: QSHost,
+ @Background backgroundLooper: Looper,
+ @Main mainHandler: Handler,
+ falsingManager: FalsingManager,
+ metricsLogger: MetricsLogger,
+ statusBarStateController: StatusBarStateController,
+ activityStarter: ActivityStarter,
+ qsLogger: QSLogger,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val systemSettings: SystemSettings
+) :
+ QSTileImpl<QSTile.State?>(
+ host,
+ backgroundLooper,
+ mainHandler,
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger
+ ) {
+ private val icon = ResourceIcon.get(R.drawable.ic_qs_font_scaling)
+
+ override fun isAvailable(): Boolean {
+ return false
+ }
+
+ override fun newTileState(): QSTile.State {
+ val state = QSTile.State()
+ state.handlesLongClick = false
+ return state
+ }
+
+ override fun handleClick(view: View?) {
+ mUiHandler.post {
+ val dialog: SystemUIDialog = FontScalingDialog(mContext, systemSettings)
+ if (view != null) {
+ dialogLaunchAnimator.showFromView(
+ dialog,
+ view,
+ DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
+ )
+ } else {
+ dialog.show()
+ }
+ }
+ }
+
+ override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
+ state?.label = mContext.getString(R.string.quick_settings_font_scaling_label)
+ state?.icon = icon
+ }
+
+ override fun getLongClickIntent(): Intent? {
+ return null
+ }
+
+ override fun getTileLabel(): CharSequence {
+ return mContext.getString(R.string.quick_settings_font_scaling_label)
+ }
+
+ companion object {
+ const val TILE_SPEC = "font_scaling"
+ private const val INTERACTION_JANK_TAG = "font_scaling"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 624def6..6bf8b76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -51,6 +51,7 @@
/** Quick settings tile: Hotspot **/
public class HotspotTile extends QSTileImpl<BooleanState> {
+ public static final String TILE_SPEC = "hotspot";
private final HotspotController mHotspotController;
private final DataSaverController mDataSaverController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 1c60486..12e9aeb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -67,6 +67,9 @@
/** Quick settings tile: Internet **/
public class InternetTile extends QSTileImpl<SignalState> {
+
+ public static final String TILE_SPEC = "internet";
+
private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
protected final NetworkController mController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 9466a69..89d402a3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -48,6 +48,8 @@
/** Quick settings tile: Location **/
public class LocationTile extends QSTileImpl<BooleanState> {
+ public static final String TILE_SPEC = "location";
+
private final LocationController mController;
private final KeyguardStateController mKeyguard;
private final Callback mCallback = new Callback();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
index e547095..2e475d4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
@@ -45,6 +45,8 @@
public class MicrophoneToggleTile extends SensorPrivacyToggleTile {
+ public static final String TILE_SPEC = "mictoggle";
+
@Inject
protected MicrophoneToggleTile(QSHost host,
@Background Looper backgroundLooper,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index a61f0ce..e189f80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -51,7 +51,9 @@
/** Quick settings tile: Enable/Disable NFC **/
public class NfcTile extends QSTileImpl<BooleanState> {
- private static final String NFC = "nfc";
+ public static final String TILE_SPEC = "nfc";
+
+ private static final String NFC = TILE_SPEC;
private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_nfc);
@Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 0e9f659..aacd53b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -61,6 +61,8 @@
public class NightDisplayTile extends QSTileImpl<BooleanState> implements
NightDisplayListener.Callback {
+ public static final String TILE_SPEC = "night";
+
/**
* Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the
* nearest hour and add on the AM/PM indicator.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index 7e17124..ae67d99 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -47,6 +47,9 @@
/** Quick settings tile: One-handed mode **/
public class OneHandedModeTile extends QSTileImpl<BooleanState> {
+
+ public static final String TILE_SPEC = "onehanded";
+
private final Icon mIcon = ResourceIcon.get(
com.android.internal.R.drawable.ic_qs_one_handed_mode);
private final SettingObserver mSetting;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index 6d50b56..92f5272 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -44,6 +44,9 @@
/** Quick settings tile: QR Code Scanner **/
public class QRCodeScannerTile extends QSTileImpl<QSTile.State> {
+
+ public static final String TILE_SPEC = "qr_code_scanner";
+
private static final String TAG = "QRCodeScanner";
private final CharSequence mLabel = mContext.getString(R.string.qr_code_scanner_title);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 248c78e..4a3c563 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -62,6 +62,8 @@
/** Quick settings tile: Quick access wallet **/
public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
+ public static final String TILE_SPEC = "wallet";
+
private static final String TAG = "QuickAccessWalletTile";
private static final String FEATURE_CHROME_OS = "org.chromium.arc";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
index 1dac339..10f1ce4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
@@ -49,6 +49,7 @@
public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState>
implements ReduceBrightColorsController.Listener{
+ public static final String TILE_SPEC = "reduce_brightness";
private final boolean mIsAvailable;
private final ReduceBrightColorsController mReduceBrightColorsController;
private boolean mIsListening;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 600874f..8888c73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -57,6 +57,9 @@
/** Quick settings tile: Rotation **/
public class RotationLockTile extends QSTileImpl<BooleanState> implements
BatteryController.BatteryStateChangeCallback {
+
+ public static final String TILE_SPEC = "rotation";
+
private static final String EMPTY_SECONDARY_STRING = "";
private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate);
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 ad00069..07b50c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -54,6 +54,9 @@
*/
public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
implements RecordingController.RecordingStateChangeCallback {
+
+ public static final String TILE_SPEC = "screenrecord";
+
private static final String TAG = "ScreenRecordTile";
private static final String INTERACTION_JANK_TAG = "screen_record";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index 92f6690a..809689c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -17,7 +17,6 @@
package com.android.systemui.qs.tiles;
import android.app.UiModeManager;
-import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Handler;
@@ -60,6 +59,9 @@
public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
ConfigurationController.ConfigurationListener,
BatteryController.BatteryStateChangeCallback {
+
+ public static final String TILE_SPEC = "dark";
+
public static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm a");
private final UiModeManager mUiModeManager;
private final BatteryController mBatteryController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 72c6bfe..6a5c990 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -49,6 +49,9 @@
/** Quick settings tile: Work profile on/off */
public class WorkModeTile extends QSTileImpl<BooleanState> implements
ManagedProfileController.Callback {
+
+ public static final String TILE_SPEC = "work";
+
private final Icon mIcon = ResourceIcon.get(R.drawable.stat_sys_managed_profile_status);
private final ManagedProfileController mProfileController;
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
index 802db7e..dc3c820 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -27,7 +27,6 @@
import com.android.systemui.CoreStartable;
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.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.SystemUIDialog;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index a979e5a..25ff308b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -25,6 +25,7 @@
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
@@ -99,6 +100,7 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
import com.android.wm.shell.sysui.ShellInterface;
import java.io.PrintWriter;
@@ -144,6 +146,7 @@
private final CommandQueue mCommandQueue;
private final UserTracker mUserTracker;
private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
+ private final Optional<UnfoldTransitionProgressForwarder> mUnfoldTransitionProgressForwarder;
private final UiEventLogger mUiEventLogger;
private final DisplayTracker mDisplayTracker;
@@ -415,6 +418,10 @@
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
params.putBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER,
mSysuiUnlockAnimationController.asBinder());
+ mUnfoldTransitionProgressForwarder.ifPresent(
+ unfoldProgressForwarder -> params.putBinder(
+ KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER,
+ unfoldProgressForwarder.asBinder()));
// Add all the interfaces exposed by the shell
mShellInterface.createExternalInterfaces(params);
@@ -512,7 +519,9 @@
DisplayTracker displayTracker,
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
AssistUtils assistUtils,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder
+ ) {
// b/241601880: This component shouldn't be running for a non-primary user
if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) {
Log.e(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable());
@@ -538,6 +547,7 @@
mSysUiState.addCallback(this::notifySystemUiStateFlags);
mUiEventLogger = uiEventLogger;
mDisplayTracker = displayTracker;
+ mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockModule.kt b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockModule.kt
new file mode 100644
index 0000000..9abe90f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockModule.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.rotationlock
+
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.RotationLockTile
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface RotationLockModule {
+
+ /** Inject RotationLockTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(RotationLockTile.TILE_SPEC)
+ fun bindRotationLockTile(rotationLockTile: RotationLockTile): QSTileImpl<*>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
new file mode 100644
index 0000000..7467805
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.screenrecord
+
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.ScreenRecordTile
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface ScreenRecordModule {
+ /** Inject ScreenRecordTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(ScreenRecordTile.TILE_SPEC)
+ fun bindScreenRecordTile(screenRecordTile: ScreenRecordTile): QSTileImpl<*>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 72a8e23..8721d71 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -99,6 +99,7 @@
import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
import com.android.systemui.settings.DisplayTracker;
@@ -480,7 +481,7 @@
}
mScreenshotView.setScreenshot(screenshot);
- if (screenshot.getTaskId() >= 0) {
+ if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) {
mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
new AssistContentRequester.Callback() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 7a62bae..fc94aed 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -93,7 +93,13 @@
@UiEvent(doc = "User has discarded the result of a long screenshot")
SCREENSHOT_LONG_SCREENSHOT_EXIT(911),
@UiEvent(doc = "A screenshot has been taken and saved to work profile")
- SCREENSHOT_SAVED_TO_WORK_PROFILE(1240);
+ SCREENSHOT_SAVED_TO_WORK_PROFILE(1240),
+ @UiEvent(doc = "Notes application triggered the screenshot for notes")
+ SCREENSHOT_FOR_NOTE_TRIGGERED(1308),
+ @UiEvent(doc = "User accepted the screenshot to be sent to the notes app")
+ SCREENSHOT_FOR_NOTE_ACCEPTED(1309),
+ @UiEvent(doc = "User cancelled the screenshot for notes app flow")
+ SCREENSHOT_FOR_NOTE_CANCELLED(1310);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 8035d19..111278a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -225,7 +225,7 @@
return;
}
- if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA)) {
+ if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_METADATA_REFACTOR)) {
Log.d(TAG, "Processing screenshot data");
ScreenshotData screenshotData = ScreenshotData.fromRequest(request);
try {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 41846f0..cd45b32 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2144,6 +2144,7 @@
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);
@@ -4624,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);
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 4bf84f7..f4782c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -25,6 +25,7 @@
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;
@@ -95,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;
@@ -127,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;
@@ -327,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) {
@@ -830,6 +834,7 @@
* may continuously be cycled through.
*/
protected final void updateDeviceEntryIndication(boolean animate) {
+ mKeyguardLogger.logUpdateDeviceEntryIndication(animate, mVisible, mDozing);
if (!mVisible) {
return;
}
@@ -1080,18 +1085,23 @@
}
}
+ 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
@@ -1134,6 +1144,13 @@
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);
}
@@ -1417,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/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index f565f3d..afa60fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -262,7 +262,6 @@
fun setStackScroller(nsslController: NotificationStackScrollLayoutController) {
this.nsslController = nsslController
- touchHelper.host = nsslController.view
touchHelper.expandCallback = nsslController.expandHelperCallback
}
@@ -736,14 +735,12 @@
private var dragDownAmountOnStart = 0.0f
lateinit var expandCallback: ExpandHelper.Callback
- lateinit var host: View
private var minDragDistance = 0
private var initialTouchX = 0f
private var initialTouchY = 0f
private var touchSlop = 0f
private var slopMultiplier = 0f
- private val temp2 = IntArray(2)
private var draggedFarEnough = false
private var startingChild: ExpandableView? = null
private var lastHeight = 0f
@@ -923,7 +920,6 @@
}
private fun findView(x: Float, y: Float): ExpandableView? {
- host.getLocationOnScreen(temp2)
- return expandCallback.getChildAtRawPosition(x + temp2[0], y + temp2[1])
+ return expandCallback.getChildAtRawPosition(x, y)
}
}
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/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
new file mode 100644
index 0000000..1099810
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.connectivity
+
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.AirplaneModeTile
+import com.android.systemui.qs.tiles.BluetoothTile
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.qs.tiles.InternetTile
+import com.android.systemui.qs.tiles.NfcTile
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface ConnectivityModule {
+
+ /** Inject InternetTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(InternetTile.TILE_SPEC)
+ fun bindInternetTile(internetTile: InternetTile): QSTileImpl<*>
+
+ /** Inject BluetoothTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(BluetoothTile.TILE_SPEC)
+ fun bindBluetoothTile(bluetoothTile: BluetoothTile): QSTileImpl<*>
+
+ /** Inject CastTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(CastTile.TILE_SPEC)
+ fun bindCastTile(castTile: CastTile): QSTileImpl<*>
+
+ /** Inject HotspotTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(HotspotTile.TILE_SPEC)
+ fun bindHotspotTile(hotspotTile: HotspotTile): QSTileImpl<*>
+
+ /** Inject AirplaneModeTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(AirplaneModeTile.TILE_SPEC)
+ fun bindAirplaneModeTile(airplaneModeTile: AirplaneModeTile): QSTileImpl<*>
+
+ /** Inject DataSaverTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(DataSaverTile.TILE_SPEC)
+ fun bindDataSaverTile(dataSaverTile: DataSaverTile): QSTileImpl<*>
+
+ /** Inject NfcTile into tileMap in QSModule */
+ @Binds @IntoMap @StringKey(NfcTile.TILE_SPEC) fun bindNfcTile(nfcTile: NfcTile): QSTileImpl<*>
+}
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 aed1324..6ef6165 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -38,10 +38,12 @@
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
@@ -56,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
@@ -86,6 +89,7 @@
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,
@@ -96,7 +100,7 @@
optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
optionalPlugin: Optional<BcSmartspaceDataPlugin>,
optionalConfigPlugin: Optional<BcSmartspaceConfigPlugin>,
-) {
+) : Dumpable {
companion object {
private const val TAG = "LockscreenSmartspaceController"
}
@@ -230,6 +234,7 @@
init {
deviceProvisionedController.addCallback(deviceProvisionedListener)
+ dumpManager.registerDumpable(this)
}
fun isEnabled(): Boolean {
@@ -543,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..82bd45c 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
@@ -14,18 +14,19 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
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,20 +36,25 @@
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
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
/**
@@ -60,6 +66,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 +94,78 @@
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
+ var clearUnseenOnUnlock = false
keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
- if (!isKeyguardShowing) {
+ if (isKeyguardShowing) {
+ // Wait for the user to spend enough time on the lock screen before clearing unseen
+ // set when unlocked
+ awaitTimeSpentNotDozing(SEEN_TIMEOUT)
+ clearUnseenOnUnlock = true
+ } else {
unseenNotifFilter.invalidateList("keyguard no longer showing")
- delay(SEEN_TIMEOUT)
+ if (clearUnseenOnUnlock) {
+ clearUnseenOnUnlock = false
+ unseenNotifications.clear()
+ }
+ trackUnseenNotifications()
+ }
+ }
+ }
+
+ private suspend fun awaitTimeSpentNotDozing(duration: Duration) {
+ keyguardRepository.isDozing
+ // Use transformLatest so that the timeout delay is cancelled if the device enters doze,
+ // and is restarted when doze ends.
+ .transformLatest { isDozing ->
+ if (!isDozing) {
+ delay(duration)
+ // Signal timeout has completed
+ emit(Unit)
+ }
+ }
+ // Suspend until the first emission
+ .first()
+ }
+
+ 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 +193,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)
}
}
@@ -215,15 +276,3 @@
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/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 13b3aca..27fe747 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -70,7 +70,15 @@
buffer.log(TAG, DEBUG, {
str1 = entry.logKey
}, {
- "No alerting: snoozed package: $str1"
+ "No heads up: snoozed package: $str1"
+ })
+ }
+
+ fun logHeadsUpPackageSnoozeBypassedHasFsi(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ }, {
+ "Heads up: package snooze bypassed because notification has full-screen intent: $str1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 9bcf92d..afeb72f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -19,6 +19,7 @@
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR;
+import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI;
import android.app.Notification;
import android.app.NotificationManager;
@@ -87,7 +88,10 @@
FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236),
@UiEvent(doc = "HUN suppressed for old when")
- HUN_SUPPRESSED_OLD_WHEN(1237);
+ HUN_SUPPRESSED_OLD_WHEN(1237),
+
+ @UiEvent(doc = "HUN snooze bypassed for potentially suppressed FSI")
+ HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI(1269);
private final int mId;
@@ -409,7 +413,15 @@
return false;
}
- if (isSnoozedPackage(sbn)) {
+ final boolean isSnoozedPackage = isSnoozedPackage(sbn);
+ final boolean fsiRequiresKeyguard = mFlags.fullScreenIntentRequiresKeyguard();
+ final boolean hasFsi = sbn.getNotification().fullScreenIntent != null;
+
+ // Assume any notification with an FSI is time-sensitive (like an alarm or incoming call)
+ // and ignore whether HUNs have been snoozed for the package.
+ final boolean shouldBypassSnooze = fsiRequiresKeyguard && hasFsi;
+
+ if (isSnoozedPackage && !shouldBypassSnooze) {
if (log) mLogger.logNoHeadsUpPackageSnoozed(entry);
return false;
}
@@ -447,6 +459,19 @@
return false;
}
}
+
+ if (isSnoozedPackage) {
+ if (log) {
+ mLogger.logHeadsUpPackageSnoozeBypassedHasFsi(entry);
+ final int uid = entry.getSbn().getUid();
+ final String packageName = entry.getSbn().getPackageName();
+ mUiEventLogger.log(HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI, uid,
+ packageName);
+ }
+
+ return true;
+ }
+
if (log) mLogger.logHeadsUp(entry);
return true;
}
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/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/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/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/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/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 608bfa6..924ae4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -45,8 +45,11 @@
import com.android.systemui.R;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.util.DialogKt;
import java.util.ArrayList;
import java.util.List;
@@ -68,6 +71,7 @@
private static final boolean DEFAULT_DISMISS_ON_DEVICE_LOCK = true;
private final Context mContext;
+ private final FeatureFlags mFeatureFlags;
@Nullable private final DismissReceiver mDismissReceiver;
private final Handler mHandler = new Handler();
private final SystemUIDialogManager mDialogManager;
@@ -96,16 +100,23 @@
// TODO(b/219008720): Remove those calls to Dependency.get by introducing a
// SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
// the content and attach listeners.
- this(context, theme, dismissOnDeviceLock, Dependency.get(SystemUIDialogManager.class),
- Dependency.get(SysUiState.class), Dependency.get(BroadcastDispatcher.class),
+ this(context, theme, dismissOnDeviceLock,
+ Dependency.get(FeatureFlags.class),
+ Dependency.get(SystemUIDialogManager.class),
+ Dependency.get(SysUiState.class),
+ Dependency.get(BroadcastDispatcher.class),
Dependency.get(DialogLaunchAnimator.class));
}
public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
- SystemUIDialogManager dialogManager, SysUiState sysUiState,
- BroadcastDispatcher broadcastDispatcher, DialogLaunchAnimator dialogLaunchAnimator) {
+ FeatureFlags featureFlags,
+ SystemUIDialogManager dialogManager,
+ SysUiState sysUiState,
+ BroadcastDispatcher broadcastDispatcher,
+ DialogLaunchAnimator dialogLaunchAnimator) {
super(context, theme);
mContext = context;
+ mFeatureFlags = featureFlags;
applyFlags(this);
WindowManager.LayoutParams attrs = getWindow().getAttributes();
@@ -130,6 +141,12 @@
for (int i = 0; i < mOnCreateRunnables.size(); i++) {
mOnCreateRunnables.get(i).run();
}
+ if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM)) {
+ DialogKt.registerAnimationOnBackInvoked(
+ /* dialog = */ this,
+ /* targetView = */ getWindow().getDecorView()
+ );
+ }
}
private void updateWindowSize() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
index 2d80edb..270c592 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
@@ -21,7 +21,7 @@
import android.widget.ImageView
import android.widget.TextView
import com.android.systemui.R
-import com.android.systemui.common.ui.view.LaunchableLinearLayout
+import com.android.systemui.animation.view.LaunchableLinearLayout
class StatusBarUserSwitcherContainer(
context: Context?,
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/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/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
index 2f34516..16c4027 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
+import android.os.ParcelUuid
+
/**
* SystemUI representation of [SubscriptionInfo]. Currently we only use two fields on the
* subscriptions themselves: subscriptionId and isOpportunistic. Any new fields that we need can be
@@ -29,4 +31,7 @@
* filtering in certain cases. See [MobileIconsInteractor] for the filtering logic
*/
val isOpportunistic: Boolean = false,
+
+ /** Subscriptions in the same group may be filtered or treated as a single subscription */
+ val groupUuid: ParcelUuid? = 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..8f6a87b 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
@@ -27,8 +28,8 @@
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.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -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..73ce5e6 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
@@ -51,10 +52,9 @@
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
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> =
@@ -348,5 +376,10 @@
SubscriptionModel(
subscriptionId = subscriptionId,
isOpportunistic = isOpportunistic,
+ groupUuid = groupUuid,
)
+
+ 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..5a2e11e 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,51 @@
* 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]
- // NOTE: at this point, we are now returning a single SubscriptionInfo
+ // Filtering only applies to subscriptions in the same group
+ if (info1.groupUuid == null || info1.groupUuid != info2.groupUuid) {
+ return@combine unfilteredSubs
+ }
- // 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 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
+
+ // 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
@@ -182,7 +192,7 @@
* validated bit from the old active network (A) while data is changing to the new one (B).
*
* This condition only applies if
- * 1. A and B are in the same subscription group (e.c. for CBRS data switching) and
+ * 1. A and B are in the same subscription group (e.g. for CBRS data switching) and
* 2. A was validated before the switch
*
* The goal of this is to minimize the flickering in the UI of the cellular indicator
@@ -195,6 +205,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 +227,12 @@
networkConnectivity
}
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = "$LOGGING_PREFIX.defaultConnection",
+ initialValue = mobileConnectionsRepo.defaultMobileNetworkConnectivity.value,
+ )
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
@@ -259,10 +281,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 +308,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/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index ac4d55c..08c14e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
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.shared.model.WifiNetworkModel
import kotlinx.coroutines.flow.StateFlow
/** Provides data related to the wifi state. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index 2cb81c8..e0e0ed7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -23,9 +23,9 @@
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
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.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
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..a4fbc2c 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
@@ -19,9 +19,9 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -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/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index 5d4a666..86a668a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -18,8 +18,8 @@
import com.android.systemui.dagger.SysUISingleton
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.RealWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
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 c45b420..7b486c1 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
@@ -43,9 +43,9 @@
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 86dcd18..96ab074 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -21,8 +21,8 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -58,25 +58,29 @@
}
@SysUISingleton
-class WifiInteractorImpl @Inject constructor(
+class WifiInteractorImpl
+@Inject
+constructor(
connectivityRepository: ConnectivityRepository,
wifiRepository: WifiRepository,
) : WifiInteractor {
- override val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
- when (info) {
- is WifiNetworkModel.Unavailable -> null
- is WifiNetworkModel.Invalid -> null
- is WifiNetworkModel.Inactive -> null
- is WifiNetworkModel.CarrierMerged -> null
- is WifiNetworkModel.Active -> when {
- info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint ->
- info.passpointProviderFriendlyName
- info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid
- else -> null
+ override val ssid: Flow<String?> =
+ wifiRepository.wifiNetwork.map { info ->
+ when (info) {
+ is WifiNetworkModel.Unavailable -> null
+ is WifiNetworkModel.Invalid -> null
+ is WifiNetworkModel.Inactive -> null
+ is WifiNetworkModel.CarrierMerged -> null
+ is WifiNetworkModel.Active ->
+ when {
+ info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint ->
+ info.passpointProviderFriendlyName
+ info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid
+ else -> null
+ }
}
}
- }
override val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
@@ -86,7 +90,6 @@
override val activity: StateFlow<DataActivityModel> = wifiRepository.wifiActivity
- override val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
- it.contains(ConnectivitySlot.WIFI)
- }
+ override val isForceHidden: Flow<Boolean> =
+ connectivityRepository.forceHiddenSlots.map { it.contains(ConnectivitySlot.WIFI) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
index da2daf2..0923d78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package com.android.systemui.statusbar.pipeline.wifi.shared.model
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.SubscriptionManager
import androidx.annotation.VisibleForTesting
-import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.log.table.Diffable
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
/** Provides information about the current wifi network. */
sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
@@ -57,9 +57,7 @@
}
}
- /**
- * A model representing that the wifi information we received was invalid in some way.
- */
+ /** A model representing that the wifi information we received was invalid in some way. */
data class Invalid(
/** A description of why the wifi information was invalid. */
val invalidReason: String,
@@ -142,21 +140,17 @@
*/
val subscriptionId: Int,
- /**
- * The signal level, guaranteed to be 0 <= level <= numberOfLevels.
- */
+ /** The signal level, guaranteed to be 0 <= level <= numberOfLevels. */
val level: Int,
- /**
- * The maximum possible level.
- */
- val numberOfLevels: Int = DEFAULT_NUM_LEVELS,
+ /** The maximum possible level. */
+ val numberOfLevels: Int = MobileConnectionRepository.DEFAULT_NUM_LEVELS,
) : WifiNetworkModel() {
init {
require(level in MIN_VALID_LEVEL..numberOfLevels) {
"0 <= wifi level <= $numberOfLevels required; level was $level"
}
- require(subscriptionId != INVALID_SUBSCRIPTION_ID) {
+ require(subscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
"subscription ID cannot be invalid"
}
}
@@ -208,9 +202,7 @@
/** See [android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED]. */
val isValidated: Boolean = false,
- /**
- * The wifi signal level, guaranteed to be 0 <= level <= 4.
- */
+ /** The wifi signal level, guaranteed to be 0 <= level <= 4. */
val level: Int,
/** See [android.net.wifi.WifiInfo.ssid]. */
@@ -255,8 +247,10 @@
if (prevVal.isPasspointAccessPoint != isPasspointAccessPoint) {
row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
}
- if (prevVal.isOnlineSignUpForPasspointAccessPoint !=
- isOnlineSignUpForPasspointAccessPoint) {
+ if (
+ prevVal.isOnlineSignUpForPasspointAccessPoint !=
+ isOnlineSignUpForPasspointAccessPoint
+ ) {
row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
}
if (prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) {
@@ -281,29 +275,29 @@
// Only include the passpoint-related values in the string if we have them. (Most
// networks won't have them so they'll be mostly clutter.)
val passpointString =
- if (isPasspointAccessPoint ||
- isOnlineSignUpForPasspointAccessPoint ||
- passpointProviderFriendlyName != null) {
+ if (
+ isPasspointAccessPoint ||
+ isOnlineSignUpForPasspointAccessPoint ||
+ passpointProviderFriendlyName != null
+ ) {
", isPasspointAp=$isPasspointAccessPoint, " +
"isOnlineSignUpForPasspointAp=$isOnlineSignUpForPasspointAccessPoint, " +
"passpointName=$passpointProviderFriendlyName"
- } else {
- ""
- }
+ } else {
+ ""
+ }
return "WifiNetworkModel.Active(networkId=$networkId, isValidated=$isValidated, " +
"level=$level, ssid=$ssid$passpointString)"
}
companion object {
- @VisibleForTesting
- internal const val MAX_VALID_LEVEL = 4
+ @VisibleForTesting internal const val MAX_VALID_LEVEL = 4
}
}
companion object {
- @VisibleForTesting
- internal const val MIN_VALID_LEVEL = 0
+ @VisibleForTesting internal const val MIN_VALID_LEVEL = 0
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 95431af..0f5ff91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -25,8 +25,6 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
-import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
@@ -34,13 +32,15 @@
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
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.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -55,15 +55,12 @@
/**
* Models the UI state for the status bar wifi icon.
*
- * This class exposes three view models, one per status bar location:
- * - [home]
- * - [keyguard]
- * - [qs]
- * In order to get the UI state for the wifi icon, you must use one of those view models (whichever
- * is correct for your location).
+ * This class exposes three view models, one per status bar location: [home], [keyguard], and [qs].
+ * In order to get the UI state for the wifi icon, you must use one of those view models (whichever
+ * is correct for your location).
*
- * Internally, this class maintains the current state of the wifi icon and notifies those three
- * view models of any changes.
+ * Internally, this class maintains the current state of the wifi icon and notifies those three view
+ * models of any changes.
*/
@SysUISingleton
class WifiViewModel
@@ -85,12 +82,13 @@
is WifiNetworkModel.Unavailable -> WifiIcon.Hidden
is WifiNetworkModel.Invalid -> WifiIcon.Hidden
is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
- is WifiNetworkModel.Inactive -> WifiIcon.Visible(
- res = WIFI_NO_NETWORK,
- ContentDescription.Loaded(
- "${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
+ is WifiNetworkModel.Inactive ->
+ WifiIcon.Visible(
+ res = WIFI_NO_NETWORK,
+ ContentDescription.Loaded(
+ "${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
+ )
)
- )
is WifiNetworkModel.Active -> {
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
when {
@@ -114,25 +112,25 @@
/** The wifi icon that should be displayed. */
private val wifiIcon: StateFlow<WifiIcon> =
combine(
- interactor.isEnabled,
- interactor.isDefault,
- interactor.isForceHidden,
- interactor.wifiNetwork,
- ) { isEnabled, isDefault, isForceHidden, wifiNetwork ->
- if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
- return@combine WifiIcon.Hidden
- }
+ interactor.isEnabled,
+ interactor.isDefault,
+ interactor.isForceHidden,
+ interactor.wifiNetwork,
+ ) { isEnabled, isDefault, isForceHidden, wifiNetwork ->
+ if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
+ return@combine WifiIcon.Hidden
+ }
- val icon = wifiNetwork.icon()
+ val icon = wifiNetwork.icon()
- return@combine when {
- isDefault -> icon
- wifiConstants.alwaysShowIconIfEnabled -> icon
- !connectivityConstants.hasDataCapabilities -> icon
- wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
- else -> WifiIcon.Hidden
+ return@combine when {
+ isDefault -> icon
+ wifiConstants.alwaysShowIconIfEnabled -> icon
+ !connectivityConstants.hasDataCapabilities -> icon
+ wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
+ else -> WifiIcon.Hidden
+ }
}
- }
.logDiffsForTable(
wifiTableLogBuffer,
columnPrefix = "",
@@ -147,34 +145,34 @@
/** The wifi activity status. Null if we shouldn't display the activity status. */
private val activity: Flow<DataActivityModel?> =
if (!connectivityConstants.shouldShowActivityConfig) {
- flowOf(null)
- } else {
- combine(interactor.activity, interactor.ssid) { activity, ssid ->
- when (ssid) {
- null -> null
- else -> activity
+ flowOf(null)
+ } else {
+ combine(interactor.activity, interactor.ssid) { activity, ssid ->
+ when (ssid) {
+ null -> null
+ else -> activity
+ }
}
}
- }
- .distinctUntilChanged()
- .logOutputChange(logger, "activity")
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
+ .distinctUntilChanged()
+ .logOutputChange(logger, "activity")
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
private val isActivityInViewVisible: Flow<Boolean> =
- activity
- .map { it?.hasActivityIn == true }
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ activity
+ .map { it?.hasActivityIn == true }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
private val isActivityOutViewVisible: Flow<Boolean> =
- activity
- .map { it?.hasActivityOut == true }
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ activity
+ .map { it?.hasActivityOut == true }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
private val isActivityContainerVisible: Flow<Boolean> =
- combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut ->
- activityIn || activityOut
- }
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+ combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut ->
+ activityIn || activityOut
+ }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
// TODO(b/238425913): It isn't ideal for the wifi icon to need to know about whether the
// airplane icon is visible. Instead, we should have a parent StatusBarSystemIconsViewModel
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/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
new file mode 100644
index 0000000..2a18b81
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use mHost 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.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.AlarmTile
+import com.android.systemui.qs.tiles.CameraToggleTile
+import com.android.systemui.qs.tiles.DndTile
+import com.android.systemui.qs.tiles.FlashlightTile
+import com.android.systemui.qs.tiles.LocationTile
+import com.android.systemui.qs.tiles.MicrophoneToggleTile
+import com.android.systemui.qs.tiles.UiModeNightTile
+import com.android.systemui.qs.tiles.WorkModeTile
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface PolicyModule {
+
+ /** Inject DndTile into tileMap in QSModule */
+ @Binds @IntoMap @StringKey(DndTile.TILE_SPEC) fun bindDndTile(dndTile: DndTile): QSTileImpl<*>
+
+ /** Inject WorkModeTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(WorkModeTile.TILE_SPEC)
+ fun bindWorkModeTile(workModeTile: WorkModeTile): QSTileImpl<*>
+
+ /** Inject FlashlightTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(FlashlightTile.TILE_SPEC)
+ fun bindFlashlightTile(flashlightTile: FlashlightTile): QSTileImpl<*>
+
+ /** Inject LocationTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(LocationTile.TILE_SPEC)
+ fun bindLocationTile(locationTile: LocationTile): QSTileImpl<*>
+
+ /** Inject CameraToggleTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(CameraToggleTile.TILE_SPEC)
+ fun bindCameraToggleTile(cameraToggleTile: CameraToggleTile): QSTileImpl<*>
+
+ /** Inject MicrophoneToggleTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(MicrophoneToggleTile.TILE_SPEC)
+ fun bindMicrophoneToggleTile(microphoneToggleTile: MicrophoneToggleTile): QSTileImpl<*>
+
+ /** Inject AlarmTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(AlarmTile.TILE_SPEC)
+ fun bindAlarmTile(alarmTile: AlarmTile): QSTileImpl<*>
+
+ @Binds
+ @IntoMap
+ @StringKey(UiModeNightTile.TILE_SPEC)
+ fun bindUiModeNightTile(uiModeNightTile: UiModeNightTile): QSTileImpl<*>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 696134c..a20a5b2 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -16,6 +16,8 @@
package com.android.systemui.temporarydisplay.chipbar
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Rect
import android.os.PowerManager
@@ -27,11 +29,14 @@
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.IdRes
+import androidx.annotation.VisibleForTesting
import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text.Companion.loadText
@@ -101,6 +106,15 @@
private lateinit var parent: ChipbarRootView
+ /** The current loading information, or null we're not currently loading. */
+ @VisibleForTesting
+ internal var loadingDetails: LoadingDetails? = null
+ private set(value) {
+ // Always cancel the old one before updating
+ field?.animator?.cancel()
+ field = value
+ }
+
override val windowLayoutParams =
commonWindowLayoutParams.apply { gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) }
@@ -143,8 +157,22 @@
// ---- End item ----
// Loading
- currentView.requireViewById<View>(R.id.loading).visibility =
- (newInfo.endItem == ChipbarEndItem.Loading).visibleIfTrue()
+ val isLoading = newInfo.endItem == ChipbarEndItem.Loading
+ val loadingView = currentView.requireViewById<ImageView>(R.id.loading)
+ loadingView.visibility = isLoading.visibleIfTrue()
+
+ if (isLoading) {
+ val currentLoadingDetails = loadingDetails
+ // Since there can be multiple chipbars, we need to check if the loading view is the
+ // same and possibly re-start the loading animation on the new view.
+ if (currentLoadingDetails == null || currentLoadingDetails.loadingView != loadingView) {
+ val newDetails = createLoadingDetails(loadingView)
+ newDetails.animator.start()
+ loadingDetails = newDetails
+ }
+ } else {
+ loadingDetails = null
+ }
// Error
currentView.requireViewById<View>(R.id.error).visibility =
@@ -223,12 +251,17 @@
override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
val innerView = view.getInnerView()
innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
- val removed = chipbarAnimator.animateViewOut(innerView, onAnimationEnd)
+
+ val fullEndRunnable = Runnable {
+ loadingDetails = null
+ onAnimationEnd.run()
+ }
+ val removed = chipbarAnimator.animateViewOut(innerView, fullEndRunnable)
// If the view doesn't get animated, the [onAnimationEnd] runnable won't get run. So, just
// run it immediately.
if (!removed) {
logger.logAnimateOutFailure()
- onAnimationEnd.run()
+ fullEndRunnable.run()
}
updateGestureListening()
@@ -269,7 +302,7 @@
}
private fun ViewGroup.getInnerView(): ViewGroup {
- return requireViewById(R.id.chipbar_inner)
+ return this.requireViewById(R.id.chipbar_inner)
}
override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -283,8 +316,28 @@
View.GONE
}
}
+
+ private fun createLoadingDetails(loadingView: View): LoadingDetails {
+ // Ideally, we would use a <ProgressBar> view, which would automatically handle the loading
+ // spinner rotation for us. However, due to b/243983980, the ProgressBar animation
+ // unexpectedly pauses when SysUI starts another window. ObjectAnimator is a workaround that
+ // won't pause.
+ val animator =
+ ObjectAnimator.ofFloat(loadingView, View.ROTATION, 0f, 360f).apply {
+ duration = LOADING_ANIMATION_DURATION_MS
+ repeatCount = ValueAnimator.INFINITE
+ interpolator = Interpolators.LINEAR
+ }
+ return LoadingDetails(loadingView, animator)
+ }
+
+ internal data class LoadingDetails(
+ val loadingView: View,
+ val animator: ObjectAnimator,
+ )
}
@IdRes private val INFO_TAG = R.id.tag_chipbar_info
private const val SWIPE_UP_GESTURE_REASON = "SWIPE_UP_GESTURE_DETECTED"
private const val TAG = "ChipbarCoordinator"
+private const val LOADING_ANIMATION_DURATION_MS = 1000L
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 2b29885..f7c8bac 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -21,6 +21,7 @@
import com.android.settingslib.users.EditUserInfoController;
import com.android.systemui.user.data.repository.UserRepositoryModule;
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule;
import com.android.systemui.user.ui.dialog.UserDialogModule;
import dagger.Binds;
@@ -36,6 +37,7 @@
includes = {
UserDialogModule.class,
UserRepositoryModule.class,
+ HeadlessSystemUserModeModule.class,
}
)
public abstract class UserModule {
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/user/domain/interactor/HeadlessSystemUserMode.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserMode.kt
new file mode 100644
index 0000000..756e6a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserMode.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.user.domain.interactor
+
+import android.os.UserManager
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+interface HeadlessSystemUserMode {
+
+ fun isHeadlessSystemUserMode(): Boolean
+}
+
+@SysUISingleton
+class HeadlessSystemUserModeImpl @Inject constructor() : HeadlessSystemUserMode {
+ override fun isHeadlessSystemUserMode(): Boolean {
+ return UserManager.isHeadlessSystemUserMode()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeModule.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeModule.kt
new file mode 100644
index 0000000..0efa2d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/HeadlessSystemUserModeModule.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.user.domain.interactor
+
+import dagger.Binds
+
+@dagger.Module
+interface HeadlessSystemUserModeModule {
+
+ @Binds
+ fun bindIsHeadlessSystemUserMode(impl: HeadlessSystemUserModeImpl): HeadlessSystemUserMode
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index c0ba3cc..3f895ad 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -86,6 +86,7 @@
private val keyguardInteractor: KeyguardInteractor,
private val featureFlags: FeatureFlags,
private val manager: UserManager,
+ private val headlessSystemUserMode: HeadlessSystemUserMode,
@Application private val applicationScope: CoroutineScope,
telephonyInteractor: TelephonyInteractor,
broadcastDispatcher: BroadcastDispatcher,
@@ -560,7 +561,10 @@
actionType = action,
isRestricted = isRestricted,
isSwitchToEnabled =
- canSwitchUsers(selectedUserId) &&
+ canSwitchUsers(
+ selectedUserId = selectedUserId,
+ isAction = true,
+ ) &&
// If the user is auto-created is must not be currently resetting.
!(isGuestUserAutoCreated && isGuestUserResetting),
)
@@ -712,10 +716,32 @@
}
}
- private suspend fun canSwitchUsers(selectedUserId: Int): Boolean {
- return withContext(backgroundDispatcher) {
- manager.getUserSwitchability(UserHandle.of(selectedUserId))
- } == UserManager.SWITCHABILITY_STATUS_OK
+ private suspend fun canSwitchUsers(
+ selectedUserId: Int,
+ isAction: Boolean = false,
+ ): Boolean {
+ val isHeadlessSystemUserMode =
+ withContext(backgroundDispatcher) { headlessSystemUserMode.isHeadlessSystemUserMode() }
+ // Whether menu item should be active. True if item is a user or if any user has
+ // signed in since reboot or in all cases for non-headless system user mode.
+ val isItemEnabled = !isAction || !isHeadlessSystemUserMode || isAnyUserUnlocked()
+ return isItemEnabled &&
+ withContext(backgroundDispatcher) {
+ manager.getUserSwitchability(UserHandle.of(selectedUserId))
+ } == UserManager.SWITCHABILITY_STATUS_OK
+ }
+
+ private suspend fun isAnyUserUnlocked(): Boolean {
+ return manager
+ .getUsers(
+ /* excludePartial= */ true,
+ /* excludeDying= */ true,
+ /* excludePreCreated= */ true
+ )
+ .any { user ->
+ user.id != UserHandle.USER_SYSTEM &&
+ withContext(backgroundDispatcher) { manager.isUserUnlocked(user.userHandle) }
+ }
}
@SuppressLint("UseCompatLoadingForDrawables")
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..8d32a48
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/ConditionalCoreStartable.java
@@ -0,0 +1,74 @@
+/*
+ * 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() {
+ 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() {
+ 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/src/com/android/systemui/util/leak/GarbageMonitorModule.kt b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.kt
new file mode 100644
index 0000000..c74e71f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitorModule.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.util.leak
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface GarbageMonitorModule {
+ /** Inject into GarbageMonitor.Service. */
+ @Binds
+ @IntoMap
+ @ClassKey(GarbageMonitor::class)
+ fun bindGarbageMonitorService(sysui: GarbageMonitor.Service): CoreStartable
+
+ @Binds
+ @IntoMap
+ @StringKey(GarbageMonitor.MemoryTile.TILE_SPEC)
+ fun bindMemoryTile(memoryTile: GarbageMonitor.MemoryTile): QSTileImpl<*>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
index 2c901d2..9429d89 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java
@@ -22,6 +22,8 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.QuickAccessWalletTile;
import com.android.systemui.wallet.ui.WalletActivity;
import java.util.concurrent.Executor;
@@ -31,6 +33,7 @@
import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import dagger.multibindings.StringKey;
/**
@@ -52,4 +55,11 @@
@Background Executor bgExecutor) {
return QuickAccessWalletClient.create(context, bgExecutor);
}
+
+ /** */
+ @Binds
+ @IntoMap
+ @StringKey(QuickAccessWalletTile.TILE_SPEC)
+ public abstract QSTileImpl<?> bindQuickAccessWalletTile(
+ QuickAccessWalletTile quickAccessWalletTile);
}
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 e8d50ca..badeb27 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -24,13 +24,19 @@
import android.os.PowerManager
import android.os.PowerManager.WAKE_REASON_BIOMETRIC
import android.os.UserHandle
-import android.provider.Settings
+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_ON_WAKE
+import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.FakeSettings
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -41,20 +47,11 @@
import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
@SmallTest
class ActiveUnlockConfigTest : SysuiTestCase() {
- private val fakeWakeUri = Uri.Builder().appendPath("wake").build()
- private val fakeUnlockIntentUri = Uri.Builder().appendPath("unlock-intent").build()
- private val fakeBioFailUri = Uri.Builder().appendPath("bio-fail").build()
- 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
+ private lateinit var secureSettings: FakeSettings
@Mock
private lateinit var contentResolver: ContentResolver
@Mock
@@ -63,33 +60,20 @@
private lateinit var dumpManager: DumpManager
@Mock
private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var mockPrintWriter: PrintWriter
@Captor
private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
private lateinit var activeUnlockConfig: ActiveUnlockConfig
+ private var currentUser: Int = 0
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE))
- .thenReturn(fakeWakeUri)
- `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT))
- .thenReturn(fakeUnlockIntentUri)
- `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
- .thenReturn(fakeBioFailUri)
- `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS))
- .thenReturn(fakeFaceErrorsUri)
- `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
- .thenReturn(fakeFaceAcquiredUri)
- `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)
-
+ currentUser = KeyguardUpdateMonitor.getCurrentUser()
+ secureSettings = FakeSettings()
activeUnlockConfig = ActiveUnlockConfig(
handler,
secureSettings,
@@ -105,8 +89,6 @@
@Test
fun onWakeupSettingChanged() {
- verifyRegisterSettingObserver()
-
// GIVEN no active unlock settings enabled
assertFalse(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -114,9 +96,8 @@
)
// WHEN unlock on wake is allowed
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
- 0, 0)).thenReturn(1)
- updateSetting(fakeWakeUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_WAKE, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE))
// THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure
assertTrue(
@@ -135,8 +116,6 @@
@Test
fun onUnlockIntentSettingChanged() {
- verifyRegisterSettingObserver()
-
// GIVEN no active unlock settings enabled
assertFalse(
activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -144,9 +123,8 @@
)
// WHEN unlock on biometric failed is allowed
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
- 0, 0)).thenReturn(1)
- updateSetting(fakeUnlockIntentUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT))
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -159,21 +137,19 @@
@Test
fun onBioFailSettingChanged() {
- verifyRegisterSettingObserver()
-
// GIVEN no active unlock settings enabled and triggering unlock intent on biometric
// enrollment setting is disabled (empty string is disabled, null would use the default)
- `when`(secureSettings.getStringForUser(
- Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
- 0)).thenReturn("")
- updateSetting(fakeUnlockIntentBioEnroll)
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, "", currentUser)
+ updateSetting(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+ ))
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL))
// WHEN unlock on biometric failed is allowed
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
- 0, 0)).thenReturn(1)
- updateSetting(fakeBioFailUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// THEN active unlock triggers allowed on: biometric failure ONLY
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -186,17 +162,14 @@
@Test
fun faceErrorSettingsChanged() {
- verifyRegisterSettingObserver()
-
// GIVEN unlock on biometric fail
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
- 0, 0)).thenReturn(1)
- updateSetting(fakeBioFailUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// WHEN face error timeout (3), allow trigger active unlock
- `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS,
- 0)).thenReturn("3")
- updateSetting(fakeFaceAcquiredUri)
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_ON_FACE_ERRORS, "3", currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS))
// THEN active unlock triggers allowed on error TIMEOUT
assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError(
@@ -208,19 +181,17 @@
@Test
fun faceAcquiredSettingsChanged() {
- verifyRegisterSettingObserver()
-
// GIVEN unlock on biometric fail
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
- 0, 0)).thenReturn(1)
- updateSetting(fakeBioFailUri)
+ secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, "1", currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger
- `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
- 0)).thenReturn(
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO,
"${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" +
- "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}")
- updateSetting(fakeFaceAcquiredUri)
+ "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}",
+ currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
// THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING
assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
@@ -236,23 +207,23 @@
@Test
fun triggerOnUnlockIntentWhenBiometricEnrolledNone() {
- verifyRegisterSettingObserver()
-
// GIVEN unlock on biometric fail
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
- 0, 0)).thenReturn(1)
- updateSetting(fakeBioFailUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// GIVEN fingerprint and face are NOT enrolled
activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
- `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
`when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false)
// 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.BiometricType.NONE.intValue}")
- updateSetting(fakeUnlockIntentBioEnroll)
+
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ "${ActiveUnlockConfig.BiometricType.NONE.intValue}", currentUser)
+ updateSetting(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+ ))
// THEN active unlock triggers allowed on unlock intent
assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -261,12 +232,9 @@
@Test
fun triggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() {
- verifyRegisterSettingObserver()
-
// GIVEN unlock on biometric fail
- `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
- 0, 0)).thenReturn(1)
- updateSetting(fakeBioFailUri)
+ secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 1, currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
// GIVEN fingerprint and face are both enrolled
activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor
@@ -275,12 +243,14 @@
// WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs
// are enrolled
- `when`(secureSettings.getStringForUser(
- Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
- 0)).thenReturn(
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
"${ActiveUnlockConfig.BiometricType.ANY_FACE.intValue}" +
- "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}")
- updateSetting(fakeUnlockIntentBioEnroll)
+ "|${ActiveUnlockConfig.BiometricType.ANY_FINGERPRINT.intValue}",
+ currentUser)
+ updateSetting(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+ ))
// THEN active unlock triggers NOT allowed on unlock intent
assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin(
@@ -305,13 +275,12 @@
@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)
+ secureSettings.putIntForUser(
+ ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
+ PowerManager.WAKE_REASON_LIFT,
+ currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
// THEN only WAKE_REASON_LIFT is considered an unlock intent
for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -325,17 +294,15 @@
@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(
+ secureSettings.putStringForUser(
+ ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS,
PowerManager.WAKE_REASON_LIFT.toString() +
"|" +
- PowerManager.WAKE_REASON_TAP.toString()
+ PowerManager.WAKE_REASON_TAP.toString(),
+ currentUser
)
- updateSetting(fakeWakeupsConsideredUnlockIntents)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
// THEN WAKE_REASON_LIFT and WAKE_REASON TAP are considered an unlock intent
for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -354,13 +321,10 @@
@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)
+ secureSettings.putStringForUser(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS, " ",
+ currentUser)
+ updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS))
// THEN no wake up gestures are considered an unlock intent
for (wakeReason in 0..WAKE_REASON_BIOMETRIC) {
@@ -373,7 +337,23 @@
PowerManager.WAKE_REASON_UNFOLD_DEVICE))
}
+ @Test
+ fun dump_onUnlockIntentWhenBiometricEnrolled_invalidNum_noArrayOutOfBoundsException() {
+ // GIVEN an invalid input (-1)
+ secureSettings.putStringForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED,
+ "-1", currentUser)
+
+ // WHEN the setting updates
+ updateSetting(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+ ))
+
+ // THEN no exception thrown
+ activeUnlockConfig.dump(mockPrintWriter, emptyArray())
+ }
+
private fun updateSetting(uri: Uri) {
+ verifyRegisterSettingObserver()
settingsObserverCaptor.value.onChange(
false,
listOf(uri),
@@ -383,13 +363,17 @@
}
private fun verifyRegisterSettingObserver() {
- verifyRegisterSettingObserver(fakeWakeUri)
- verifyRegisterSettingObserver(fakeUnlockIntentUri)
- verifyRegisterSettingObserver(fakeBioFailUri)
- verifyRegisterSettingObserver(fakeFaceErrorsUri)
- verifyRegisterSettingObserver(fakeFaceAcquiredUri)
- verifyRegisterSettingObserver(fakeUnlockIntentBioEnroll)
- verifyRegisterSettingObserver(fakeWakeupsConsideredUnlockIntents)
+ verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED
+ ))
+ verifyRegisterSettingObserver(secureSettings.getUriFor(
+ ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
+ ))
}
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/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/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 075ef9d..4a1c1cf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -38,6 +38,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.BiometricSourceType;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -350,7 +351,8 @@
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
verify(mSideFpsController, never()).hide(any());
}
@@ -363,7 +365,7 @@
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
- verify(mSideFpsController, never()).show(any());
+ verify(mSideFpsController, never()).show(any(), anyInt());
}
@Test
@@ -375,7 +377,7 @@
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
- verify(mSideFpsController, never()).show(any());
+ verify(mSideFpsController, never()).show(any(), anyInt());
}
@Test
@@ -387,7 +389,7 @@
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
- verify(mSideFpsController, never()).show(any());
+ verify(mSideFpsController, never()).show(any(), anyInt());
}
@Test
@@ -395,13 +397,14 @@
setupGetSecurityView();
setupConditionsToEnableSideFpsHint();
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
reset(mSideFpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE);
verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
- verify(mSideFpsController, never()).show(any());
+ verify(mSideFpsController, never()).show(any(), anyInt());
}
@Test
@@ -416,13 +419,14 @@
setupGetSecurityView();
setupConditionsToEnableSideFpsHint();
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
reset(mSideFpsController);
mKeyguardSecurityContainerController.onStartingToHide();
verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
- verify(mSideFpsController, never()).show(any());
+ verify(mSideFpsController, never()).show(any(), anyInt());
}
@Test
@@ -430,13 +434,14 @@
setupGetSecurityView();
setupConditionsToEnableSideFpsHint();
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
- verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
reset(mSideFpsController);
mKeyguardSecurityContainerController.onPause();
verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
- verify(mSideFpsController, never()).show(any());
+ verify(mSideFpsController, never()).show(any(), anyInt());
}
@Test
@@ -448,7 +453,8 @@
mKeyguardSecurityContainerController.onResume(0);
- verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
+ verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD);
verify(mSideFpsController, never()).hide(any());
}
@@ -463,7 +469,7 @@
mKeyguardSecurityContainerController.onResume(0);
verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
- verify(mSideFpsController, never()).show(any());
+ verify(mSideFpsController, never()).show(any(), anyInt());
}
@Test
@@ -600,6 +606,14 @@
any(KeyguardSecurityCallback.class));
}
+ @Test
+ public void testReinflateViewFlipper() {
+ mKeyguardSecurityContainerController.reinflateViewFlipper();
+ verify(mKeyguardSecurityViewFlipperController).clearViews();
+ verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
+ any(KeyguardSecurityCallback.class));
+ }
+
private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
mKeyguardSecurityContainerController.onViewAttached();
verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index cd8857a..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));
@@ -770,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);
@@ -1733,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();
@@ -1749,7 +1783,7 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
- occludingAppRequestsFaceAuth();
+ alternateBouncerVisible();
mTestableLooper.processAllMessages();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
@@ -1839,7 +1873,7 @@
}
@Test
- public void testShouldListenForFace_whenUdfpsBouncerIsShowing_returnsTrue()
+ public void testShouldListenForFace_whenAlternateBouncerIsShowing_returnsTrue()
throws RemoteException {
// Preconditions for face auth to run
keyguardNotGoingAway();
@@ -1851,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();
@@ -1867,7 +1901,7 @@
biometricsEnabledForCurrentUser();
userNotCurrentlySwitching();
deviceNotGoingToSleep();
- mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+ alternateBouncerVisible();
mTestableLooper.processAllMessages();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
@@ -1877,6 +1911,10 @@
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
}
+ private void alternateBouncerVisible() {
+ mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+ }
+
@Test
public void testShouldListenForFace_whenFaceIsLockedOut_returnsTrue()
throws RemoteException {
@@ -1887,7 +1925,7 @@
biometricsNotDisabledThroughDevicePolicyManager();
biometricsEnabledForCurrentUser();
userNotCurrentlySwitching();
- mKeyguardUpdateMonitor.setUdfpsBouncerShowing(true);
+ mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
mTestableLooper.processAllMessages();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
@@ -2466,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);
@@ -2520,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/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index b69491e..b7d0059 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -284,4 +284,26 @@
// THEN the lock icon is shown
verify(mLockIconView).setContentDescription(LOCKED_LABEL);
}
+
+ @Test
+ public void lockIconShows_afterUnlockStateChanges() {
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardStateCallback();
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ // and biometric running state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+ reset(mLockIconView);
+
+ // WHEN the unlocked state changes
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the lock icon is shown
+ verify(mLockIconView).setContentDescription(LOCKED_LABEL);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
new file mode 100644
index 0000000..9fcb9c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.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.keyguard
+
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+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.mockito.Mock
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NumPadAnimatorTest : SysuiTestCase() {
+ @Mock lateinit var background: GradientDrawable
+ @Mock lateinit var buttonImage: Drawable
+ private lateinit var underTest: NumPadAnimator
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = NumPadAnimator(context, background, 0, buttonImage)
+ }
+
+ @Test
+ fun testOnLayout() {
+ underTest.onLayout(100)
+ verify(background).cornerRadius = 50f
+ reset(background)
+ underTest.onLayout(100)
+ verify(background, never()).cornerRadius = anyFloat()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
new file mode 100644
index 0000000..777dd4e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.accessibility.fontscaling
+
+import android.os.Handler
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.widget.ImageView
+import android.widget.SeekBar
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.SystemSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Tests for [FontScalingDialog]. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class FontScalingDialogTest : SysuiTestCase() {
+ private lateinit var fontScalingDialog: FontScalingDialog
+ private lateinit var systemSettings: SystemSettings
+ private val fontSizeValueArray: Array<String> =
+ mContext
+ .getResources()
+ .getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
+
+ @Before
+ fun setUp() {
+ val mainHandler = Handler(TestableLooper.get(this).getLooper())
+ systemSettings = FakeSettings()
+ fontScalingDialog = FontScalingDialog(mContext, systemSettings as FakeSettings)
+ }
+
+ @Test
+ fun showTheDialog_seekbarIsShowingCorrectProgress() {
+ fontScalingDialog.show()
+
+ val seekBar: SeekBar = fontScalingDialog.findViewById<SeekBar>(R.id.seekbar)!!
+ val progress: Int = seekBar.getProgress()
+ val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
+
+ assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat())
+
+ fontScalingDialog.dismiss()
+ }
+
+ @Test
+ fun progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScaled() {
+ fontScalingDialog.show()
+
+ val iconEnd: ImageView = fontScalingDialog.findViewById(R.id.icon_end)!!
+ val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
+ fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
+ val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
+
+ seekBarWithIconButtonsView.setProgress(0)
+
+ iconEnd.performClick()
+
+ val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
+ assertThat(seekBar.getProgress()).isEqualTo(1)
+ assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat())
+
+ fontScalingDialog.dismiss()
+ }
+
+ @Test
+ fun progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScaled() {
+ fontScalingDialog.show()
+
+ val iconStart: ImageView = fontScalingDialog.findViewById(R.id.icon_start)!!
+ val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
+ fontScalingDialog.findViewById(R.id.font_scaling_slider)!!
+ val seekBar: SeekBar = fontScalingDialog.findViewById(R.id.seekbar)!!
+
+ seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
+
+ iconStart.performClick()
+
+ val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f)
+ assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
+ assertThat(currentScale)
+ .isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat())
+
+ fontScalingDialog.dismiss()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index ed0cd7e..31d0d12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -19,6 +19,7 @@
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.graphics.Typeface
+import android.graphics.fonts.FontVariationAxis
import android.testing.AndroidTestingRunner
import android.text.Layout
import android.text.StaticLayout
@@ -178,4 +179,71 @@
assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
}
+
+ @Test
+ fun testSetTextStyle_addWeight() {
+ testWeightChange("", 100, FontVariationAxis.fromFontVariationSettings("'wght' 100")!!)
+ }
+
+ @Test
+ fun testSetTextStyle_changeWeight() {
+ testWeightChange(
+ "'wght' 500",
+ 100,
+ FontVariationAxis.fromFontVariationSettings("'wght' 100")!!
+ )
+ }
+
+ @Test
+ fun testSetTextStyle_addWeightWithOtherAxis() {
+ testWeightChange(
+ "'wdth' 100",
+ 100,
+ FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
+ )
+ }
+
+ @Test
+ fun testSetTextStyle_changeWeightWithOtherAxis() {
+ testWeightChange(
+ "'wght' 500, 'wdth' 100",
+ 100,
+ FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
+ )
+ }
+
+ private fun testWeightChange(
+ initialFontVariationSettings: String,
+ weight: Int,
+ expectedFontVariationSettings: Array<FontVariationAxis>
+ ) {
+ val layout = makeLayout("Hello, World", PAINT)
+ val valueAnimator = mock(ValueAnimator::class.java)
+ val textInterpolator = mock(TextInterpolator::class.java)
+ val paint =
+ TextPaint().apply {
+ typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
+ fontVariationSettings = initialFontVariationSettings
+ }
+ `when`(textInterpolator.targetPaint).thenReturn(paint)
+
+ val textAnimator =
+ TextAnimator(layout, {}).apply {
+ this.textInterpolator = textInterpolator
+ this.animator = valueAnimator
+ }
+ textAnimator.setTextStyle(weight = weight, animate = false)
+
+ val resultFontVariationList =
+ FontVariationAxis.fromFontVariationSettings(
+ textInterpolator.targetPaint.fontVariationSettings
+ )
+ expectedFontVariationSettings.forEach { expectedAxis ->
+ val resultAxis = resultFontVariationList?.filter { it.tag == expectedAxis.tag }?.get(0)
+ assertThat(resultAxis).isNotNull()
+ if (resultAxis != null) {
+ assertThat(resultAxis.styleValue).isEqualTo(expectedAxis.styleValue)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 41beada..612e557 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -267,6 +267,17 @@
}
@Test
+ fun testShowOverlayReasonWhenDisplayChanged() = testWithDisplay {
+ sideFpsController.show(SideFpsUiRequestSource.AUTO_SHOW, REASON_AUTH_KEYGUARD)
+ executor.runAllReady()
+ sideFpsController.orientationListener.onDisplayChanged(1 /* displayId */)
+ executor.runAllReady()
+
+ assertThat(sideFpsController.orientationReasonListener.reason)
+ .isEqualTo(REASON_AUTH_KEYGUARD)
+ }
+
+ @Test
fun testShowsAndHides() = testWithDisplay {
overlayController.show(SENSOR_ID, REASON_UNKNOWN)
executor.runAllReady()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index 7177919..fd6e31b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -51,6 +51,8 @@
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import java.util.ArrayList;
+
import javax.inject.Provider;
@SmallTest
@@ -195,6 +197,33 @@
}
@Test
+ public void test_nullClipData_showsNothing() {
+ when(mClipboardManager.getPrimaryClip()).thenReturn(null);
+
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
+
+ verifyZeroInteractions(mUiEventLogger);
+ verifyZeroInteractions(mClipboardToast);
+ verifyZeroInteractions(mOverlayControllerProvider);
+ }
+
+ @Test
+ public void test_emptyClipData_showsToast() {
+ ClipDescription description = new ClipDescription("Test", new String[0]);
+ ClipData noItems = new ClipData(description, new ArrayList<>());
+ when(mClipboardManager.getPrimaryClip()).thenReturn(noItems);
+
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
+
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
+ verify(mClipboardToast, times(1)).showCopiedToast();
+ verifyZeroInteractions(mOverlayControllerProvider);
+ }
+
+ @Test
public void test_minimizedLayoutFlagOff_usesLegacy() {
mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
index faef35e..c0dada4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
@@ -53,25 +53,15 @@
}
@Test
- fun test_nullClipData() {
- val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, null, "test source")
- assertNull(model.clipData)
- assertEquals("test source", model.source)
- assertEquals(ClipboardModel.Type.OTHER, model.type)
- assertNull(model.item)
- assertFalse(model.isSensitive)
- assertFalse(model.isRemote)
- assertNull(model.loadThumbnail(mContext))
- }
-
- @Test
fun test_textClipData() {
val source = "test source"
val model = ClipboardModel.fromClipData(mContext, mClipboardUtils, mSampleClipData, source)
assertEquals(mSampleClipData, model.clipData)
assertEquals(source, model.source)
assertEquals(ClipboardModel.Type.TEXT, model.type)
- assertEquals(mSampleClipData.getItemAt(0), model.item)
+ assertEquals(mSampleClipData.getItemAt(0).text, model.text)
+ assertEquals(mSampleClipData.getItemAt(0).textLinks, model.textLinks)
+ assertEquals(mSampleClipData.getItemAt(0).uri, model.uri)
assertFalse(model.isSensitive)
assertFalse(model.isRemote)
assertNull(model.loadThumbnail(mContext))
@@ -84,7 +74,7 @@
b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
description.extras = b
val data = ClipData(description, mSampleClipData.getItemAt(0))
- val (_, _, _, _, sensitive) =
+ val (_, _, _, _, _, _, sensitive) =
ClipboardModel.fromClipData(mContext, mClipboardUtils, data, "")
assertTrue(sensitive)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 0ac2667..2099281 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -139,29 +139,35 @@
}
@Test
- public void test_setClipData_nullData_legacy() {
- ClipData clipData = null;
- mOverlayController.setClipDataLegacy(clipData, "");
-
- verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
- verify(mClipboardOverlayView, times(0)).showShareChip();
- verify(mClipboardOverlayView, times(1)).getEnterAnimation();
- }
-
- @Test
public void test_setClipData_invalidImageData_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
ClipData clipData = new ClipData("", new String[]{"image/png"},
new ClipData.Item(Uri.parse("")));
mOverlayController.setClipDataLegacy(clipData, "");
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
- verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_nonImageUri_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
+ ClipData clipData = new ClipData("", new String[]{"resource/png"},
+ new ClipData.Item(Uri.parse("")));
+
+ mOverlayController.setClipDataLegacy(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(1)).showShareChip();
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
}
@Test
public void test_setClipData_textData_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
+
mOverlayController.setClipDataLegacy(mSampleClipData, "");
verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
@@ -171,6 +177,8 @@
@Test
public void test_setClipData_sensitiveTextData_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
+
ClipDescription description = mSampleClipData.getDescription();
PersistableBundle b = new PersistableBundle();
b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
@@ -185,6 +193,7 @@
@Test
public void test_setClipData_repeatedCalls_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
when(mAnimator.isRunning()).thenReturn(true);
mOverlayController.setClipDataLegacy(mSampleClipData, "");
@@ -195,6 +204,7 @@
@Test
public void test_viewCallbacks_onShareTapped_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
mOverlayController.setClipDataLegacy(mSampleClipData, "");
mCallbacks.onShareButtonTapped();
@@ -205,6 +215,7 @@
@Test
public void test_viewCallbacks_onDismissTapped_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
mOverlayController.setClipDataLegacy(mSampleClipData, "");
mCallbacks.onDismissButtonTapped();
@@ -215,6 +226,8 @@
@Test
public void test_multipleDismissals_dismissesOnce_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
+
mCallbacks.onSwipeDismissInitiated(mAnimator);
mCallbacks.onDismissButtonTapped();
mCallbacks.onSwipeDismissInitiated(mAnimator);
@@ -226,6 +239,7 @@
@Test
public void test_remoteCopy_withFlagOn_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
@@ -236,6 +250,7 @@
@Test
public void test_remoteCopy_withFlagOff_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
mOverlayController.setClipDataLegacy(mSampleClipData, "");
@@ -245,6 +260,7 @@
@Test
public void test_nonRemoteCopy_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
@@ -255,6 +271,8 @@
@Test
public void test_logsUseLastClipSource_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
+
mOverlayController.setClipDataLegacy(mSampleClipData, "first.package");
mCallbacks.onDismissButtonTapped();
mOverlayController.setClipDataLegacy(mSampleClipData, "second.package");
@@ -267,6 +285,7 @@
@Test
public void test_logOnClipboardActionsShown_legacy() {
+ mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
ClipData.Item item = mSampleClipData.getItemAt(0);
item.setTextLinks(Mockito.mock(TextLinks.class));
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
@@ -292,16 +311,6 @@
// start of refactored setClipData tests
@Test
- public void test_setClipData_nullData() {
- ClipData clipData = null;
- mOverlayController.setClipData(clipData, "");
-
- verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
- verify(mClipboardOverlayView, times(0)).showShareChip();
- verify(mClipboardOverlayView, times(1)).getEnterAnimation();
- }
-
- @Test
public void test_setClipData_invalidImageData() {
ClipData clipData = new ClipData("", new String[]{"image/png"},
new ClipData.Item(Uri.parse("")));
@@ -309,7 +318,19 @@
mOverlayController.setClipData(clipData, "");
verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
- verify(mClipboardOverlayView, times(0)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).showShareChip();
+ verify(mClipboardOverlayView, times(1)).getEnterAnimation();
+ }
+
+ @Test
+ public void test_setClipData_nonImageUri() {
+ ClipData clipData = new ClipData("", new String[]{"resource/png"},
+ new ClipData.Item(Uri.parse("")));
+
+ mOverlayController.setClipData(clipData, "");
+
+ verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
+ verify(mClipboardOverlayView, times(1)).showShareChip();
verify(mClipboardOverlayView, times(1)).getEnterAnimation();
}
@@ -425,7 +446,7 @@
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
.thenReturn(true);
- when(mClipboardUtils.getAction(any(ClipData.Item.class), anyString()))
+ when(mClipboardUtils.getAction(any(CharSequence.class), any(TextLinks.class), anyString()))
.thenReturn(Optional.of(Mockito.mock(RemoteAction.class)));
when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
new file mode 100644
index 0000000..2ed0346
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.ui.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link SeekBarWithIconButtonsView}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class SeekBarWithIconButtonsViewTest extends SysuiTestCase {
+
+ private ImageView mIconStart;
+ private ImageView mIconEnd;
+ private SeekBar mSeekbar;
+ private SeekBarWithIconButtonsView mIconDiscreteSliderLinearLayout;
+
+ @Before
+ public void setUp() {
+ mIconDiscreteSliderLinearLayout = new SeekBarWithIconButtonsView(mContext);
+ mIconStart = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_start);
+ mIconEnd = mIconDiscreteSliderLinearLayout.findViewById(R.id.icon_end);
+ mSeekbar = mIconDiscreteSliderLinearLayout.findViewById(R.id.seekbar);
+ }
+
+ @Test
+ public void setSeekBarProgressZero_startIconAndFrameDisabled() {
+ mIconDiscreteSliderLinearLayout.setProgress(0);
+
+ assertThat(mIconStart.isEnabled()).isFalse();
+ assertThat(mIconEnd.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void setSeekBarProgressMax_endIconAndFrameDisabled() {
+ mIconDiscreteSliderLinearLayout.setProgress(mSeekbar.getMax());
+
+ assertThat(mIconEnd.isEnabled()).isFalse();
+ assertThat(mIconStart.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void setSeekBarProgressMax_allIconsAndFramesEnabled() {
+ // We are using the default value for the max of seekbar.
+ // Therefore, the max value will be DEFAULT_SEEKBAR_MAX = 6.
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+
+ assertThat(mIconStart.isEnabled()).isTrue();
+ assertThat(mIconEnd.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void clickIconEnd_currentProgressIsOneToMax_reachesMax() {
+ mIconDiscreteSliderLinearLayout.setProgress(mSeekbar.getMax() - 1);
+ mIconEnd.performClick();
+
+ assertThat(mSeekbar.getProgress()).isEqualTo(mSeekbar.getMax());
+ }
+
+ @Test
+ public void clickIconStart_currentProgressIsOne_reachesZero() {
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+ mIconStart.performClick();
+
+ assertThat(mSeekbar.getProgress()).isEqualTo(0);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
index 9f4a7c8..b3329eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationTypesUpdaterTest.java
@@ -32,7 +32,9 @@
import com.android.settingslib.dream.DreamBackend;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.condition.SelfExecutingMonitor;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Monitor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -66,13 +68,16 @@
private ComplicationTypesUpdater mController;
+ private Monitor mMonitor;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mDreamBackend.getEnabledComplications()).thenReturn(new HashSet<>());
+ mMonitor = SelfExecutingMonitor.createInstance();
mController = new ComplicationTypesUpdater(mDreamBackend, mExecutor,
- mSecureSettings, mDreamOverlayStateController);
+ mSecureSettings, mDreamOverlayStateController, mMonitor);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
index ec448f9..f6662d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamClockTimeComplicationTest.java
@@ -29,7 +29,9 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.condition.SelfExecutingMonitor;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.shared.condition.Monitor;
import org.junit.Before;
import org.junit.Test;
@@ -69,10 +71,13 @@
@Mock
private ComplicationLayoutParams mLayoutParams;
+ private Monitor mMonitor;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(mDreamClockTimeViewHolderProvider.get()).thenReturn(mDreamClockTimeViewHolder);
+ mMonitor = SelfExecutingMonitor.createInstance();
}
/**
@@ -83,7 +88,8 @@
final DreamClockTimeComplication.Registrant registrant =
new DreamClockTimeComplication.Registrant(
mDreamOverlayStateController,
- mComplication);
+ mComplication,
+ mMonitor);
registrant.start();
verify(mDreamOverlayStateController).addComplication(eq(mComplication));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
index a4cf15c..3312c43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java
@@ -37,7 +37,8 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.common.ui.view.LaunchableImageView;
+import com.android.systemui.animation.view.LaunchableImageView;
+import com.android.systemui.condition.SelfExecutingMonitor;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
import com.android.systemui.controls.controller.StructureInfo;
@@ -46,6 +47,7 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.dagger.DreamHomeControlsComplicationComponent;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.shared.condition.Monitor;
import org.junit.Before;
import org.junit.Test;
@@ -101,6 +103,8 @@
@Captor
private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor;
+ private Monitor mMonitor;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -112,6 +116,8 @@
Optional.of(mControlsListingController));
when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE);
when(mView.findViewById(R.id.home_controls_chip)).thenReturn(mHomeControlsView);
+
+ mMonitor = SelfExecutingMonitor.createInstance();
}
@Test
@@ -126,7 +132,7 @@
public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setHaveFavorites(false);
@@ -139,7 +145,7 @@
public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setHaveFavorites(false);
@@ -152,7 +158,7 @@
public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setHaveFavorites(false);
@@ -165,7 +171,7 @@
public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setHaveFavorites(true);
@@ -178,7 +184,7 @@
public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setHaveFavorites(true);
@@ -191,7 +197,7 @@
public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setServiceAvailable(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
index c8b2b25..ef62abf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/SmartSpaceComplicationTest.java
@@ -30,9 +30,12 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.condition.SelfExecutingMonitor;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
+import com.android.systemui.shared.condition.Condition;
+import com.android.systemui.shared.condition.Monitor;
import org.junit.Before;
import org.junit.Test;
@@ -43,6 +46,8 @@
import org.mockito.MockitoAnnotations;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -60,9 +65,14 @@
@Mock
private View mBcSmartspaceView;
+ private Monitor mMonitor;
+
+ private final Set<Condition> mPreconditions = new HashSet<>();
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mMonitor = SelfExecutingMonitor.createInstance();
}
/**
@@ -79,7 +89,8 @@
return new SmartSpaceComplication.Registrant(
mDreamOverlayStateController,
mComplication,
- mSmartspaceController);
+ mSmartspaceController,
+ mMonitor);
}
@Test
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..5bb8367 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
@@ -18,47 +18,58 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.app.StatusBarManager
+import android.app.admin.DevicePolicyManager
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
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
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
@Mock private lateinit var context: Context
@Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: CameraQuickAffordanceConfig
+ private lateinit var testScope: TestScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- setLaunchable(true)
+ setLaunchable()
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
underTest =
CameraQuickAffordanceConfig(
context,
packageManager,
- ) {
- cameraGestureHelper
- }
+ { cameraGestureHelper },
+ userTracker,
+ devicePolicyManager,
+ testDispatcher,
+ )
}
@Test
@@ -73,23 +84,57 @@
}
@Test
- fun `getPickerScreenState - default when launchable`() = runTest {
- setLaunchable(true)
+ fun `getPickerScreenState - default when launchable`() =
+ testScope.runTest {
+ setLaunchable(true)
- Truth.assertThat(underTest.getPickerScreenState())
- .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
- }
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ }
@Test
- fun `getPickerScreenState - unavailable when not launchable`() = runTest {
- setLaunchable(false)
+ fun `getPickerScreenState - unavailable when camera app not installed`() =
+ testScope.runTest {
+ setLaunchable(isCameraAppInstalled = false)
- Truth.assertThat(underTest.getPickerScreenState())
- .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
- }
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
- private fun setLaunchable(isLaunchable: Boolean) {
+ @Test
+ fun `getPickerScreenState - unavailable when camera disabled by admin`() =
+ testScope.runTest {
+ setLaunchable(isCameraDisabledByDeviceAdmin = true)
+
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable when secure camera disabled by admin`() =
+ testScope.runTest {
+ setLaunchable(isSecureCameraDisabledByDeviceAdmin = true)
+
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ private fun setLaunchable(
+ isCameraAppInstalled: Boolean = true,
+ isCameraDisabledByDeviceAdmin: Boolean = false,
+ isSecureCameraDisabledByDeviceAdmin: Boolean = false,
+ ) {
whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY))
- .thenReturn(isLaunchable)
+ .thenReturn(isCameraAppInstalled)
+ whenever(devicePolicyManager.getCameraDisabled(null, userTracker.userId))
+ .thenReturn(isCameraDisabledByDeviceAdmin)
+ whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
+ .thenReturn(
+ if (isSecureCameraDisabledByDeviceAdmin) {
+ DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA
+ } else {
+ 0
+ }
+ )
}
}
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..f1b9c5f 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,22 +17,26 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.app.admin.DevicePolicyManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.SysuiTestCase
import com.android.systemui.camera.CameraIntentsWrapper
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -40,63 +44,98 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
@Mock private lateinit var activityIntentHelper: ActivityIntentHelper
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
private lateinit var underTest: VideoCameraQuickAffordanceConfig
+ private lateinit var userTracker: UserTracker
+ private lateinit var testScope: TestScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ val testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ userTracker = FakeUserTracker()
underTest =
VideoCameraQuickAffordanceConfig(
context = context,
cameraIntents = CameraIntentsWrapper(context),
activityIntentHelper = activityIntentHelper,
- userTracker = FakeUserTracker(),
+ userTracker = userTracker,
+ devicePolicyManager = devicePolicyManager,
+ backgroundDispatcher = testDispatcher,
)
}
@Test
- fun `lockScreenState - visible when launchable`() = runTest {
- setLaunchable(true)
+ fun `lockScreenState - visible when launchable`() =
+ testScope.runTest {
+ setLaunchable()
- val lockScreenState = collectLastValue(underTest.lockScreenState)
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
- assertThat(lockScreenState())
- .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
- }
+ assertThat(lockScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
+ }
@Test
- fun `lockScreenState - hidden when not launchable`() = runTest {
- setLaunchable(false)
+ fun `lockScreenState - hidden when app not installed on device`() =
+ testScope.runTest {
+ setLaunchable(isVideoCameraAppInstalled = false)
- val lockScreenState = collectLastValue(underTest.lockScreenState)
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
- assertThat(lockScreenState())
- .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
- }
+ assertThat(lockScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
@Test
- fun `getPickerScreenState - default when launchable`() = runTest {
- setLaunchable(true)
+ fun `lockScreenState - hidden when camera disabled by admin`() =
+ testScope.runTest {
+ setLaunchable(isCameraDisabledByAdmin = true)
- assertThat(underTest.getPickerScreenState())
- .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
- }
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+ assertThat(lockScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
@Test
- fun `getPickerScreenState - unavailable when not launchable`() = runTest {
- setLaunchable(false)
+ fun `getPickerScreenState - default when launchable`() =
+ testScope.runTest {
+ setLaunchable()
- assertThat(underTest.getPickerScreenState())
- .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
- }
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ }
- private fun setLaunchable(isLaunchable: Boolean) {
+ @Test
+ fun `getPickerScreenState - unavailable when app not installed on device`() =
+ testScope.runTest {
+ setLaunchable(isVideoCameraAppInstalled = false)
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable when camera disabled by admin`() =
+ testScope.runTest {
+ setLaunchable(isCameraDisabledByAdmin = true)
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ private fun setLaunchable(
+ isVideoCameraAppInstalled: Boolean = true,
+ isCameraDisabledByAdmin: Boolean = false,
+ ) {
whenever(
activityIntentHelper.getTargetActivityInfo(
any(),
@@ -105,11 +144,13 @@
)
)
.thenReturn(
- if (isLaunchable) {
+ if (isVideoCameraAppInstalled) {
mock()
} else {
null
}
)
+ whenever(devicePolicyManager.getCameraDisabled(null, userTracker.userId))
+ .thenReturn(isCameraDisabledByAdmin)
}
}
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 5c7d242..a07a714 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
@@ -126,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
@@ -187,6 +189,7 @@
mediaFlags = mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
+ keyguardUpdateMonitor = keyguardUpdateMonitor
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -242,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
@@ -682,6 +686,19 @@
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
@@ -810,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 =
@@ -1862,6 +1897,20 @@
.onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
+ @Test
+ fun testSessionDestroyed_noNotificationKey_stillRemoved() {
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+
+ // When a notiifcation is added and then removed before it is fully processed
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+ backgroundExecutor.runAllReady()
+ mediaDataManager.onNotificationRemoved(KEY)
+
+ // We still make sure to remove it
+ verify(listener).onMediaDataRemoved(eq(KEY))
+ }
+
/** Helper function to add a media notification and capture the resulting MediaData */
private fun addNotificationAndLoad() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
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..a72634b 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,8 +57,10 @@
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
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -58,6 +68,8 @@
import org.mockito.Mock
import org.mockito.Mockito.floatThat
import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -90,11 +102,14 @@
@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)
@@ -131,7 +150,6 @@
MediaPlayerData.clear()
}
- @Ignore("b/253229241")
@Test
fun testPlayerOrdering() {
// Test values: key, data, last active time
@@ -308,7 +326,6 @@
}
}
- @Ignore("b/253229241")
@Test
fun testOrderWithSmartspace_prioritized() {
testPlayerOrdering()
@@ -316,7 +333,7 @@
// If smartspace is prioritized
MediaPlayerData.addMediaRecommendation(
SMARTSPACE_KEY,
- EMPTY_SMARTSPACE_MEDIA_DATA,
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
panel,
true,
clock
@@ -326,7 +343,6 @@
assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
}
- @Ignore("b/253229241")
@Test
fun testOrderWithSmartspace_prioritized_updatingVisibleMediaPlayers() {
testPlayerOrdering()
@@ -343,7 +359,6 @@
assertTrue(MediaPlayerData.visiblePlayerKeys().elementAt(2).isSsMediaRec)
}
- @Ignore("b/253229241")
@Test
fun testOrderWithSmartspace_notPrioritized() {
testPlayerOrdering()
@@ -351,7 +366,7 @@
// If smartspace is not prioritized
MediaPlayerData.addMediaRecommendation(
SMARTSPACE_KEY,
- EMPTY_SMARTSPACE_MEDIA_DATA,
+ EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
panel,
false,
clock
@@ -362,7 +377,6 @@
assertTrue(MediaPlayerData.playerKeys().elementAt(idx).isSsMediaRec)
}
- @Ignore("b/253229241")
@Test
fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
testPlayerOrdering()
@@ -400,7 +414,6 @@
)
}
- @Ignore("b/253229241")
@Test
fun testSwipeDismiss_logged() {
mediaCarouselController.mediaCarouselScrollHandler.dismissCallback.invoke()
@@ -408,7 +421,6 @@
verify(logger).logSwipeDismiss()
}
- @Ignore("b/253229241")
@Test
fun testSettingsButton_logged() {
mediaCarouselController.settingsButton.callOnClick()
@@ -416,18 +428,16 @@
verify(logger).logCarouselSettings()
}
- @Ignore("b/253229241")
@Test
fun testLocationChangeQs_logged() {
mediaCarouselController.onDesiredLocationChanged(
- MediaHierarchyManager.LOCATION_QS,
+ LOCATION_QS,
mediaHostState,
animate = false
)
- verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
+ verify(logger).logCarouselPosition(LOCATION_QS)
}
- @Ignore("b/253229241")
@Test
fun testLocationChangeQqs_logged() {
mediaCarouselController.onDesiredLocationChanged(
@@ -438,7 +448,6 @@
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
}
- @Ignore("b/253229241")
@Test
fun testLocationChangeLockscreen_logged() {
mediaCarouselController.onDesiredLocationChanged(
@@ -449,7 +458,6 @@
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
}
- @Ignore("b/253229241")
@Test
fun testLocationChangeDream_logged() {
mediaCarouselController.onDesiredLocationChanged(
@@ -460,7 +468,6 @@
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
}
- @Ignore("b/253229241")
@Test
fun testRecommendationRemoved_logged() {
val packageName = "smartspace package"
@@ -474,7 +481,6 @@
verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
}
- @Ignore("b/253229241")
@Test
fun testMediaLoaded_ScrollToActivePlayer() {
listener.value.onMediaDataLoaded(
@@ -532,7 +538,6 @@
)
}
- @Ignore("b/253229241")
@Test
fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
listener.value.onSmartspaceMediaDataLoaded(
@@ -576,7 +581,6 @@
assertEquals(playerIndex, 0)
}
- @Ignore("b/253229241")
@Test
fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
var result = false
@@ -588,7 +592,6 @@
assertEquals(true, result)
}
- @Ignore("b/253229241")
@Test
fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
var result = false
@@ -602,7 +605,6 @@
assertEquals(true, result)
}
- @Ignore("b/253229241")
@Test
fun testGetCurrentVisibleMediaContentIntent() {
val clickIntent1 = mock(PendingIntent::class.java)
@@ -649,7 +651,6 @@
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
}
- @Ignore("b/253229241")
@Test
fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
val delta = 0.0001F
@@ -671,7 +672,6 @@
verify(pageIndicator).alpha = floatThat { abs(it - 1.0F) < delta }
}
- @Ignore("b/253229241")
@Test
fun testOnConfigChanged_playersAreAddedBack() {
listener.value.onMediaDataLoaded(
@@ -697,7 +697,7 @@
val playersSize = MediaPlayerData.players().size
- configListener.value.onConfigChanged(capture(newConfig))
+ configListener.value.onConfigChanged(Configuration())
assertEquals(playersSize, MediaPlayerData.players().size)
assertEquals(
@@ -740,4 +740,96 @@
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()
+ }
+
+ @Test
+ fun testInvisibleToUserAndExpanded_playersNotListening() {
+ // Add players to carousel.
+ testPlayerOrdering()
+
+ // Make the carousel visible to user in expanded layout.
+ mediaCarouselController.currentlyExpanded = true
+ mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true
+
+ // panel is the player for each MediaPlayerData.
+ // Verify that seekbar listening attribute in media control panel is set to true.
+ verify(panel, times(MediaPlayerData.players().size)).listening = true
+
+ // Make the carousel invisible to user.
+ mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = false
+
+ // panel is the player for each MediaPlayerData.
+ // Verify that seekbar listening attribute in media control panel is set to false.
+ verify(panel, times(MediaPlayerData.players().size)).listening = false
+ }
+
+ @Test
+ fun testVisibleToUserAndExpanded_playersListening() {
+ // Add players to carousel.
+ testPlayerOrdering()
+
+ // Make the carousel visible to user in expanded layout.
+ mediaCarouselController.currentlyExpanded = true
+ mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true
+
+ // panel is the player for each MediaPlayerData.
+ // Verify that seekbar listening attribute in media control panel is set to true.
+ verify(panel, times(MediaPlayerData.players().size)).listening = true
+ }
+
+ @Test
+ fun testUMOCollapsed_playersNotListening() {
+ // Add players to carousel.
+ testPlayerOrdering()
+
+ // Make the carousel in collapsed layout.
+ mediaCarouselController.currentlyExpanded = false
+
+ // panel is the player for each MediaPlayerData.
+ // Verify that seekbar listening attribute in media control panel is set to false.
+ verify(panel, times(MediaPlayerData.players().size)).listening = false
+
+ // Make the carousel visible to user.
+ reset(panel)
+ mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = true
+
+ // Verify that seekbar listening attribute in media control panel is set to false.
+ verify(panel, times(MediaPlayerData.players().size)).listening = false
+ }
}
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/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/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
index bc67df6..5f206b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt
@@ -13,8 +13,9 @@
@RunWith(Parameterized::class)
@SmallTest
-internal class FloatingRotationButtonPositionCalculatorTest(private val testCase: TestCase)
- : SysuiTestCase() {
+internal class FloatingRotationButtonPositionCalculatorTest(
+ private val testCase: TestCase,
+) : SysuiTestCase() {
@Test
fun calculatePosition() {
@@ -34,11 +35,18 @@
val expectedPosition: Position
) {
override fun toString(): String =
- "when calculator = $calculator, " +
- "rotation = $rotation, " +
- "taskbarVisible = $taskbarVisible, " +
- "taskbarStashed = $taskbarStashed - " +
- "expected $expectedPosition"
+ buildString {
+ append("when calculator = ")
+ append(when (calculator) {
+ posLeftCalculator -> "LEFT"
+ posRightCalculator -> "RIGHT"
+ else -> error("Unknown calculator: $calculator")
+ })
+ append(", rotation = $rotation")
+ append(", taskbarVisible = $taskbarVisible")
+ append(", taskbarStashed = $taskbarStashed")
+ append(" - expected $expectedPosition")
+ }
}
companion object {
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/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index 3281fa9..e222542 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.qs.tiles.DndTile
import com.android.systemui.qs.tiles.DreamTile
import com.android.systemui.qs.tiles.FlashlightTile
+import com.android.systemui.qs.tiles.FontScalingTile
import com.android.systemui.qs.tiles.HotspotTile
import com.android.systemui.qs.tiles.InternetTile
import com.android.systemui.qs.tiles.LocationTile
@@ -51,14 +52,15 @@
import com.android.systemui.qs.tiles.WorkModeTile
import com.android.systemui.util.leak.GarbageMonitor
import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
import org.mockito.Mock
import org.mockito.Mockito.inOrder
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
private val specMap = mapOf(
"internet" to InternetTile::class.java,
@@ -87,7 +89,8 @@
"qr_code_scanner" to QRCodeScannerTile::class.java,
"onehanded" to OneHandedModeTile::class.java,
"color_correction" to ColorCorrectionTile::class.java,
- "dream" to DreamTile::class.java
+ "dream" to DreamTile::class.java,
+ "font_scaling" to FontScalingTile::class.java
)
@RunWith(AndroidTestingRunner::class)
@@ -126,6 +129,7 @@
@Mock private lateinit var oneHandedModeTile: OneHandedModeTile
@Mock private lateinit var colorCorrectionTile: ColorCorrectionTile
@Mock private lateinit var dreamTile: DreamTile
+ @Mock private lateinit var fontScalingTile: FontScalingTile
private lateinit var factory: QSFactoryImpl
@@ -137,39 +141,43 @@
whenever(qsHost.userContext).thenReturn(mContext)
whenever(customTileBuilder.build()).thenReturn(customTile)
+ val tileMap = mutableMapOf<String, Provider<QSTileImpl<*>>>(
+ "internet" to Provider { internetTile },
+ "bt" to Provider { bluetoothTile },
+ "dnd" to Provider { dndTile },
+ "inversion" to Provider { colorInversionTile },
+ "airplane" to Provider { airplaneTile },
+ "work" to Provider { workTile },
+ "rotation" to Provider { rotationTile },
+ "flashlight" to Provider { flashlightTile },
+ "location" to Provider { locationTile },
+ "cast" to Provider { castTile },
+ "hotspot" to Provider { hotspotTile },
+ "battery" to Provider { batterySaverTile },
+ "saver" to Provider { dataSaverTile },
+ "night" to Provider { nightDisplayTile },
+ "nfc" to Provider { nfcTile },
+ "dark" to Provider { darkModeTile },
+ "screenrecord" to Provider { screenRecordTile },
+ "reduce_brightness" to Provider { reduceBrightColorsTile },
+ "cameratoggle" to Provider { cameraToggleTile },
+ "mictoggle" to Provider { microphoneToggleTile },
+ "controls" to Provider { deviceControlsTile },
+ "alarm" to Provider { alarmTile },
+ "wallet" to Provider { quickAccessWalletTile },
+ "qr_code_scanner" to Provider { qrCodeScannerTile },
+ "onehanded" to Provider { oneHandedModeTile },
+ "color_correction" to Provider { colorCorrectionTile },
+ "dream" to Provider { dreamTile },
+ "font_scaling" to Provider { fontScalingTile }
+ )
+
factory = QSFactoryImpl(
{ qsHost },
{ customTileBuilder },
- { internetTile },
- { bluetoothTile },
- { dndTile },
- { colorInversionTile },
- { airplaneTile },
- { workTile },
- { rotationTile },
- { flashlightTile },
- { locationTile },
- { castTile },
- { hotspotTile },
- { batterySaverTile },
- { dataSaverTile },
- { nightDisplayTile },
- { nfcTile },
- { memoryTile },
- { darkModeTile },
- { screenRecordTile },
- { reduceBrightColorsTile },
- { cameraToggleTile },
- { microphoneToggleTile },
- { deviceControlsTile },
- { alarmTile },
- { quickAccessWalletTile },
- { qrCodeScannerTile },
- { oneHandedModeTile },
- { colorCorrectionTile },
- { dreamTile }
+ tileMap,
)
- // When adding/removing tiles, fix also [specMap]
+ // When adding/removing tiles, fix also [specMap] and [tileMap]
}
@Test
@@ -205,4 +213,4 @@
inOrder.verify(tile).initialize()
inOrder.verify(tile).postStale()
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/FontScalingTileTest.kt
new file mode 100644
index 0000000..57abae0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/FontScalingTileTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.qs.tiles.dialog
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tiles.FontScalingTile
+import com.android.systemui.util.settings.FakeSettings
+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.`when`
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class FontScalingTileTest : SysuiTestCase() {
+ @Mock private lateinit var qsHost: QSTileHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var fontScalingTile: FontScalingTile
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ `when`(qsHost.getContext()).thenReturn(mContext)
+ fontScalingTile =
+ FontScalingTile(
+ qsHost,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ FalsingManagerFake(),
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ dialogLaunchAnimator,
+ FakeSettings()
+ )
+ fontScalingTile.initialize()
+ }
+
+ @Test
+ fun isNotAvailable_whenNotSupportedDevice_returnsFalse() {
+ val isAvailable = fontScalingTile.isAvailable()
+
+ assertThat(isAvailable).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index ea0e454..9acd47e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -16,14 +16,13 @@
package com.android.systemui.reardisplay;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import android.hardware.devicestate.DeviceStateManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.View;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
@@ -37,8 +36,6 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import java.util.concurrent.Executor;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -63,9 +60,11 @@
controller.showRearDisplayDialog(CLOSED_BASE_STATE);
assertTrue(controller.mRearDisplayEducationDialog.isShowing());
- View deviceOpenedWarningTextView = controller.mRearDisplayEducationDialog.findViewById(
- R.id.rear_display_warning_text_view);
- assertNull(deviceOpenedWarningTextView);
+ TextView deviceClosedTitleTextView = controller.mRearDisplayEducationDialog.findViewById(
+ R.id.rear_display_title_text_view);
+ assertEquals(deviceClosedTitleTextView.getText().toString(),
+ getContext().getResources().getString(
+ R.string.rear_display_folded_bottom_sheet_title));
}
@Test
@@ -79,9 +78,11 @@
controller.showRearDisplayDialog(OPEN_BASE_STATE);
assertTrue(controller.mRearDisplayEducationDialog.isShowing());
- View deviceOpenedWarningTextView = controller.mRearDisplayEducationDialog.findViewById(
- R.id.rear_display_warning_text_view);
- assertNotNull(deviceOpenedWarningTextView);
+ TextView deviceClosedTitleTextView = controller.mRearDisplayEducationDialog.findViewById(
+ R.id.rear_display_title_text_view);
+ assertEquals(deviceClosedTitleTextView.getText().toString(),
+ getContext().getResources().getString(
+ R.string.rear_display_unfolded_bottom_sheet_title));
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 1fa2ace..c40c287 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -38,7 +38,7 @@
import com.android.internal.util.ScreenshotRequest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.SCREENSHOT_METADATA
+import com.android.systemui.flags.Flags.SCREENSHOT_METADATA_REFACTOR
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER
@@ -126,7 +126,7 @@
// Flipped in selected test cases
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
- flags.set(SCREENSHOT_METADATA, false)
+ flags.set(SCREENSHOT_METADATA_REFACTOR, false)
service.attach(
mContext,
@@ -183,7 +183,7 @@
@Test
fun takeScreenshotFullscreen_screenshotDataEnabled() {
- flags.set(SCREENSHOT_METADATA, true)
+ flags.set(SCREENSHOT_METADATA_REFACTOR, true)
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
@@ -260,7 +260,7 @@
@Test
fun takeScreenshotFullscreen_userLocked() {
- flags.set(SCREENSHOT_METADATA, true)
+ flags.set(SCREENSHOT_METADATA_REFACTOR, true)
whenever(userManager.isUserUnlocked).thenReturn(false)
@@ -302,7 +302,7 @@
@Test
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
- flags.set(SCREENSHOT_METADATA, true)
+ flags.set(SCREENSHOT_METADATA_REFACTOR, true)
whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
.thenReturn(true)
@@ -353,7 +353,7 @@
@Test
fun takeScreenshotFullscreen_userLocked_metadataDisabled() {
- flags.set(SCREENSHOT_METADATA, false)
+ flags.set(SCREENSHOT_METADATA_REFACTOR, false)
whenever(userManager.isUserUnlocked).thenReturn(false)
val request =
@@ -394,7 +394,7 @@
@Test
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers_metadataDisabled() {
- flags.set(SCREENSHOT_METADATA, false)
+ flags.set(SCREENSHOT_METADATA_REFACTOR, false)
whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
.thenReturn(true)
@@ -445,7 +445,7 @@
@Test
fun takeScreenshot_workProfile_nullBitmap_metadataDisabled() {
- flags.set(SCREENSHOT_METADATA, false)
+ flags.set(SCREENSHOT_METADATA_REFACTOR, false)
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
@@ -487,7 +487,7 @@
}
@Test
fun takeScreenshot_workProfile_nullBitmap() {
- flags.set(SCREENSHOT_METADATA, true)
+ flags.set(SCREENSHOT_METADATA_REFACTOR, true)
val request =
ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER)
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..aa1636d 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
@@ -249,6 +249,21 @@
}
@Test
+ public void addCallback_preCondition_noConditions_reportAllConditionsMet() {
+ final Monitor
+ monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(mCondition1)));
+ final Monitor.Callback callback = mock(
+ Monitor.Callback.class);
+
+ monitor.addSubscription(new Monitor.Subscription.Builder(callback).build());
+ mExecutor.runAllReady();
+ verify(callback, never()).onConditionsChanged(true);
+ mCondition1.fakeUpdateCondition(true);
+ mExecutor.runAllReady();
+ verify(callback).onConditionsChanged(true);
+ }
+
+ @Test
public void removeCallback_noFailureOnDoubleRemove() {
final Condition condition = mock(
Condition.class);
@@ -471,4 +486,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 f9f2c45..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,33 @@
}
@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();
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 9c69a6a..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
@@ -34,6 +34,7 @@
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
@@ -129,6 +130,9 @@
private lateinit var configPlugin: BcSmartspaceConfigPlugin
@Mock
+ private lateinit var dumpManager: DumpManager
+
+ @Mock
private lateinit var controllerListener: SmartspaceTargetListener
@Captor
@@ -228,6 +232,7 @@
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..8109e24 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
@@ -23,6 +23,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.advanceTimeBy
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
@@ -38,6 +39,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 +66,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 +94,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 +118,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 +175,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 +199,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 +225,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 +278,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()
@@ -270,16 +312,19 @@
fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
- // GIVEN: Keyguard is showing, unseen notification is present
+ // GIVEN: Keyguard is showing, not dozing, unseen notification is present
keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setDozing(false)
runKeyguardCoordinatorTest {
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
- // WHEN: Keyguard is no longer showing for 5 seconds
- keyguardRepository.setKeyguardShowing(false)
+ // WHEN: five seconds have passed
+ testScheduler.advanceTimeBy(5.seconds)
testScheduler.runCurrent()
- testScheduler.advanceTimeBy(5.seconds.inWholeMilliseconds)
+
+ // WHEN: Keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
testScheduler.runCurrent()
// WHEN: Keyguard is shown again
@@ -292,7 +337,7 @@
}
@Test
- fun unseenNotificationIsNotMarkedAsSeenIfTimeThresholdNotMet() {
+ fun unseenNotificationIsNotMarkedAsSeenIfShadeNotExpanded() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
// GIVEN: Keyguard is showing, unseen notification is present
@@ -301,10 +346,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 +370,7 @@
val keyguardCoordinator =
KeyguardCoordinator(
testDispatcher,
+ headsUpManager,
keyguardNotifVisibilityProvider,
keyguardRepository,
notifPipelineFlags,
@@ -364,12 +408,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/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 03af527..fbec95b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -741,7 +741,7 @@
}
@Test
- public void testShouldFullScreen_snoozed_occluding_withStrictRules() throws Exception {
+ public void testShouldNotFullScreen_snoozed_occluding_withStrictRules() throws Exception {
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -753,16 +753,41 @@
when(mKeyguardStateController.isOccluded()).thenReturn(true);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
- .isEqualTo(FullScreenIntentDecision.FSI_KEYGUARD_OCCLUDED);
+ .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
- .isTrue();
- verify(mLogger, never()).logNoFullscreen(any(), any());
+ .isFalse();
+ verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
- verify(mLogger).logFullscreen(entry, "Expected not to HUN while keyguard occluded");
+ verify(mLogger, never()).logFullscreen(any(), any());
}
@Test
- public void testShouldFullScreen_snoozed_lockedShade_withStrictRules() throws Exception {
+ public void testShouldHeadsUp_snoozed_occluding_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger).logHeadsUpPackageSnoozeBypassedHasFsi(entry);
+ verify(mLogger, never()).logHeadsUp(any());
+
+ assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+ UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+ assertThat(fakeUiEvent.eventId).isEqualTo(
+ NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI.getId());
+ assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+ assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
+ }
+
+ @Test
+ public void testShouldNotFullScreen_snoozed_lockedShade_withStrictRules() throws Exception {
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -774,12 +799,37 @@
when(mKeyguardStateController.isOccluded()).thenReturn(false);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
- .isEqualTo(FullScreenIntentDecision.FSI_LOCKED_SHADE);
+ .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
- .isTrue();
- verify(mLogger, never()).logNoFullscreen(any(), any());
+ .isFalse();
+ verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
verify(mLogger, never()).logNoFullscreenWarning(any(), any());
- verify(mLogger).logFullscreen(entry, "Keyguard is showing and not occluded");
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldHeadsUp_snoozed_lockedShade_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger).logHeadsUpPackageSnoozeBypassedHasFsi(entry);
+ verify(mLogger, never()).logHeadsUp(any());
+
+ assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+ UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+ assertThat(fakeUiEvent.eventId).isEqualTo(
+ NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI.getId());
+ assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+ assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
}
@Test
@@ -795,21 +845,41 @@
when(mKeyguardStateController.isOccluded()).thenReturn(false);
assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
- .isEqualTo(FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD);
+ .isEqualTo(FullScreenIntentDecision.NO_FSI_EXPECTED_TO_HUN);
assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
.isFalse();
- verify(mLogger, never()).logNoFullscreen(any(), any());
- verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+ verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldHeadsUp_snoozed_unlocked_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger).logHeadsUpPackageSnoozeBypassedHasFsi(entry);
+ verify(mLogger, never()).logHeadsUp(any());
assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
assertThat(fakeUiEvent.eventId).isEqualTo(
- NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD.getId());
+ NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI.getId());
assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
}
+ /* TODO: Verify the FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD UiEvent some other way. */
+
/**
* Bubbles can happen.
*/
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/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/phone/SystemUIDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 6c83e9f..14a925a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -19,8 +19,10 @@
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.Intent;
@@ -32,6 +34,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -46,12 +50,15 @@
public class SystemUIDialogTest extends SysuiTestCase {
@Mock
+ private FeatureFlags mFeatureFlags;
+ @Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher);
}
@@ -86,4 +93,20 @@
verify(mBroadcastDispatcher, never()).unregisterReceiver(any());
assertFalse(dialog.isShowing());
}
+
+ @Test
+ public void usePredictiveBackAnimFlag() {
+ when(mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM))
+ .thenReturn(true);
+ final SystemUIDialog dialog = new SystemUIDialog(mContext);
+
+ dialog.show();
+
+ assertTrue(dialog.isShowing());
+ verify(mFeatureFlags, atLeast(1))
+ .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM);
+
+ dialog.dismiss();
+ assertFalse(dialog.isShowing());
+ }
}
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..f0f213b 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
@@ -25,8 +26,9 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
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.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+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..cd4d847 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.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
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 314e250..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
@@ -61,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
@@ -69,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
@@ -85,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")
@@ -101,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)
@@ -118,9 +119,8 @@
DEFAULT_NAME,
SEP,
telephonyManager,
- globalSettings,
+ systemUiCarrierConfig,
fakeBroadcastDispatcher,
- connectionsRepo.globalMobileDataSettingChangedEvent,
mobileMappings,
IMMEDIATE,
logger,
@@ -400,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()
@@ -466,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>()
@@ -487,7 +459,6 @@
assertThat(latest).isTrue()
- telephonyJob.cancel()
job.cancel()
}
@@ -584,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)
@@ -601,7 +607,7 @@
}
@Test
- fun `network name - operatorAlphaShort - tracked`() =
+ fun `operatorAlphaShort - tracked`() =
runBlocking(IMMEDIATE) {
var latest: String? = null
@@ -673,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 */
@@ -716,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..8090205 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,23 +39,25 @@
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
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
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)
@@ -767,21 +896,31 @@
// Subscription 1
private const val SUB_1_ID = 1
+ private val GROUP_1 = ParcelUuid(UUID.randomUUID())
private val SUB_1 =
mock<SubscriptionInfo>().also {
whenever(it.subscriptionId).thenReturn(SUB_1_ID)
- whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID()))
+ whenever(it.groupUuid).thenReturn(GROUP_1)
}
- private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
+ private val MODEL_1 =
+ SubscriptionModel(
+ subscriptionId = SUB_1_ID,
+ groupUuid = GROUP_1,
+ )
// Subscription 2
private const val SUB_2_ID = 2
+ private val GROUP_2 = ParcelUuid(UUID.randomUUID())
private val SUB_2 =
mock<SubscriptionInfo>().also {
whenever(it.subscriptionId).thenReturn(SUB_2_ID)
- whenever(it.groupUuid).thenReturn(ParcelUuid(UUID.randomUUID()))
+ whenever(it.groupUuid).thenReturn(GROUP_2)
}
- private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
+ private val MODEL_2 =
+ SubscriptionModel(
+ subscriptionId = SUB_2_ID,
+ groupUuid = GROUP_2,
+ )
// Subs 3 and 4 are considered to be in the same group ------------------------------------
private val GROUP_ID_3_4 = ParcelUuid(UUID.randomUUID())
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..bbca001 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+import android.os.ParcelUuid
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.settingslib.mobile.MobileMappings
@@ -27,11 +28,14 @@
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
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import java.util.UUID
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -50,6 +54,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 +68,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ connectivityRepository = FakeConnectivityRepository()
+
connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
connectionsRepository.setMobileConnectionRepositoryMap(
mapOf(
@@ -79,6 +86,8 @@
connectionsRepository,
carrierConfigTracker,
logger = mock(),
+ tableLogger = mock(),
+ connectivityRepository,
userSetupRepository,
testScope.backgroundScope,
)
@@ -97,6 +106,21 @@
job.cancel()
}
+ // Based on the logic from the old pipeline, we'll never filter subs when there are more than 2
+ @Test
+ fun filteredSubscriptions_moreThanTwo_doesNotFilter() =
+ testScope.runTest {
+ connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
+
+ job.cancel()
+ }
+
@Test
fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
testScope.runTest {
@@ -111,10 +135,50 @@
}
@Test
- fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
+ fun filteredSubscriptions_opportunistic_differentGroups_doesNotFilter() =
testScope.runTest {
connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_opportunistic_nonGrouped_doesNotFilter() =
+ testScope.runTest {
+ val (sub1, sub2) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_2_ID),
+ opportunistic = Pair(true, true),
+ grouped = false,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub1, sub2))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+
+ var latest: List<SubscriptionModel>? = null
+ val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(listOf(sub1, sub2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_3() =
+ testScope.runTest {
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub3, sub4))
+ connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
@@ -122,15 +186,21 @@
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the active one when the config is false
- assertThat(latest).isEqualTo(listOf(SUB_3_OPP))
+ assertThat(latest).isEqualTo(listOf(sub3))
job.cancel()
}
@Test
- fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
+ fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_4() =
testScope.runTest {
- connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+ val (sub3, sub4) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
+ opportunistic = Pair(true, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub3, sub4))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(false)
@@ -139,15 +209,21 @@
val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
// Filtered subscriptions should show the active one when the config is false
- assertThat(latest).isEqualTo(listOf(SUB_4_OPP))
+ assertThat(latest).isEqualTo(listOf(sub4))
job.cancel()
}
@Test
- fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
+ fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_active_1() =
testScope.runTest {
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(false, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub1, sub3))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
@@ -157,15 +233,21 @@
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
// true
- assertThat(latest).isEqualTo(listOf(SUB_1))
+ assertThat(latest).isEqualTo(listOf(sub1))
job.cancel()
}
@Test
- fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
+ fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_nonActive_1() =
testScope.runTest {
- connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+ val (sub1, sub3) =
+ createSubscriptionPair(
+ subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+ opportunistic = Pair(false, true),
+ grouped = true,
+ )
+ connectionsRepository.setSubscriptions(listOf(sub1, sub3))
connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
.thenReturn(true)
@@ -175,7 +257,7 @@
// Filtered subscriptions should show the primary (non-opportunistic) if the config is
// true
- assertThat(latest).isEqualTo(listOf(SUB_1))
+ assertThat(latest).isEqualTo(listOf(sub1))
job.cancel()
}
@@ -609,6 +691,59 @@
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()
+ }
+
+ /**
+ * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions
+ * flow.
+ */
+ private fun createSubscriptionPair(
+ subscriptionIds: Pair<Int, Int>,
+ opportunistic: Pair<Boolean, Boolean> = Pair(false, false),
+ grouped: Boolean = false,
+ ): Pair<SubscriptionModel, SubscriptionModel> {
+ val groupUuid = if (grouped) ParcelUuid(UUID.randomUUID()) else null
+ val sub1 =
+ SubscriptionModel(
+ subscriptionId = subscriptionIds.first,
+ isOpportunistic = opportunistic.first,
+ groupUuid = groupUuid,
+ )
+
+ val sub2 =
+ SubscriptionModel(
+ subscriptionId = subscriptionIds.second,
+ isOpportunistic = opportunistic.second,
+ groupUuid = groupUuid,
+ )
+
+ return Pair(sub1, sub2)
+ }
+
companion object {
private val tableLogBuffer =
TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
@@ -622,11 +757,21 @@
private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, tableLogBuffer)
private const val SUB_3_ID = 3
- private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true)
+ private val SUB_3_OPP =
+ SubscriptionModel(
+ subscriptionId = SUB_3_ID,
+ isOpportunistic = true,
+ groupUuid = ParcelUuid(UUID.randomUUID()),
+ )
private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, tableLogBuffer)
private const val SUB_4_ID = 4
- private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true)
+ private val SUB_4_OPP =
+ SubscriptionModel(
+ subscriptionId = SUB_4_ID,
+ isOpportunistic = true,
+ groupUuid = ParcelUuid(UUID.randomUUID()),
+ )
private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, tableLogBuffer)
}
}
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/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index f5837d6..1bf431b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
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.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
index 3c4e85b..9cf08c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepositoryTest.kt
@@ -19,7 +19,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
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.shared.model.WifiNetworkModel
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 7099f1f..db791bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -36,8 +36,8 @@
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
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.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -83,13 +83,14 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(
- broadcastDispatcher.broadcastFlow(
- any(),
- nullable(),
- anyInt(),
- nullable(),
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ anyInt(),
+ nullable(),
+ )
)
- ).thenReturn(flowOf(Unit))
+ .thenReturn(flowOf(Unit))
executor = FakeExecutor(FakeSystemClock())
scope = CoroutineScope(IMMEDIATE)
underTest = createRepo()
@@ -101,150 +102,152 @@
}
@Test
- fun isWifiEnabled_initiallyGetsWifiManagerValue() = runBlocking(IMMEDIATE) {
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ fun isWifiEnabled_initiallyGetsWifiManagerValue() =
+ runBlocking(IMMEDIATE) {
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
- underTest = createRepo()
+ underTest = createRepo()
- assertThat(underTest.isWifiEnabled.value).isTrue()
- }
-
- @Test
- fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() = runBlocking(IMMEDIATE) {
- // We need to call launch on the flows so that they start updating
- val networkJob = underTest.wifiNetwork.launchIn(this)
- val enabledJob = underTest.isWifiEnabled.launchIn(this)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback().onCapabilitiesChanged(
- NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
- )
-
- assertThat(underTest.isWifiEnabled.value).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback().onCapabilitiesChanged(
- NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
- )
-
- assertThat(underTest.isWifiEnabled.value).isFalse()
-
- networkJob.cancel()
- enabledJob.cancel()
- }
-
- @Test
- fun isWifiEnabled_networkLost_valueUpdated() = runBlocking(IMMEDIATE) {
- // We need to call launch on the flows so that they start updating
- val networkJob = underTest.wifiNetwork.launchIn(this)
- val enabledJob = underTest.isWifiEnabled.launchIn(this)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback().onLost(NETWORK)
-
- assertThat(underTest.isWifiEnabled.value).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback().onLost(NETWORK)
-
- assertThat(underTest.isWifiEnabled.value).isFalse()
-
- networkJob.cancel()
- enabledJob.cancel()
- }
-
- @Test
- fun isWifiEnabled_intentsReceived_valueUpdated() = runBlocking(IMMEDIATE) {
- val intentFlow = MutableSharedFlow<Unit>()
- whenever(
- broadcastDispatcher.broadcastFlow(
- any(),
- nullable(),
- anyInt(),
- nullable(),
- )
- ).thenReturn(intentFlow)
- underTest = createRepo()
-
- val job = underTest.isWifiEnabled.launchIn(this)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- intentFlow.emit(Unit)
-
- assertThat(underTest.isWifiEnabled.value).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- intentFlow.emit(Unit)
-
- assertThat(underTest.isWifiEnabled.value).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() = runBlocking(IMMEDIATE) {
- val intentFlow = MutableSharedFlow<Unit>()
- whenever(
- broadcastDispatcher.broadcastFlow(
- any(),
- nullable(),
- anyInt(),
- nullable(),
- )
- ).thenReturn(intentFlow)
- underTest = createRepo()
-
- val networkJob = underTest.wifiNetwork.launchIn(this)
- val enabledJob = underTest.isWifiEnabled.launchIn(this)
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- intentFlow.emit(Unit)
- assertThat(underTest.isWifiEnabled.value).isFalse()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- getNetworkCallback().onLost(NETWORK)
- assertThat(underTest.isWifiEnabled.value).isTrue()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(false)
- getNetworkCallback().onCapabilitiesChanged(
- NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
- )
- assertThat(underTest.isWifiEnabled.value).isFalse()
-
- whenever(wifiManager.isWifiEnabled).thenReturn(true)
- intentFlow.emit(Unit)
- assertThat(underTest.isWifiEnabled.value).isTrue()
-
- networkJob.cancel()
- enabledJob.cancel()
- }
-
- @Test
- fun isWifiDefault_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
- val job = underTest.isWifiDefault.launchIn(this)
-
- assertThat(underTest.isWifiDefault.value).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun isWifiDefault_wifiNetwork_isTrue() = runBlocking(IMMEDIATE) {
- val job = underTest.isWifiDefault.launchIn(this)
-
- val wifiInfo = mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
+ assertThat(underTest.isWifiEnabled.value).isTrue()
}
- getDefaultNetworkCallback().onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo)
- )
+ @Test
+ fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() =
+ runBlocking(IMMEDIATE) {
+ // We need to call launch on the flows so that they start updating
+ val networkJob = underTest.wifiNetwork.launchIn(this)
+ val enabledJob = underTest.isWifiEnabled.launchIn(this)
- assertThat(underTest.isWifiDefault.value).isTrue()
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- job.cancel()
- }
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ networkJob.cancel()
+ enabledJob.cancel()
+ }
+
+ @Test
+ fun isWifiEnabled_networkLost_valueUpdated() =
+ runBlocking(IMMEDIATE) {
+ // We need to call launch on the flows so that they start updating
+ val networkJob = underTest.wifiNetwork.launchIn(this)
+ val enabledJob = underTest.isWifiEnabled.launchIn(this)
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ getNetworkCallback().onLost(NETWORK)
+
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ getNetworkCallback().onLost(NETWORK)
+
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ networkJob.cancel()
+ enabledJob.cancel()
+ }
+
+ @Test
+ fun isWifiEnabled_intentsReceived_valueUpdated() =
+ runBlocking(IMMEDIATE) {
+ val intentFlow = MutableSharedFlow<Unit>()
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ anyInt(),
+ nullable(),
+ )
+ )
+ .thenReturn(intentFlow)
+ underTest = createRepo()
+
+ val job = underTest.isWifiEnabled.launchIn(this)
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ intentFlow.emit(Unit)
+
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ intentFlow.emit(Unit)
+
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() =
+ runBlocking(IMMEDIATE) {
+ val intentFlow = MutableSharedFlow<Unit>()
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ anyInt(),
+ nullable(),
+ )
+ )
+ .thenReturn(intentFlow)
+ underTest = createRepo()
+
+ val networkJob = underTest.wifiNetwork.launchIn(this)
+ val enabledJob = underTest.isWifiEnabled.launchIn(this)
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ intentFlow.emit(Unit)
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ getNetworkCallback().onLost(NETWORK)
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ intentFlow.emit(Unit)
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ networkJob.cancel()
+ enabledJob.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_initiallyGetsDefault() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ assertThat(underTest.isWifiDefault.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_wifiNetwork_isTrue() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) }
+
+ getDefaultNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
+ }
/** Regression test for b/266628069. */
@Test
@@ -252,16 +255,18 @@
runBlocking(IMMEDIATE) {
val job = underTest.isWifiDefault.launchIn(this)
- val transportInfo = VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
- val networkCapabilities = mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(it.transportInfo).thenReturn(transportInfo)
- }
+ val transportInfo =
+ VpnTransportInfo(
+ /* type= */ 0,
+ /* sessionId= */ "sessionId",
+ )
+ val networkCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(transportInfo)
+ }
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
@@ -276,16 +281,18 @@
runBlocking(IMMEDIATE) {
val job = underTest.isWifiDefault.launchIn(this)
- val transportInfo = VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
- val networkCapabilities = mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
- whenever(it.transportInfo).thenReturn(transportInfo)
- }
+ val transportInfo =
+ VpnTransportInfo(
+ /* type= */ 0,
+ /* sessionId= */ "sessionId",
+ )
+ val networkCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_VPN)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(transportInfo)
+ }
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, networkCapabilities)
@@ -295,31 +302,15 @@
}
@Test
- fun isWifiDefault_cellularVcnNetwork_isTrue() = runBlocking(IMMEDIATE) {
- val job = underTest.isWifiDefault.launchIn(this)
-
- val capabilities = mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(underTest.isWifiDefault.value).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun wifiNetwork_cellularAndWifiTransports_usesCellular_isTrue() =
+ fun isWifiDefault_cellularVcnNetwork_isTrue() =
runBlocking(IMMEDIATE) {
val job = underTest.isWifiDefault.launchIn(this)
- val capabilities = mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
+ val capabilities =
+ mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+ }
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -329,117 +320,134 @@
}
@Test
- fun isWifiDefault_cellularNotVcnNetwork_isFalse() = runBlocking(IMMEDIATE) {
- val job = underTest.isWifiDefault.launchIn(this)
+ fun wifiNetwork_cellularAndWifiTransports_usesCellular_isTrue() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
- val capabilities = mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(mock())
+ val capabilities =
+ mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
}
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(underTest.isWifiDefault.value).isFalse()
-
- job.cancel()
- }
-
@Test
- fun isWifiDefault_wifiNetworkLost_isFalse() = runBlocking(IMMEDIATE) {
- val job = underTest.isWifiDefault.launchIn(this)
+ fun isWifiDefault_cellularNotVcnNetwork_isFalse() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
- // First, add a network
- getDefaultNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(underTest.isWifiDefault.value).isTrue()
+ val capabilities =
+ mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(mock())
+ }
- // WHEN the network is lost
- getDefaultNetworkCallback().onLost(NETWORK)
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
- // THEN we update to false
- assertThat(underTest.isWifiDefault.value).isFalse()
+ assertThat(underTest.isWifiDefault.value).isFalse()
- job.cancel()
- }
-
- @Test
- fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
-
- assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT)
-
- job.cancel()
- }
-
- @Test
- fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
-
- val wifiInfo = mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
- val network = mock<Network>().apply {
- whenever(this.getNetId()).thenReturn(NETWORK_ID)
+ job.cancel()
}
- getNetworkCallback().onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
-
- job.cancel()
- }
-
@Test
- fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ fun isWifiDefault_wifiNetworkLost_isFalse() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isWifiDefault.launchIn(this)
- val wifiInfo = mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
+ // First, add a network
+ getDefaultNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ // WHEN the network is lost
+ getDefaultNetworkCallback().onLost(NETWORK)
+
+ // THEN we update to false
+ assertThat(underTest.isWifiDefault.value).isFalse()
+
+ job.cancel()
}
- getNetworkCallback().onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+ @Test
+ fun wifiNetwork_initiallyGetsDefault() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+ assertThat(latest).isEqualTo(WIFI_NETWORK_DEFAULT)
- job.cancel()
- }
+ job.cancel()
+ }
+
+ @Test
+ fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(SSID)
+ whenever(this.isPrimary).thenReturn(true)
+ }
+ val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ val latestActive = latest as WifiNetworkModel.Active
+ assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+ assertThat(latestActive.ssid).isEqualTo(SSID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.isCarrierMerged).thenReturn(true)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+
+ job.cancel()
+ }
@Test
fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- val wifiInfo = mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
- }
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.isCarrierMerged).thenReturn(true)
+ whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+ }
- getNetworkCallback().onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
+ getNetworkCallback()
+ .onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(wifiInfo),
+ )
assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
@@ -450,26 +458,25 @@
fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
val rssi = -57
- val wifiInfo = mock<WifiInfo>().apply {
- whenever(this.isPrimary).thenReturn(true)
- whenever(this.isCarrierMerged).thenReturn(true)
- whenever(this.rssi).thenReturn(rssi)
- whenever(this.subscriptionId).thenReturn(567)
- }
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.isCarrierMerged).thenReturn(true)
+ whenever(this.rssi).thenReturn(rssi)
+ whenever(this.subscriptionId).thenReturn(567)
+ }
whenever(wifiManager.calculateSignalLevel(rssi)).thenReturn(2)
whenever(wifiManager.maxSignalLevel).thenReturn(5)
- getNetworkCallback().onCapabilitiesChanged(
- NETWORK,
- createWifiNetworkCapabilities(wifiInfo),
- )
+ getNetworkCallback()
+ .onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(wifiInfo),
+ )
assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
val latestCarrierMerged = latest as WifiNetworkModel.CarrierMerged
@@ -483,73 +490,71 @@
}
@Test
- fun wifiNetwork_notValidated_networkNotValidated() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ fun wifiNetwork_notValidated_networkNotValidated() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- getNetworkCallback().onCapabilitiesChanged(
- NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = false)
- )
+ getNetworkCallback()
+ .onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = false)
+ )
- assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse()
+ assertThat((latest as WifiNetworkModel.Active).isValidated).isFalse()
- job.cancel()
- }
-
- @Test
- fun wifiNetwork_validated_networkValidated() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
-
- getNetworkCallback().onCapabilitiesChanged(
- NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = true)
- )
-
- assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
-
- val wifiInfo = mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(false)
+ job.cancel()
}
- getNetworkCallback().onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+ @Test
+ fun wifiNetwork_validated_networkValidated() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+ getNetworkCallback()
+ .onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(PRIMARY_WIFI_INFO, isValidated = true)
+ )
- job.cancel()
- }
+ assertThat((latest as WifiNetworkModel.Active).isValidated).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(SSID)
+ whenever(this.isPrimary).thenReturn(false)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+ job.cancel()
+ }
/** Regression test for b/266628069. */
@Test
fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- val transportInfo = VpnTransportInfo(
- /* type= */ 0,
- /* sessionId= */ "sessionId",
- )
+ val transportInfo =
+ VpnTransportInfo(
+ /* type= */ 0,
+ /* sessionId= */ "sessionId",
+ )
getNetworkCallback()
.onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(transportInfo))
@@ -559,86 +564,16 @@
}
@Test
- fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
-
- val capabilities = mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
-
- job.cancel()
- }
-
- @Test
- fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
-
- val wifiInfo = mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(false)
- }
- val capabilities = mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo))
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
-
- val capabilities = mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(mock())
- }
-
- getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
-
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
+ fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() =
runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- val capabilities = mock<NetworkCapabilities>().apply {
- whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
- whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
- whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
- }
+ val capabilities =
+ mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+ }
getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
@@ -651,309 +586,346 @@
}
@Test
- fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- // Start with the original network
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(SSID)
+ whenever(this.isPrimary).thenReturn(false)
+ }
+ val capabilities =
+ mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(VcnTransportInfo(wifiInfo))
+ }
- // WHEN we update to a new primary network
- val newNetworkId = 456
- val newNetwork = mock<Network>().apply {
- whenever(this.getNetId()).thenReturn(newNetworkId)
+ getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+ assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+ job.cancel()
}
- val newSsid = "CD"
- val newWifiInfo = mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(true)
- }
-
- getNetworkCallback().onCapabilitiesChanged(
- newNetwork, createWifiNetworkCapabilities(newWifiInfo)
- )
-
- // THEN we use the new network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(newNetworkId)
- assertThat(latestActive.ssid).isEqualTo(newSsid)
-
- job.cancel()
- }
@Test
- fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- // Start with the original network
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+ val capabilities =
+ mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(mock())
+ }
- // WHEN we notify of a new but non-primary network
- val newNetworkId = 456
- val newNetwork = mock<Network>().apply {
- whenever(this.getNetId()).thenReturn(newNetworkId)
+ getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+ assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+ job.cancel()
}
- val newSsid = "EF"
- val newWifiInfo = mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(false)
- }
-
- getNetworkCallback().onCapabilitiesChanged(
- newNetwork, createWifiNetworkCapabilities(newWifiInfo)
- )
-
- // THEN we still use the original network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
-
- job.cancel()
- }
@Test
- fun wifiNetwork_newNetworkCapabilities_flowHasNewData() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- val wifiInfo = mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
+ val capabilities =
+ mock<NetworkCapabilities>().apply {
+ whenever(this.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(this.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(this.transportInfo).thenReturn(VcnTransportInfo(PRIMARY_WIFI_INFO))
+ }
+
+ getNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ val latestActive = latest as WifiNetworkModel.Active
+ assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+ assertThat(latestActive.ssid).isEqualTo(SSID)
+
+ job.cancel()
}
- // Start with the original network
- getNetworkCallback().onCapabilitiesChanged(
- NETWORK, createWifiNetworkCapabilities(wifiInfo, isValidated = true)
- )
+ @Test
+ fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- // WHEN we keep the same network ID but change the SSID
- val newSsid = "CD"
- val newWifiInfo = mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(newSsid)
- whenever(this.isPrimary).thenReturn(true)
+ // Start with the original network
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+
+ // WHEN we update to a new primary network
+ val newNetworkId = 456
+ val newNetwork =
+ mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
+ val newSsid = "CD"
+ val newWifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(newSsid)
+ whenever(this.isPrimary).thenReturn(true)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo))
+
+ // THEN we use the new network
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ val latestActive = latest as WifiNetworkModel.Active
+ assertThat(latestActive.networkId).isEqualTo(newNetworkId)
+ assertThat(latestActive.ssid).isEqualTo(newSsid)
+
+ job.cancel()
}
- getNetworkCallback().onCapabilitiesChanged(
- NETWORK, createWifiNetworkCapabilities(newWifiInfo, isValidated = false)
- )
-
- // THEN we've updated to the new SSID
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(newSsid)
- assertThat(latestActive.isValidated).isFalse()
-
- job.cancel()
- }
-
@Test
- fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand
- getNetworkCallback().onLost(NETWORK)
+ // Start with the original network
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- // THEN there's no crash and we still have no network
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+ // WHEN we notify of a new but non-primary network
+ val newNetworkId = 456
+ val newNetwork =
+ mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
+ val newSsid = "EF"
+ val newWifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(newSsid)
+ whenever(this.isPrimary).thenReturn(false)
+ }
- job.cancel()
- }
+ getNetworkCallback()
+ .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(newWifiInfo))
- @Test
- fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ // THEN we still use the original network
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ val latestActive = latest as WifiNetworkModel.Active
+ assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+ assertThat(latestActive.ssid).isEqualTo(SSID)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we lose our current network
- getNetworkCallback().onLost(NETWORK)
-
- // THEN we update to no network
- assertThat(latest is WifiNetworkModel.Inactive).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
-
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
-
- // WHEN we lose an unknown network
- val unknownNetwork = mock<Network>().apply {
- whenever(this.getNetId()).thenReturn(543)
+ job.cancel()
}
- getNetworkCallback().onLost(unknownNetwork)
-
- // THEN we still have our previous network
- assertThat(latest is WifiNetworkModel.Active).isTrue()
- val latestActive = latest as WifiNetworkModel.Active
- assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
- assertThat(latestActive.ssid).isEqualTo(SSID)
-
- job.cancel()
- }
@Test
- fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() = runBlocking(IMMEDIATE) {
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ fun wifiNetwork_newNetworkCapabilities_flowHasNewData() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(SSID)
+ whenever(this.isPrimary).thenReturn(true)
+ }
- // WHEN we update to a new network...
- val newNetworkId = 89
- val newNetwork = mock<Network>().apply {
- whenever(this.getNetId()).thenReturn(newNetworkId)
+ // Start with the original network
+ getNetworkCallback()
+ .onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(wifiInfo, isValidated = true)
+ )
+
+ // WHEN we keep the same network ID but change the SSID
+ val newSsid = "CD"
+ val newWifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(newSsid)
+ whenever(this.isPrimary).thenReturn(true)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(newWifiInfo, isValidated = false)
+ )
+
+ // THEN we've updated to the new SSID
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ val latestActive = latest as WifiNetworkModel.Active
+ assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+ assertThat(latestActive.ssid).isEqualTo(newSsid)
+ assertThat(latestActive.isValidated).isFalse()
+
+ job.cancel()
}
- getNetworkCallback().onCapabilitiesChanged(
- newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
- )
- // ...and lose the old network
- getNetworkCallback().onLost(NETWORK)
- // THEN we still have the new network
- assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId)
+ @Test
+ fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- job.cancel()
- }
+ // WHEN we receive #onLost without any #onCapabilitiesChanged beforehand
+ getNetworkCallback().onLost(NETWORK)
+
+ // THEN there's no crash and we still have no network
+ assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+ assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
+
+ // WHEN we lose our current network
+ getNetworkCallback().onLost(NETWORK)
+
+ // THEN we update to no network
+ assertThat(latest is WifiNetworkModel.Inactive).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+ assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
+
+ // WHEN we lose an unknown network
+ val unknownNetwork = mock<Network>().apply { whenever(this.getNetId()).thenReturn(543) }
+ getNetworkCallback().onLost(unknownNetwork)
+
+ // THEN we still have our previous network
+ assertThat(latest is WifiNetworkModel.Active).isTrue()
+ val latestActive = latest as WifiNetworkModel.Active
+ assertThat(latestActive.networkId).isEqualTo(NETWORK_ID)
+ assertThat(latestActive.ssid).isEqualTo(SSID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() =
+ runBlocking(IMMEDIATE) {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+ assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(NETWORK_ID)
+
+ // WHEN we update to a new network...
+ val newNetworkId = 89
+ val newNetwork =
+ mock<Network>().apply { whenever(this.getNetId()).thenReturn(newNetworkId) }
+ getNetworkCallback()
+ .onCapabilitiesChanged(newNetwork, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+ // ...and lose the old network
+ getNetworkCallback().onLost(NETWORK)
+
+ // THEN we still have the new network
+ assertThat((latest as WifiNetworkModel.Active).networkId).isEqualTo(newNetworkId)
+
+ job.cancel()
+ }
/** Regression test for b/244173280. */
@Test
- fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() = runBlocking(IMMEDIATE) {
- var latest1: WifiNetworkModel? = null
- val job1 = underTest
- .wifiNetwork
- .onEach { latest1 = it }
- .launchIn(this)
+ fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
+ runBlocking(IMMEDIATE) {
+ var latest1: WifiNetworkModel? = null
+ val job1 = underTest.wifiNetwork.onEach { latest1 = it }.launchIn(this)
- getNetworkCallback()
- .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO))
- assertThat(latest1 is WifiNetworkModel.Active).isTrue()
- val latest1Active = latest1 as WifiNetworkModel.Active
- assertThat(latest1Active.networkId).isEqualTo(NETWORK_ID)
- assertThat(latest1Active.ssid).isEqualTo(SSID)
+ assertThat(latest1 is WifiNetworkModel.Active).isTrue()
+ val latest1Active = latest1 as WifiNetworkModel.Active
+ assertThat(latest1Active.networkId).isEqualTo(NETWORK_ID)
+ assertThat(latest1Active.ssid).isEqualTo(SSID)
- // WHEN we add a second subscriber after having already emitted a value
- var latest2: WifiNetworkModel? = null
- val job2 = underTest
- .wifiNetwork
- .onEach { latest2 = it }
- .launchIn(this)
+ // WHEN we add a second subscriber after having already emitted a value
+ var latest2: WifiNetworkModel? = null
+ val job2 = underTest.wifiNetwork.onEach { latest2 = it }.launchIn(this)
- // THEN the second subscribe receives the already-emitted value
- assertThat(latest2 is WifiNetworkModel.Active).isTrue()
- val latest2Active = latest2 as WifiNetworkModel.Active
- assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID)
- assertThat(latest2Active.ssid).isEqualTo(SSID)
+ // THEN the second subscribe receives the already-emitted value
+ assertThat(latest2 is WifiNetworkModel.Active).isTrue()
+ val latest2Active = latest2 as WifiNetworkModel.Active
+ assertThat(latest2Active.networkId).isEqualTo(NETWORK_ID)
+ assertThat(latest2Active.ssid).isEqualTo(SSID)
- job1.cancel()
- job2.cancel()
- }
+ job1.cancel()
+ job2.cancel()
+ }
@Test
- fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) {
- var latest: DataActivityModel? = null
- val job = underTest
- .wifiActivity
- .onEach { latest = it }
- .launchIn(this)
+ fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
+ runBlocking(IMMEDIATE) {
+ var latest: DataActivityModel? = null
+ val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
- assertThat(latest).isEqualTo(
- DataActivityModel(hasActivityIn = false, hasActivityOut = false)
- )
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) {
- var latest: DataActivityModel? = null
- val job = underTest
- .wifiActivity
- .onEach { latest = it }
- .launchIn(this)
+ fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
+ runBlocking(IMMEDIATE) {
+ var latest: DataActivityModel? = null
+ val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
- assertThat(latest).isEqualTo(
- DataActivityModel(hasActivityIn = true, hasActivityOut = false)
- )
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) {
- var latest: DataActivityModel? = null
- val job = underTest
- .wifiActivity
- .onEach { latest = it }
- .launchIn(this)
+ fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
+ runBlocking(IMMEDIATE) {
+ var latest: DataActivityModel? = null
+ val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
- assertThat(latest).isEqualTo(
- DataActivityModel(hasActivityIn = false, hasActivityOut = true)
- )
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) {
- var latest: DataActivityModel? = null
- val job = underTest
- .wifiActivity
- .onEach { latest = it }
- .launchIn(this)
+ fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
+ runBlocking(IMMEDIATE) {
+ var latest: DataActivityModel? = null
+ val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
- getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
- assertThat(latest).isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
+ assertThat(latest)
+ .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
- job.cancel()
- }
+ job.cancel()
+ }
private fun createRepo(): WifiRepositoryImpl {
return WifiRepositoryImpl(
@@ -998,14 +970,13 @@
private companion object {
const val NETWORK_ID = 45
- val NETWORK = mock<Network>().apply {
- whenever(this.getNetId()).thenReturn(NETWORK_ID)
- }
+ val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
const val SSID = "AB"
- val PRIMARY_WIFI_INFO: WifiInfo = mock<WifiInfo>().apply {
- whenever(this.ssid).thenReturn(SSID)
- whenever(this.isPrimary).thenReturn(true)
- }
+ val PRIMARY_WIFI_INFO: WifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(SSID)
+ whenever(this.isPrimary).thenReturn(true)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index 089a170..fc2277b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -22,8 +22,8 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,10 +57,7 @@
wifiRepository.setWifiNetwork(WifiNetworkModel.Unavailable)
var latest: String? = "default"
- val job = underTest
- .ssid
- .onEach { latest = it }
- .launchIn(this)
+ val job = underTest.ssid.onEach { latest = it }.launchIn(this)
assertThat(latest).isNull()
@@ -68,238 +65,223 @@
}
@Test
- fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+ fun ssid_inactiveNetwork_outputsNull() =
+ runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
- var latest: String? = "default"
- val job = underTest
- .ssid
- .onEach { latest = it }
- .launchIn(this)
+ var latest: String? = "default"
+ val job = underTest.ssid.onEach { latest = it }.launchIn(this)
- assertThat(latest).isNull()
+ assertThat(latest).isNull()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(
- WifiNetworkModel.CarrierMerged(networkId = 1, subscriptionId = 2, level = 1)
- )
+ fun ssid_carrierMergedNetwork_outputsNull() =
+ runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.CarrierMerged(networkId = 1, subscriptionId = 2, level = 1)
+ )
- var latest: String? = "default"
- val job = underTest
- .ssid
- .onEach { latest = it }
- .launchIn(this)
+ var latest: String? = "default"
+ val job = underTest.ssid.onEach { latest = it }.launchIn(this)
- assertThat(latest).isNull()
+ assertThat(latest).isNull()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
- networkId = 1,
- level = 1,
- isPasspointAccessPoint = true,
- passpointProviderFriendlyName = "friendly",
- ))
+ fun ssid_isPasspointAccessPoint_outputsPasspointName() =
+ runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 1,
+ isPasspointAccessPoint = true,
+ passpointProviderFriendlyName = "friendly",
+ )
+ )
- var latest: String? = null
- val job = underTest
- .ssid
- .onEach { latest = it }
- .launchIn(this)
+ var latest: String? = null
+ val job = underTest.ssid.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo("friendly")
+ assertThat(latest).isEqualTo("friendly")
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
- networkId = 1,
- level = 1,
- isOnlineSignUpForPasspointAccessPoint = true,
- passpointProviderFriendlyName = "friendly",
- ))
+ fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() =
+ runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 1,
+ isOnlineSignUpForPasspointAccessPoint = true,
+ passpointProviderFriendlyName = "friendly",
+ )
+ )
- var latest: String? = null
- val job = underTest
- .ssid
- .onEach { latest = it }
- .launchIn(this)
+ var latest: String? = null
+ val job = underTest.ssid.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo("friendly")
+ assertThat(latest).isEqualTo("friendly")
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
- networkId = 1,
- level = 1,
- ssid = WifiManager.UNKNOWN_SSID,
- ))
+ fun ssid_unknownSsid_outputsNull() =
+ runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 1,
+ ssid = WifiManager.UNKNOWN_SSID,
+ )
+ )
- var latest: String? = "default"
- val job = underTest
- .ssid
- .onEach { latest = it }
- .launchIn(this)
+ var latest: String? = "default"
+ val job = underTest.ssid.onEach { latest = it }.launchIn(this)
- assertThat(latest).isNull()
+ assertThat(latest).isNull()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
- networkId = 1,
- level = 1,
- ssid = "MyAwesomeWifiNetwork",
- ))
+ fun ssid_validSsid_outputsSsid() =
+ runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 1,
+ ssid = "MyAwesomeWifiNetwork",
+ )
+ )
- var latest: String? = null
- val job = underTest
- .ssid
- .onEach { latest = it }
- .launchIn(this)
+ var latest: String? = null
+ val job = underTest.ssid.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo("MyAwesomeWifiNetwork")
+ assertThat(latest).isEqualTo("MyAwesomeWifiNetwork")
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun isEnabled_matchesRepoIsEnabled() = runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
- val job = underTest
- .isEnabled
- .onEach { latest = it }
- .launchIn(this)
+ fun isEnabled_matchesRepoIsEnabled() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isEnabled.onEach { latest = it }.launchIn(this)
- wifiRepository.setIsWifiEnabled(true)
- yield()
- assertThat(latest).isTrue()
+ wifiRepository.setIsWifiEnabled(true)
+ yield()
+ assertThat(latest).isTrue()
- wifiRepository.setIsWifiEnabled(false)
- yield()
- assertThat(latest).isFalse()
+ wifiRepository.setIsWifiEnabled(false)
+ yield()
+ assertThat(latest).isFalse()
- wifiRepository.setIsWifiEnabled(true)
- yield()
- assertThat(latest).isTrue()
+ wifiRepository.setIsWifiEnabled(true)
+ yield()
+ assertThat(latest).isTrue()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun isDefault_matchesRepoIsDefault() = runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
- val job = underTest
- .isDefault
- .onEach { latest = it }
- .launchIn(this)
+ fun isDefault_matchesRepoIsDefault() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefault.onEach { latest = it }.launchIn(this)
- wifiRepository.setIsWifiDefault(true)
- yield()
- assertThat(latest).isTrue()
+ wifiRepository.setIsWifiDefault(true)
+ yield()
+ assertThat(latest).isTrue()
- wifiRepository.setIsWifiDefault(false)
- yield()
- assertThat(latest).isFalse()
+ wifiRepository.setIsWifiDefault(false)
+ yield()
+ assertThat(latest).isFalse()
- wifiRepository.setIsWifiDefault(true)
- yield()
- assertThat(latest).isTrue()
+ wifiRepository.setIsWifiDefault(true)
+ yield()
+ assertThat(latest).isTrue()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun wifiNetwork_matchesRepoWifiNetwork() = runBlocking(IMMEDIATE) {
- val wifiNetwork = WifiNetworkModel.Active(
- networkId = 45,
- isValidated = true,
- level = 3,
- ssid = "AB",
- passpointProviderFriendlyName = "friendly"
- )
- wifiRepository.setWifiNetwork(wifiNetwork)
+ fun wifiNetwork_matchesRepoWifiNetwork() =
+ runBlocking(IMMEDIATE) {
+ val wifiNetwork =
+ WifiNetworkModel.Active(
+ networkId = 45,
+ isValidated = true,
+ level = 3,
+ ssid = "AB",
+ passpointProviderFriendlyName = "friendly"
+ )
+ wifiRepository.setWifiNetwork(wifiNetwork)
- var latest: WifiNetworkModel? = null
- val job = underTest
- .wifiNetwork
- .onEach { latest = it }
- .launchIn(this)
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(wifiNetwork)
+ assertThat(latest).isEqualTo(wifiNetwork)
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) {
- var latest: DataActivityModel? = null
- val job = underTest
- .activity
- .onEach { latest = it }
- .launchIn(this)
+ fun activity_matchesRepoWifiActivity() =
+ runBlocking(IMMEDIATE) {
+ var latest: DataActivityModel? = null
+ val job = underTest.activity.onEach { latest = it }.launchIn(this)
- val activity1 = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
- wifiRepository.setWifiActivity(activity1)
- yield()
- assertThat(latest).isEqualTo(activity1)
+ val activity1 = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity1)
+ yield()
+ assertThat(latest).isEqualTo(activity1)
- val activity2 = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
- wifiRepository.setWifiActivity(activity2)
- yield()
- assertThat(latest).isEqualTo(activity2)
+ val activity2 = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity2)
+ yield()
+ assertThat(latest).isEqualTo(activity2)
- val activity3 = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
- wifiRepository.setWifiActivity(activity3)
- yield()
- assertThat(latest).isEqualTo(activity3)
+ val activity3 = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity3)
+ yield()
+ assertThat(latest).isEqualTo(activity3)
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) {
- connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+ fun isForceHidden_repoHasWifiHidden_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
- var latest: Boolean? = null
- val job = underTest
- .isForceHidden
- .onEach { latest = it }
- .launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
- assertThat(latest).isTrue()
+ assertThat(latest).isTrue()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() = runBlocking(IMMEDIATE) {
- connectivityRepository.setForceHiddenIcons(setOf())
+ fun isForceHidden_repoDoesNotHaveWifiHidden_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ connectivityRepository.setForceHiddenIcons(setOf())
- var latest: Boolean? = null
- val job = underTest
- .isForceHidden
- .onEach { latest = it }
- .launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
}
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
index 824cebd..ab4e93c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package com.android.systemui.statusbar.pipeline.wifi.shared.model
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableRowLogger
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel.Companion.MIN_VALID_LEVEL
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Companion.MIN_VALID_LEVEL
import com.google.common.truth.Truth.assertThat
import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index b8ace2f..60f564e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -38,11 +38,11 @@
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.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import com.android.systemui.util.mockito.whenever
@@ -62,16 +62,11 @@
private lateinit var testableLooper: TestableLooper
- @Mock
- private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
- @Mock
- private lateinit var logger: ConnectivityPipelineLogger
- @Mock
- private lateinit var tableLogBuffer: TableLogBuffer
- @Mock
- private lateinit var connectivityConstants: ConnectivityConstants
- @Mock
- private lateinit var wifiConstants: WifiConstants
+ @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock private lateinit var connectivityConstants: ConnectivityConstants
+ @Mock private lateinit var wifiConstants: WifiConstants
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
@@ -91,25 +86,28 @@
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(Dispatchers.Unconfined)
- airplaneModeViewModel = AirplaneModeViewModelImpl(
- AirplaneModeInteractor(
- airplaneModeRepository,
- connectivityRepository,
- ),
- logger,
- scope,
- )
- viewModel = WifiViewModel(
- airplaneModeViewModel,
- connectivityConstants,
- context,
- logger,
- tableLogBuffer,
- interactor,
- scope,
- statusBarPipelineFlags,
- wifiConstants,
- ).home
+ airplaneModeViewModel =
+ AirplaneModeViewModelImpl(
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ connectivityRepository,
+ ),
+ logger,
+ scope,
+ )
+ viewModel =
+ WifiViewModel(
+ airplaneModeViewModel,
+ connectivityConstants,
+ context,
+ logger,
+ tableLogBuffer,
+ interactor,
+ scope,
+ statusBarPipelineFlags,
+ wifiConstants,
+ )
+ .home
}
// Note: The following tests are more like integration tests, since they stand up a full
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index b932837..648d7a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -36,11 +36,11 @@
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.FakeConnectivityRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index e5cfec9..45ebb39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -29,11 +29,11 @@
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -79,14 +79,15 @@
wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
- airplaneModeViewModel = AirplaneModeViewModelImpl(
- AirplaneModeInteractor(
- airplaneModeRepository,
- connectivityRepository,
- ),
- logger,
- scope,
- )
+ airplaneModeViewModel =
+ AirplaneModeViewModelImpl(
+ AirplaneModeInteractor(
+ airplaneModeRepository,
+ connectivityRepository,
+ ),
+ logger,
+ scope,
+ )
createAndSetViewModel()
}
@@ -104,451 +105,386 @@
// instances. There are also some tests that verify all 3 instances received the same data.
@Test
- fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
- var latestHome: WifiIcon? = null
- val jobHome = underTest
- .home
- .wifiIcon
- .onEach { latestHome = it }
- .launchIn(this)
+ fun wifiIcon_allLocationViewModelsReceiveSameData() =
+ runBlocking(IMMEDIATE) {
+ var latestHome: WifiIcon? = null
+ val jobHome = underTest.home.wifiIcon.onEach { latestHome = it }.launchIn(this)
- var latestKeyguard: WifiIcon? = null
- val jobKeyguard = underTest
- .keyguard
- .wifiIcon
- .onEach { latestKeyguard = it }
- .launchIn(this)
+ var latestKeyguard: WifiIcon? = null
+ val jobKeyguard =
+ underTest.keyguard.wifiIcon.onEach { latestKeyguard = it }.launchIn(this)
- var latestQs: WifiIcon? = null
- val jobQs = underTest
- .qs
- .wifiIcon
- .onEach { latestQs = it }
- .launchIn(this)
+ var latestQs: WifiIcon? = null
+ val jobQs = underTest.qs.wifiIcon.onEach { latestQs = it }.launchIn(this)
- wifiRepository.setWifiNetwork(
- WifiNetworkModel.Active(
- NETWORK_ID,
- isValidated = true,
- level = 1
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1)
)
- )
- yield()
+ yield()
- assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java)
- assertThat(latestHome).isEqualTo(latestKeyguard)
- assertThat(latestKeyguard).isEqualTo(latestQs)
+ assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java)
+ assertThat(latestHome).isEqualTo(latestKeyguard)
+ assertThat(latestKeyguard).isEqualTo(latestQs)
- jobHome.cancel()
- jobKeyguard.cancel()
- jobQs.cancel()
- }
+ jobHome.cancel()
+ jobKeyguard.cancel()
+ jobQs.cancel()
+ }
@Test
- fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
- createAndSetViewModel()
- wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+ fun activity_showActivityConfigFalse_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var activityIn: Boolean? = null
- val activityInJob = underTest
- .home
- .isActivityInViewVisible
- .onEach { activityIn = it }
- .launchIn(this)
+ var activityIn: Boolean? = null
+ val activityInJob =
+ underTest.home.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this)
- var activityOut: Boolean? = null
- val activityOutJob = underTest
- .home
- .isActivityOutViewVisible
- .onEach { activityOut = it }
- .launchIn(this)
+ var activityOut: Boolean? = null
+ val activityOutJob =
+ underTest.home.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this)
- var activityContainer: Boolean? = null
- val activityContainerJob = underTest
- .home
- .isActivityContainerVisible
- .onEach { activityContainer = it }
- .launchIn(this)
+ var activityContainer: Boolean? = null
+ val activityContainerJob =
+ underTest.home.isActivityContainerVisible
+ .onEach { activityContainer = it }
+ .launchIn(this)
- // Verify that on launch, we receive false.
- assertThat(activityIn).isFalse()
- assertThat(activityOut).isFalse()
- assertThat(activityContainer).isFalse()
+ // Verify that on launch, we receive false.
+ assertThat(activityIn).isFalse()
+ assertThat(activityOut).isFalse()
+ assertThat(activityContainer).isFalse()
- activityInJob.cancel()
- activityOutJob.cancel()
- activityContainerJob.cancel()
- }
+ activityInJob.cancel()
+ activityOutJob.cancel()
+ activityContainerJob.cancel()
+ }
@Test
- fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
- createAndSetViewModel()
- wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+ fun activity_showActivityConfigFalse_noUpdatesReceived() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var activityIn: Boolean? = null
- val activityInJob = underTest
- .home
- .isActivityInViewVisible
- .onEach { activityIn = it }
- .launchIn(this)
+ var activityIn: Boolean? = null
+ val activityInJob =
+ underTest.home.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this)
- var activityOut: Boolean? = null
- val activityOutJob = underTest
- .home
- .isActivityOutViewVisible
- .onEach { activityOut = it }
- .launchIn(this)
+ var activityOut: Boolean? = null
+ val activityOutJob =
+ underTest.home.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this)
- var activityContainer: Boolean? = null
- val activityContainerJob = underTest
- .home
- .isActivityContainerVisible
- .onEach { activityContainer = it }
- .launchIn(this)
+ var activityContainer: Boolean? = null
+ val activityContainerJob =
+ underTest.home.isActivityContainerVisible
+ .onEach { activityContainer = it }
+ .launchIn(this)
- // WHEN we update the repo to have activity
- val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
- wifiRepository.setWifiActivity(activity)
- yield()
+ // WHEN we update the repo to have activity
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- // THEN we didn't update to the new activity (because our config is false)
- assertThat(activityIn).isFalse()
- assertThat(activityOut).isFalse()
- assertThat(activityContainer).isFalse()
+ // THEN we didn't update to the new activity (because our config is false)
+ assertThat(activityIn).isFalse()
+ assertThat(activityOut).isFalse()
+ assertThat(activityContainer).isFalse()
- activityInJob.cancel()
- activityOutJob.cancel()
- activityContainerJob.cancel()
- }
+ activityInJob.cancel()
+ activityOutJob.cancel()
+ activityContainerJob.cancel()
+ }
@Test
- fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
+ fun activity_nullSsid_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1))
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(NETWORK_ID, ssid = null, level = 1)
+ )
- var activityIn: Boolean? = null
- val activityInJob = underTest
- .home
- .isActivityInViewVisible
- .onEach { activityIn = it }
- .launchIn(this)
+ var activityIn: Boolean? = null
+ val activityInJob =
+ underTest.home.isActivityInViewVisible.onEach { activityIn = it }.launchIn(this)
- var activityOut: Boolean? = null
- val activityOutJob = underTest
- .home
- .isActivityOutViewVisible
- .onEach { activityOut = it }
- .launchIn(this)
+ var activityOut: Boolean? = null
+ val activityOutJob =
+ underTest.home.isActivityOutViewVisible.onEach { activityOut = it }.launchIn(this)
- var activityContainer: Boolean? = null
- val activityContainerJob = underTest
- .home
- .isActivityContainerVisible
- .onEach { activityContainer = it }
- .launchIn(this)
+ var activityContainer: Boolean? = null
+ val activityContainerJob =
+ underTest.home.isActivityContainerVisible
+ .onEach { activityContainer = it }
+ .launchIn(this)
- // WHEN we update the repo to have activity
- val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
- wifiRepository.setWifiActivity(activity)
- yield()
+ // WHEN we update the repo to have activity
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- // THEN we still output false because our network's SSID is null
- assertThat(activityIn).isFalse()
- assertThat(activityOut).isFalse()
- assertThat(activityContainer).isFalse()
+ // THEN we still output false because our network's SSID is null
+ assertThat(activityIn).isFalse()
+ assertThat(activityOut).isFalse()
+ assertThat(activityContainer).isFalse()
- activityInJob.cancel()
- activityOutJob.cancel()
- activityContainerJob.cancel()
- }
+ activityInJob.cancel()
+ activityOutJob.cancel()
+ activityContainerJob.cancel()
+ }
@Test
- fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
- wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+ fun activity_allLocationViewModelsReceiveSameData() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latestHome: Boolean? = null
- val jobHome = underTest
- .home
- .isActivityInViewVisible
- .onEach { latestHome = it }
- .launchIn(this)
+ var latestHome: Boolean? = null
+ val jobHome =
+ underTest.home.isActivityInViewVisible.onEach { latestHome = it }.launchIn(this)
- var latestKeyguard: Boolean? = null
- val jobKeyguard = underTest
- .keyguard
- .isActivityInViewVisible
- .onEach { latestKeyguard = it }
- .launchIn(this)
+ var latestKeyguard: Boolean? = null
+ val jobKeyguard =
+ underTest.keyguard.isActivityInViewVisible
+ .onEach { latestKeyguard = it }
+ .launchIn(this)
- var latestQs: Boolean? = null
- val jobQs = underTest
- .qs
- .isActivityInViewVisible
- .onEach { latestQs = it }
- .launchIn(this)
+ var latestQs: Boolean? = null
+ val jobQs = underTest.qs.isActivityInViewVisible.onEach { latestQs = it }.launchIn(this)
- val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
- wifiRepository.setWifiActivity(activity)
- yield()
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- assertThat(latestHome).isTrue()
- assertThat(latestKeyguard).isTrue()
- assertThat(latestQs).isTrue()
+ assertThat(latestHome).isTrue()
+ assertThat(latestKeyguard).isTrue()
+ assertThat(latestQs).isTrue()
- jobHome.cancel()
- jobKeyguard.cancel()
- jobQs.cancel()
- }
+ jobHome.cancel()
+ jobKeyguard.cancel()
+ jobQs.cancel()
+ }
@Test
- fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
- wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+ fun activityIn_hasActivityInTrue_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest
- .home
- .isActivityInViewVisible
- .onEach { latest = it }
- .launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.home.isActivityInViewVisible.onEach { latest = it }.launchIn(this)
- val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
- wifiRepository.setWifiActivity(activity)
- yield()
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- assertThat(latest).isTrue()
+ assertThat(latest).isTrue()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
- wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+ fun activityIn_hasActivityInFalse_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest
- .home
- .isActivityInViewVisible
- .onEach { latest = it }
- .launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.home.isActivityInViewVisible.onEach { latest = it }.launchIn(this)
- val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
- wifiRepository.setWifiActivity(activity)
- yield()
+ val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- assertThat(latest).isFalse()
+ assertThat(latest).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
- wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+ fun activityOut_hasActivityOutTrue_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest
- .home
- .isActivityOutViewVisible
- .onEach { latest = it }
- .launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.home.isActivityOutViewVisible.onEach { latest = it }.launchIn(this)
- val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
- wifiRepository.setWifiActivity(activity)
- yield()
+ val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- assertThat(latest).isTrue()
+ assertThat(latest).isTrue()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
- wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+ fun activityOut_hasActivityOutFalse_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest
- .home
- .isActivityOutViewVisible
- .onEach { latest = it }
- .launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.home.isActivityOutViewVisible.onEach { latest = it }.launchIn(this)
- val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
- wifiRepository.setWifiActivity(activity)
- yield()
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- assertThat(latest).isFalse()
+ assertThat(latest).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
- wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+ fun activityContainer_hasActivityInTrue_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest
- .home
- .isActivityContainerVisible
- .onEach { latest = it }
- .launchIn(this)
+ var latest: Boolean? = null
+ val job =
+ underTest.home.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
- val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
- wifiRepository.setWifiActivity(activity)
- yield()
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- assertThat(latest).isTrue()
+ assertThat(latest).isTrue()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
- wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+ fun activityContainer_hasActivityOutTrue_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest
- .home
- .isActivityContainerVisible
- .onEach { latest = it }
- .launchIn(this)
+ var latest: Boolean? = null
+ val job =
+ underTest.home.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
- val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
- wifiRepository.setWifiActivity(activity)
- yield()
+ val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- assertThat(latest).isTrue()
+ assertThat(latest).isTrue()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
- wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+ fun activityContainer_inAndOutTrue_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest
- .home
- .isActivityContainerVisible
- .onEach { latest = it }
- .launchIn(this)
+ var latest: Boolean? = null
+ val job =
+ underTest.home.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
- val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
- wifiRepository.setWifiActivity(activity)
- yield()
+ val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- assertThat(latest).isTrue()
+ assertThat(latest).isTrue()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
- createAndSetViewModel()
- wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+ fun activityContainer_inAndOutFalse_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
- var latest: Boolean? = null
- val job = underTest
- .home
- .isActivityContainerVisible
- .onEach { latest = it }
- .launchIn(this)
+ var latest: Boolean? = null
+ val job =
+ underTest.home.isActivityContainerVisible.onEach { latest = it }.launchIn(this)
- val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
- wifiRepository.setWifiActivity(activity)
- yield()
+ val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- assertThat(latest).isFalse()
+ assertThat(latest).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun airplaneSpacer_notAirplaneMode_outputsFalse() = runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
- val job = underTest
- .qs
- .isAirplaneSpacerVisible
- .onEach { latest = it }
- .launchIn(this)
+ fun airplaneSpacer_notAirplaneMode_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.qs.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this)
- airplaneModeRepository.setIsAirplaneMode(false)
- yield()
+ airplaneModeRepository.setIsAirplaneMode(false)
+ yield()
- assertThat(latest).isFalse()
+ assertThat(latest).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun airplaneSpacer_airplaneForceHidden_outputsFalse() = runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
- val job = underTest
- .qs
- .isAirplaneSpacerVisible
- .onEach { latest = it }
- .launchIn(this)
+ fun airplaneSpacer_airplaneForceHidden_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.qs.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this)
- airplaneModeRepository.setIsAirplaneMode(true)
- connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
- yield()
+ airplaneModeRepository.setIsAirplaneMode(true)
+ connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.AIRPLANE))
+ yield()
- assertThat(latest).isFalse()
+ assertThat(latest).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun airplaneSpacer_airplaneIconVisible_outputsTrue() = runBlocking(IMMEDIATE) {
- var latest: Boolean? = null
- val job = underTest
- .qs
- .isAirplaneSpacerVisible
- .onEach { latest = it }
- .launchIn(this)
+ fun airplaneSpacer_airplaneIconVisible_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.qs.isAirplaneSpacerVisible.onEach { latest = it }.launchIn(this)
- airplaneModeRepository.setIsAirplaneMode(true)
- yield()
+ airplaneModeRepository.setIsAirplaneMode(true)
+ yield()
- assertThat(latest).isTrue()
+ assertThat(latest).isTrue()
- job.cancel()
- }
+ job.cancel()
+ }
private fun createAndSetViewModel() {
// [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
// creations rely on certain config values that we mock out in individual tests. This method
// allows tests to create the view model only after those configs are correctly set up.
- underTest = WifiViewModel(
- airplaneModeViewModel,
- connectivityConstants,
- context,
- logger,
- tableLogBuffer,
- interactor,
- scope,
- statusBarPipelineFlags,
- wifiConstants,
- )
+ underTest =
+ WifiViewModel(
+ airplaneModeViewModel,
+ connectivityConstants,
+ context,
+ logger,
+ tableLogBuffer,
+ interactor,
+ scope,
+ statusBarPipelineFlags,
+ wifiConstants,
+ )
}
companion object {
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/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index fc7436a..586bdc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -27,6 +27,7 @@
import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import android.widget.TextView
+import androidx.core.animation.doOnCancel
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
@@ -361,6 +362,105 @@
}
@Test
+ fun displayView_loading_animationStarted() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ assertThat(underTest.loadingDetails!!.animator.isStarted).isTrue()
+ }
+
+ @Test
+ fun displayView_notLoading_noAnimation() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Error,
+ )
+ )
+
+ assertThat(underTest.loadingDetails).isNull()
+ }
+
+ @Test
+ fun displayView_loadingThenNotLoading_animationStopped() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ val animator = underTest.loadingDetails!!.animator
+ var cancelled = false
+ animator.doOnCancel { cancelled = true }
+
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Button(Text.Loaded("button")) {},
+ )
+ )
+
+ assertThat(cancelled).isTrue()
+ assertThat(underTest.loadingDetails).isNull()
+ }
+
+ @Test
+ fun displayView_loadingThenHideView_animationStopped() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ val animator = underTest.loadingDetails!!.animator
+ var cancelled = false
+ animator.doOnCancel { cancelled = true }
+
+ underTest.removeView(DEVICE_ID, "TestReason")
+
+ assertThat(cancelled).isTrue()
+ assertThat(underTest.loadingDetails).isNull()
+ }
+
+ @Test
+ fun displayView_loadingThenNewLoading_animationStaysTheSame() {
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ val animator = underTest.loadingDetails!!.animator
+ var cancelled = false
+ animator.doOnCancel { cancelled = true }
+
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Resource(R.id.check_box, null),
+ Text.Loaded("new text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ assertThat(underTest.loadingDetails!!.animator).isEqualTo(animator)
+ assertThat(underTest.loadingDetails!!.animator.isStarted).isTrue()
+ assertThat(cancelled).isFalse()
+ }
+
+ @Test
fun displayView_vibrationEffect_doubleClickEffect() {
underTest.displayView(
createChipbarInfo(
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/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index abbdab0..5288608 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -20,17 +20,13 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
-import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
-import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.util.TestFoldStateProvider
-import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,9 +41,7 @@
@Before
fun setUp() {
- progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(
- foldStateProvider
- )
+ progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
progressProvider.addCallback(listener)
}
@@ -79,9 +73,7 @@
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
)
- with(listener.ensureTransitionFinished()) {
- assertHasSingleFinishingEvent()
- }
+ with(listener.ensureTransitionFinished()) { assertHasSingleFinishingEvent() }
}
@Test
@@ -150,106 +142,12 @@
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) },
)
- with(listener.ensureTransitionFinished()) {
- assertHasFoldAnimationAtTheEnd()
- }
- }
-
- private class TestUnfoldProgressListener : TransitionProgressListener {
-
- private val recordings: MutableList<UnfoldTransitionRecording> = arrayListOf()
- private var currentRecording: UnfoldTransitionRecording? = null
-
- override fun onTransitionStarted() {
- assertWithMessage("Trying to start a transition when it is already in progress")
- .that(currentRecording).isNull()
-
- currentRecording = UnfoldTransitionRecording()
- }
-
- override fun onTransitionProgress(progress: Float) {
- assertWithMessage("Received transition progress event when it's not started")
- .that(currentRecording).isNotNull()
- currentRecording!!.addProgress(progress)
- }
-
- override fun onTransitionFinishing() {
- assertWithMessage("Received transition finishing event when it's not started")
- .that(currentRecording).isNotNull()
- currentRecording!!.onFinishing()
- }
-
- override fun onTransitionFinished() {
- assertWithMessage("Received transition finish event when it's not started")
- .that(currentRecording).isNotNull()
- recordings += currentRecording!!
- currentRecording = null
- }
-
- fun ensureTransitionFinished(): UnfoldTransitionRecording {
- waitForCondition { recordings.size == 1 }
- return recordings.first()
- }
-
- class UnfoldTransitionRecording {
- private val progressHistory: MutableList<Float> = arrayListOf()
- private var finishingInvocations: Int = 0
-
- fun addProgress(progress: Float) {
- assertThat(progress).isAtMost(1.0f)
- assertThat(progress).isAtLeast(0.0f)
-
- progressHistory += progress
- }
-
- fun onFinishing() {
- finishingInvocations++
- }
-
- fun assertIncreasingProgress() {
- assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
- assertThat(progressHistory).isInOrder()
- }
-
- fun assertDecreasingProgress() {
- assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
- assertThat(progressHistory).isInOrder(Comparator.reverseOrder<Float>())
- }
-
- fun assertFinishedWithUnfold() {
- assertThat(progressHistory).isNotEmpty()
- assertThat(progressHistory.last()).isEqualTo(1.0f)
- }
-
- fun assertFinishedWithFold() {
- assertThat(progressHistory).isNotEmpty()
- assertThat(progressHistory.last()).isEqualTo(0.0f)
- }
-
- fun assertHasFoldAnimationAtTheEnd() {
- // Check that there are at least a few decreasing events at the end
- assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
- assertThat(progressHistory.takeLast(MIN_ANIMATION_EVENTS))
- .isInOrder(Comparator.reverseOrder<Float>())
- assertThat(progressHistory.last()).isEqualTo(0.0f)
- }
-
- fun assertHasSingleFinishingEvent() {
- assertWithMessage("onTransitionFinishing callback should be invoked exactly " +
- "one time").that(finishingInvocations).isEqualTo(1)
- }
- }
-
- private companion object {
- private const val MIN_ANIMATION_EVENTS = 5
- }
+ with(listener.ensureTransitionFinished()) { assertHasFoldAnimationAtTheEnd() }
}
private fun runOnMainThreadWithInterval(vararg blocks: () -> Unit, intervalMillis: Long = 60) {
blocks.forEach {
- InstrumentationRegistry.getInstrumentation().runOnMainSync {
- it()
- }
+ InstrumentationRegistry.getInstrumentation().runOnMainSync { it() }
Thread.sleep(intervalMillis)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt
new file mode 100644
index 0000000..0e7e039
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiverTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.unfold.progress
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class RemoteUnfoldTransitionReceiverTest : SysuiTestCase() {
+
+ private val progressProvider = RemoteUnfoldTransitionReceiver { it.run() }
+ private val listener = TestUnfoldProgressListener()
+
+ @Before
+ fun setUp() {
+ progressProvider.addCallback(listener)
+ }
+
+ @Test
+ fun onTransitionStarted_propagated() {
+ progressProvider.onTransitionStarted()
+
+ listener.assertStarted()
+ }
+
+ @Test
+ fun onTransitionProgress_propagated() {
+ progressProvider.onTransitionStarted()
+
+ progressProvider.onTransitionProgress(0.5f)
+
+ listener.assertLastProgress(0.5f)
+ }
+
+ @Test
+ fun onTransitionEnded_propagated() {
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.5f)
+
+ progressProvider.onTransitionFinished()
+
+ listener.ensureTransitionFinished()
+ }
+
+ @Test
+ fun onTransitionStarted_afterCallbackRemoved_notPropagated() {
+ progressProvider.removeCallback(listener)
+
+ progressProvider.onTransitionStarted()
+
+ listener.assertNotStarted()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
new file mode 100644
index 0000000..f653207
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.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.unfold.progress
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+
+/** Listener usable by tests with some handy assertions. */
+class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionProgressListener {
+
+ private val recordings: MutableList<UnfoldTransitionRecording> = arrayListOf()
+ private var currentRecording: UnfoldTransitionRecording? = null
+
+ override fun onTransitionStarted() {
+ assertWithMessage("Trying to start a transition when it is already in progress")
+ .that(currentRecording)
+ .isNull()
+
+ currentRecording = UnfoldTransitionRecording()
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ assertWithMessage("Received transition progress event when it's not started")
+ .that(currentRecording)
+ .isNotNull()
+ currentRecording!!.addProgress(progress)
+ }
+
+ override fun onTransitionFinishing() {
+ assertWithMessage("Received transition finishing event when it's not started")
+ .that(currentRecording)
+ .isNotNull()
+ currentRecording!!.onFinishing()
+ }
+
+ override fun onTransitionFinished() {
+ assertWithMessage("Received transition finish event when it's not started")
+ .that(currentRecording)
+ .isNotNull()
+ recordings += currentRecording!!
+ currentRecording = null
+ }
+
+ fun ensureTransitionFinished(): UnfoldTransitionRecording {
+ waitForCondition { recordings.size == 1 }
+ return recordings.first()
+ }
+
+ fun assertStarted() {
+ assertWithMessage("Transition didn't start").that(currentRecording).isNotNull()
+ }
+
+ fun assertNotStarted() {
+ assertWithMessage("Transition started").that(currentRecording).isNull()
+ }
+
+ fun assertLastProgress(progress: Float) {
+ currentRecording?.assertLastProgress(progress) ?: error("unfold not in progress.")
+ }
+
+ class UnfoldTransitionRecording {
+ private val progressHistory: MutableList<Float> = arrayListOf()
+ private var finishingInvocations: Int = 0
+
+ fun addProgress(progress: Float) {
+ assertThat(progress).isAtMost(1.0f)
+ assertThat(progress).isAtLeast(0.0f)
+
+ progressHistory += progress
+ }
+
+ fun onFinishing() {
+ finishingInvocations++
+ }
+
+ fun assertIncreasingProgress() {
+ assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
+ assertThat(progressHistory).isInOrder()
+ }
+
+ fun assertDecreasingProgress() {
+ assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
+ assertThat(progressHistory).isInOrder(Comparator.reverseOrder<Float>())
+ }
+
+ fun assertFinishedWithUnfold() {
+ assertThat(progressHistory).isNotEmpty()
+ assertThat(progressHistory.last()).isEqualTo(1.0f)
+ }
+
+ fun assertFinishedWithFold() {
+ assertThat(progressHistory).isNotEmpty()
+ assertThat(progressHistory.last()).isEqualTo(0.0f)
+ }
+
+ fun assertHasFoldAnimationAtTheEnd() {
+ // Check that there are at least a few decreasing events at the end
+ assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
+ assertThat(progressHistory.takeLast(MIN_ANIMATION_EVENTS))
+ .isInOrder(Comparator.reverseOrder<Float>())
+ assertThat(progressHistory.last()).isEqualTo(0.0f)
+ }
+
+ fun assertHasSingleFinishingEvent() {
+ assertWithMessage(
+ "onTransitionFinishing callback should be invoked exactly " + "one time"
+ )
+ .that(finishingInvocations)
+ .isEqualTo(1)
+ }
+
+ fun assertLastProgress(progress: Float) {
+ assertThat(progressHistory.last()).isEqualTo(progress)
+ }
+ }
+
+ private companion object {
+ private const val MIN_ANIMATION_EVENTS = 5
+ }
+}
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..0257ebd 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
@@ -28,7 +28,6 @@
import android.os.UserManager
import android.provider.Settings
import androidx.test.filters.SmallTest
-import com.android.internal.R.drawable.ic_account_circle
import com.android.internal.logging.UiEventLogger
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
@@ -87,6 +86,7 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var manager: UserManager
+ @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode
@Mock private lateinit var activityManager: ActivityManager
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@@ -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,8 +142,10 @@
KeyguardInteractor(
repository = keyguardRepository,
commandQueue = commandQueue,
+ featureFlags = featureFlags,
),
manager = manager,
+ headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
TelephonyInteractor(
@@ -844,6 +849,50 @@
assertThat(selectedUser()).isNotNull()
}
+ @Test
+ fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled() =
+ testScope.runTest {
+ keyguardRepository.setKeyguardShowing(true)
+ whenever(manager.getUserSwitchability(any()))
+ .thenReturn(UserManager.SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED)
+ val userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ isAddUsersFromLockscreen = true
+ )
+ )
+
+ runCurrent()
+ underTest.userRecords.value
+ .filter { it.info == null }
+ .forEach { action -> assertThat(action.isSwitchToEnabled).isFalse() }
+ }
+
+ @Test
+ fun userRecords_isActionAndNoUsersUnlocked_actionIsDisabled_HeadlessMode() =
+ testScope.runTest {
+ keyguardRepository.setKeyguardShowing(true)
+ whenever(headlessSystemUserMode.isHeadlessSystemUserMode()).thenReturn(true)
+ whenever(manager.isUserUnlocked(anyInt())).thenReturn(false)
+ val userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ isAddUsersFromLockscreen = true
+ )
+ )
+
+ runCurrent()
+ underTest.userRecords.value
+ .filter { it.info == null }
+ .forEach { action -> assertThat(action.isSwitchToEnabled).isFalse() }
+ }
+
private fun assertUsers(
models: List<UserModel>?,
count: Int,
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..2fedb87 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
@@ -41,6 +41,7 @@
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.util.mockito.mock
@@ -71,6 +72,7 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var activityManager: ActivityManager
@Mock private lateinit var manager: UserManager
+ @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var uiEventLogger: UiEventLogger
@@ -82,7 +84,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 +234,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,10 +250,11 @@
KeyguardInteractor(
repository = keyguardRepository,
commandQueue = commandQueue,
+ featureFlags = featureFlags,
),
- featureFlags =
- FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) },
+ featureFlags = featureFlags,
manager = manager,
+ headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
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..166b909 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
@@ -41,6 +41,7 @@
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
@@ -72,6 +73,7 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var activityManager: ActivityManager
@Mock private lateinit var manager: UserManager
+ @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var uiEventLogger: UiEventLogger
@@ -134,6 +136,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,12 +152,11 @@
KeyguardInteractor(
repository = keyguardRepository,
commandQueue = commandQueue,
+ featureFlags = featureFlags
),
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- },
+ featureFlags = featureFlags,
manager = manager,
+ headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
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..b367a60
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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;
+ }
+
+ public FakeConditionalCoreStartable(Monitor monitor, Callback callback) {
+ super(monitor);
+ 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);
+ }
+
+ @Test
+ public void testOnStartCallbackWithNoConditions() {
+ final CoreStartable coreStartable =
+ new FakeConditionalCoreStartable(mMonitor,
+ 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()).isEmpty();
+
+ 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/condition/SelfExecutingMonitor.java b/packages/SystemUI/tests/utils/src/com/android/systemui/condition/SelfExecutingMonitor.java
new file mode 100644
index 0000000..7ee05d0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/condition/SelfExecutingMonitor.java
@@ -0,0 +1,62 @@
+/*
+ * 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.condition;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+/**
+ * {@link SelfExecutingMonitor} creates a monitor that independently executes its logic through
+ * a {@link FakeExecutor}, which is ran at when a subscription is added and removed.
+ */
+public class SelfExecutingMonitor extends Monitor {
+ private final FakeExecutor mExecutor;
+
+ /**
+ * Default constructor that allows specifying the FakeExecutor to use.
+ */
+ public SelfExecutingMonitor(FakeExecutor executor) {
+ super(executor);
+ mExecutor = executor;
+ }
+
+ @Override
+ public Subscription.Token addSubscription(@NonNull Subscription subscription) {
+ final Subscription.Token result = super.addSubscription(subscription);
+ mExecutor.runAllReady();
+ return result;
+ }
+
+ @Override
+ public void removeSubscription(@NonNull Subscription.Token token) {
+ super.removeSubscription(token);
+ mExecutor.runNextReady();
+ }
+
+ /**
+ * Creates a {@link SelfExecutingMonitor} with a self-managed {@link FakeExecutor}. Use only
+ * for cases where condition state only will be set at when a subscription is added.
+ */
+ public static SelfExecutingMonitor createInstance() {
+ final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ final FakeExecutor mExecutor = new FakeExecutor(mFakeSystemClock);
+ return new SelfExecutingMonitor(mExecutor);
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/TestCoroutineSchedulerUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/TestCoroutineSchedulerUtils.kt
new file mode 100644
index 0000000..84e2a5c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/TestCoroutineSchedulerUtils.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.
+ */
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.coroutines
+
+import kotlin.time.Duration
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+
+/**
+ * Moves the virtual clock of this dispatcher forward by the specified [Duration].
+ *
+ * @see [TestCoroutineScheduler.advanceTimeBy]
+ */
+fun TestCoroutineScheduler.advanceTimeBy(duration: Duration) {
+ advanceTimeBy(duration.inWholeMilliseconds)
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
new file mode 100644
index 0000000..b395d9c
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.unfold
+
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
+import com.android.systemui.unfold.util.ATraceLoggerTransitionProgressListener
+import dagger.Module
+import dagger.Provides
+import java.util.Optional
+import javax.inject.Provider
+import javax.inject.Singleton
+
+/** Binds classes needed to provide unfold transition progresses to another process. */
+@Module
+class UnfoldRemoteModule {
+ @Provides
+ @Singleton
+ fun provideTransitionProvider(
+ config: UnfoldTransitionConfig,
+ traceListener: ATraceLoggerTransitionProgressListener,
+ remoteReceiverProvider: Provider<RemoteUnfoldTransitionReceiver>,
+ ): Optional<RemoteUnfoldTransitionReceiver> {
+ if (!config.isEnabled) {
+ return Optional.empty()
+ }
+ val remoteReceiver = remoteReceiverProvider.get()
+ remoteReceiver.addCallback(traceListener)
+ return Optional.of(remoteReceiver)
+ }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index cfb959e..068347c 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -24,6 +24,7 @@
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
+import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
@@ -68,3 +69,38 @@
val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
val rotationChangeProvider: RotationChangeProvider
}
+
+/**
+ * Generates a [RemoteTransitionProgress] usable to receive unfold transition progress from another
+ * process.
+ */
+@Singleton
+@Component(modules = [UnfoldRemoteModule::class])
+interface RemoteUnfoldSharedComponent {
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance context: Context,
+ @BindsInstance config: UnfoldTransitionConfig,
+ @BindsInstance @UnfoldMain executor: Executor,
+ @BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor,
+ @BindsInstance windowManager: IWindowManager,
+ @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
+ ): RemoteUnfoldSharedComponent
+ }
+
+ val remoteTransitionProgress: Optional<RemoteUnfoldTransitionReceiver>
+ val rotationChangeProvider: RotationChangeProvider
+}
+
+/**
+ * Usable to receive and propagate unfold transition progresses
+ *
+ * All unfold events received by [remoteReceiver] will be propagated to [localProvider].
+ * [remoteReceiver] is meant to receive events from a remote process (E.g. from a binder service).
+ */
+data class RemoteTransitionProgress(
+ val localProvider: UnfoldTransitionProgressProvider,
+ val remoteReceiver: UnfoldTransitionProgressProvider.TransitionProgressListener
+)
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 31616fa..5ffc094 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -19,6 +19,7 @@
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
+import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
import com.android.systemui.unfold.updates.DeviceFoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
@@ -102,4 +103,16 @@
EmptyHingeAngleProvider
}
}
+
+ @Provides
+ @Singleton
+ fun provideProgressForwarder(
+ config: UnfoldTransitionConfig,
+ progressForwarder: Provider<UnfoldTransitionProgressForwarder>
+ ): Optional<UnfoldTransitionProgressForwarder> {
+ if (!config.isEnabled) {
+ return Optional.empty()
+ }
+ return Optional.of(progressForwarder.get())
+ }
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index aa93c629..8eb79df 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -63,3 +63,26 @@
tracingTagPrefix,
windowManager,
)
+
+/**
+ * Factory for [RemoteUnfoldSharedComponent].
+ *
+ * Wraps [DaggerRemoteUnfoldSharedComponent] (that is autogenerated), for better discoverability.
+ */
+fun createRemoteUnfoldSharedComponent(
+ context: Context,
+ config: UnfoldTransitionConfig,
+ mainExecutor: Executor,
+ singleThreadBgExecutor: Executor,
+ tracingTagPrefix: String,
+ windowManager: IWindowManager,
+ ): RemoteUnfoldSharedComponent =
+ DaggerRemoteUnfoldSharedComponent.factory()
+ .create(
+ context,
+ config,
+ mainExecutor,
+ singleThreadBgExecutor,
+ windowManager,
+ tracingTagPrefix,
+ )
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/IUnfoldAnimation.aidl b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/IUnfoldAnimation.aidl
new file mode 100644
index 0000000..07a1db4
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/IUnfoldAnimation.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.unfold.progress;
+
+
+import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
+
+
+/**
+ * Interface exposed by System UI to allow remote process to register for unfold animation events.
+ */
+oneway interface IUnfoldAnimation {
+
+ /**
+ * Sets a listener for the animation.
+ *
+ * Only one listener is supported. If there are multiple, the earlier one will be overridden.
+ */
+ void setListener(in IUnfoldTransitionListener listener);
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/IUnfoldTransitionListener.aidl b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/IUnfoldTransitionListener.aidl
new file mode 100644
index 0000000..8f46b1b
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/IUnfoldTransitionListener.aidl
@@ -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.unfold.progress;
+
+
+/**
+ * Implemented by remote processes to receive unfold animation events from System UI.
+ */
+oneway interface IUnfoldTransitionListener {
+ /**
+ * Sent when unfold animation started.
+ */
+ void onTransitionStarted() = 1;
+
+ /**
+ * Sent when unfold animation progress changes.
+ */
+ void onTransitionProgress(float progress) = 2;
+
+ /**
+ * Sent when unfold animation finished.
+ */
+ void onTransitionFinished() = 3;
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiver.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiver.kt
new file mode 100644
index 0000000..5e4bcc9
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/RemoteUnfoldTransitionReceiver.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.unfold.progress
+
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.dagger.UnfoldMain
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Receives unfold events from remote senders (System UI).
+ *
+ * A binder to an instance to this class (created with [RemoteUnfoldTransitionReceiver.asBinder])
+ * should be sent to the remote process providing events.
+ */
+class RemoteUnfoldTransitionReceiver
+@Inject
+constructor(@UnfoldMain private val executor: Executor) :
+ UnfoldTransitionProgressProvider, IUnfoldTransitionListener.Stub() {
+
+ private val listeners: MutableSet<TransitionProgressListener> = mutableSetOf()
+
+ override fun onTransitionStarted() {
+ executor.execute { listeners.forEach { it.onTransitionStarted() } }
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ executor.execute { listeners.forEach { it.onTransitionProgress(progress) } }
+ }
+
+ override fun onTransitionFinished() {
+ executor.execute { listeners.forEach { it.onTransitionFinished() } }
+ }
+
+ override fun addCallback(listener: TransitionProgressListener) {
+ listeners += listener
+ }
+
+ override fun removeCallback(listener: TransitionProgressListener) {
+ listeners -= listener
+ }
+
+ override fun destroy() {
+ listeners.clear()
+ }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldTransitionProgressForwarder.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldTransitionProgressForwarder.kt
new file mode 100644
index 0000000..b654521
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldTransitionProgressForwarder.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.unfold.progress
+
+import android.os.RemoteException
+import android.util.Log
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import javax.inject.Inject
+
+/** Forwards received unfold events to [remoteListener], when present. */
+class UnfoldTransitionProgressForwarder @Inject constructor() :
+ TransitionProgressListener, IUnfoldAnimation.Stub() {
+
+ private var remoteListener: IUnfoldTransitionListener? = null
+
+ override fun onTransitionStarted() {
+ try {
+ Log.d(TAG, "onTransitionStarted")
+ remoteListener?.onTransitionStarted()
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed call onTransitionStarted", e)
+ }
+ }
+
+ override fun onTransitionFinished() {
+ try {
+ Log.d(TAG, "onTransitionFinished")
+ remoteListener?.onTransitionFinished()
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed call onTransitionFinished", e)
+ }
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ try {
+ remoteListener?.onTransitionProgress(progress)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed call onTransitionProgress", e)
+ }
+ }
+
+ override fun setListener(listener: IUnfoldTransitionListener?) {
+ remoteListener = listener
+ }
+
+ companion object {
+ private val TAG = UnfoldTransitionProgressForwarder::class.java.simpleName
+ }
+}
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/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 0589cfc..cf880eb 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1512,6 +1512,7 @@
case MSG_I_BT_SERVICE_DISCONNECTED_PROFILE:
if (msg.arg1 != BluetoothProfile.HEADSET) {
synchronized (mDeviceStateLock) {
+ mBtHelper.onBtProfileDisconnected(msg.arg1);
mDeviceInventory.onBtProfileDisconnected(msg.arg1);
}
} else {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b8e3a3a..9f15128 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3831,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
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/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 6cd42f8..f959821 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -279,7 +279,11 @@
}
AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index));
- mA2dp.setAvrcpAbsoluteVolume(index);
+ try {
+ mA2dp.setAvrcpAbsoluteVolume(index);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while changing abs volume", e);
+ }
}
/*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getA2dpCodec(
@@ -287,7 +291,12 @@
if (mA2dp == null) {
return AudioSystem.AUDIO_FORMAT_DEFAULT;
}
- final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device);
+ final BluetoothCodecStatus btCodecStatus = null;
+ try {
+ mA2dp.getCodecStatus(device);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while getting status of " + device, e);
+ }
if (btCodecStatus == null) {
return AudioSystem.AUDIO_FORMAT_DEFAULT;
}
@@ -421,7 +430,11 @@
}
AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex));
- mLeAudio.setVolume(volume);
+ try {
+ mLeAudio.setVolume(volume);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while setting LE volume", e);
+ }
}
/*package*/ synchronized void setHearingAidVolume(int index, int streamType,
@@ -447,7 +460,11 @@
AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
}
- mHearingAid.setVolume(gainDB);
+ try {
+ mHearingAid.setVolume(gainDB);
+ } catch (Exception e) {
+ Log.i(TAG, "Exception while setting hearing aid volume", e);
+ }
}
/*package*/ synchronized void onBroadcastScoConnectionState(int state) {
@@ -487,6 +504,35 @@
mBluetoothHeadset = null;
}
+ //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ /*package*/ synchronized void onBtProfileDisconnected(int profile) {
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dp = null;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAid = null;
+ break;
+ case BluetoothProfile.LE_AUDIO:
+ mLeAudio = null;
+ break;
+
+ case BluetoothProfile.A2DP_SINK:
+ case BluetoothProfile.LE_AUDIO_BROADCAST:
+ // shouldn't be received here as profile doesn't involve BtHelper
+ Log.e(TAG, "onBtProfileDisconnected: Not a profile handled by BtHelper "
+ + BluetoothProfile.getProfileName(profile));
+ break;
+
+ default:
+ // Not a valid profile to disconnect
+ Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect "
+ + BluetoothProfile.getProfileName(profile));
+ break;
+ }
+ }
+
+ //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
onHeadsetProfileConnected((BluetoothHeadset) proxy);
@@ -672,7 +718,6 @@
public void onServiceConnected(int profile, BluetoothProfile proxy) {
switch(profile) {
case BluetoothProfile.A2DP:
- case BluetoothProfile.A2DP_SINK:
case BluetoothProfile.HEADSET:
case BluetoothProfile.HEARING_AID:
case BluetoothProfile.LE_AUDIO:
@@ -682,6 +727,10 @@
mDeviceBroker.postBtProfileConnected(profile, proxy);
break;
+ case BluetoothProfile.A2DP_SINK:
+ // no A2DP sink functionality handled by BtHelper
+ case BluetoothProfile.LE_AUDIO_BROADCAST:
+ // no broadcast functionality handled by BtHelper
default:
break;
}
@@ -690,14 +739,19 @@
switch (profile) {
case BluetoothProfile.A2DP:
- case BluetoothProfile.A2DP_SINK:
case BluetoothProfile.HEADSET:
case BluetoothProfile.HEARING_AID:
case BluetoothProfile.LE_AUDIO:
- case BluetoothProfile.LE_AUDIO_BROADCAST:
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "BT profile service: disconnecting "
+ + BluetoothProfile.getProfileName(profile) + " profile"));
mDeviceBroker.postBtProfileDisconnected(profile);
break;
+ case BluetoothProfile.A2DP_SINK:
+ // no A2DP sink functionality handled by BtHelper
+ case BluetoothProfile.LE_AUDIO_BROADCAST:
+ // no broadcast functionality handled by BtHelper
default:
break;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 787bfb0..7d390415 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -228,7 +228,16 @@
@Override
public void onError(int errorCode, int vendorCode) {
- super.onError(errorCode, vendorCode);
+ if (getContext().getResources().getBoolean(R.bool.config_powerPressMapping)
+ && errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR
+ && vendorCode == getContext().getResources()
+ .getInteger(R.integer.config_powerPressCode)) {
+ // Translating vendor code to internal code
+ super.onError(BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED,
+ 0 /* vendorCode */);
+ } else {
+ super.onError(errorCode, vendorCode);
+ }
if (errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_BAD_CALIBRATION) {
BiometricNotificationUtils.showBadCalibrationNotification(getContext());
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 612d906..14b19fb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -37,6 +37,7 @@
import android.util.Slog;
import android.view.accessibility.AccessibilityManager;
+import com.android.internal.R;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -143,7 +144,17 @@
}
});
mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- super.onAcquired(acquiredInfo, vendorCode);
+
+ if (getContext().getResources().getBoolean(R.bool.config_powerPressMapping)
+ && acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR
+ && vendorCode == getContext().getResources()
+ .getInteger(R.integer.config_powerPressCode)) {
+ // Translating vendor code to internal code
+ super.onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED,
+ 0 /* vendorCode */);
+ } else {
+ super.onAcquired(acquiredInfo, vendorCode);
+ }
}
@Override
@@ -270,8 +281,5 @@
}
@Override
- public void onPowerPressed() {
- onAcquired(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED,
- 0 /* vendorCode */);
- }
+ public void onPowerPressed() {}
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 2b7fbfb..482dd7a 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -135,6 +135,8 @@
* </thermalThrottling>
*
* <refreshRate>
+ * <defaultRefreshRateInHbmHdr>75</defaultRefreshRateInHbmHdr>
+ * <defaultRefreshRateInHbmSunlight>75</defaultRefreshRateInHbmSunlight>
* <lowerBlockingZoneConfigs>
* <defaultRefreshRate>75</defaultRefreshRate>
* <blockingZoneThreshold>
@@ -404,6 +406,7 @@
private static final long STABLE_FLAG = 1L << 62;
private static final int DEFAULT_PEAK_REFRESH_RATE = 0;
private static final int DEFAULT_REFRESH_RATE = 60;
+ private static final int DEFAULT_REFRESH_RATE_IN_HBM = 0;
private static final int DEFAULT_LOW_REFRESH_RATE = 60;
private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
private static final int[] DEFAULT_BRIGHTNESS_THRESHOLDS = new int[]{};
@@ -585,6 +588,15 @@
private int mDefaultRefreshRate = DEFAULT_REFRESH_RATE;
/**
+ * Default refresh rate while the device has high brightness mode enabled for HDR.
+ */
+ private int mDefaultRefreshRateInHbmHdr = DEFAULT_REFRESH_RATE_IN_HBM;
+
+ /**
+ * Default refresh rate while the device has high brightness mode enabled for Sunlight.
+ */
+ private int mDefaultRefreshRateInHbmSunlight = DEFAULT_REFRESH_RATE_IN_HBM;
+ /**
* Default refresh rate in the high zone defined by brightness and ambient thresholds.
* If non-positive, then the refresh rate is unchanged even if thresholds are configured.
*/
@@ -1322,6 +1334,21 @@
}
/**
+ * @return Default refresh rate while the device has high brightness mode enabled for HDR.
+ */
+ public int getDefaultRefreshRateInHbmHdr() {
+ return mDefaultRefreshRateInHbmHdr;
+ }
+
+ /**
+ * @return Default refresh rate while the device has high brightness mode enabled because of
+ * high lux.
+ */
+ public int getDefaultRefreshRateInHbmSunlight() {
+ return mDefaultRefreshRateInHbmSunlight;
+ }
+
+ /**
* @return Default refresh rate in the higher blocking zone of the associated display
*/
public int getDefaultHighBlockingZoneRefreshRate() {
@@ -1474,6 +1501,8 @@
+ ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate
+ ", mDefaultPeakRefreshRate= " + mDefaultPeakRefreshRate
+ ", mDefaultRefreshRate= " + mDefaultRefreshRate
+ + ", mDefaultRefreshRateInHbmHdr= " + mDefaultRefreshRateInHbmHdr
+ + ", mDefaultRefreshRateInHbmSunlight= " + mDefaultRefreshRateInHbmSunlight
+ ", mLowDisplayBrightnessThresholds= "
+ Arrays.toString(mLowDisplayBrightnessThresholds)
+ ", mLowAmbientBrightnessThresholds= "
@@ -1789,6 +1818,7 @@
: refreshRateConfigs.getHigherBlockingZoneConfigs();
loadPeakDefaultRefreshRate(refreshRateConfigs);
loadDefaultRefreshRate(refreshRateConfigs);
+ loadDefaultRefreshRateInHbm(refreshRateConfigs);
loadLowerRefreshRateBlockingZones(lowerBlockingZoneConfig);
loadHigherRefreshRateBlockingZones(higherBlockingZoneConfig);
}
@@ -1813,6 +1843,26 @@
}
}
+ private void loadDefaultRefreshRateInHbm(RefreshRateConfigs refreshRateConfigs) {
+ if (refreshRateConfigs != null
+ && refreshRateConfigs.getDefaultRefreshRateInHbmHdr() != null) {
+ mDefaultRefreshRateInHbmHdr = refreshRateConfigs.getDefaultRefreshRateInHbmHdr()
+ .intValue();
+ } else {
+ mDefaultRefreshRateInHbmHdr = mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInHbmHdr);
+ }
+
+ if (refreshRateConfigs != null
+ && refreshRateConfigs.getDefaultRefreshRateInHbmSunlight() != null) {
+ mDefaultRefreshRateInHbmSunlight =
+ refreshRateConfigs.getDefaultRefreshRateInHbmSunlight().intValue();
+ } else {
+ mDefaultRefreshRateInHbmSunlight = mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInHbmSunlight);
+ }
+ }
+
/**
* Loads the refresh rate configurations pertaining to the upper blocking zones.
*/
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 7c6b667..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);
+ }
}
/**
@@ -2534,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();
@@ -2554,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
@@ -2789,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;
}
@@ -2858,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..97ab587 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -42,6 +42,7 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;
+import java.util.Objects;
/**
* Internal controller for starting and stopping the current dream and managing related state.
@@ -119,10 +120,20 @@
+ ", isPreviewMode=" + isPreviewMode + ", canDoze=" + canDoze
+ ", userId=" + userId + ", reason='" + reason + "'");
- if (mCurrentDream != null) {
- mPreviousDreams.add(mCurrentDream);
- }
+ final DreamRecord oldDream = mCurrentDream;
mCurrentDream = new DreamRecord(token, name, isPreviewMode, canDoze, userId, wakeLock);
+ if (oldDream != null) {
+ if (!oldDream.mWakingGently) {
+ // We will stop these previous dreams once the new dream is started.
+ mPreviousDreams.add(oldDream);
+ } else if (Objects.equals(oldDream.mName, mCurrentDream.mName)) {
+ // We are attempting to start a dream that is currently waking up gently.
+ // Let's silently stop the old instance here to clear the dream state.
+ // This should happen after the new mCurrentDream is set to avoid announcing
+ // a "dream stopped" state.
+ stopDreamInstance(/* immediately */ true, "restarting same dream", oldDream);
+ }
+ }
mCurrentDream.mDreamStartTime = SystemClock.elapsedRealtime();
MetricsLogger.visible(mContext,
@@ -244,8 +255,6 @@
}
mListener.onDreamStopped(dream.mToken);
- } else if (dream.mCanDoze && !mCurrentDream.mCanDoze) {
- mListener.stopDozing(dream.mToken);
}
} finally {
@@ -272,7 +281,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 +301,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/injector/SystemEmergencyHelper.java b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
index dc52990..1fb00ef 100644
--- a/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
+++ b/services/core/java/com/android/server/location/injector/SystemEmergencyHelper.java
@@ -16,6 +16,8 @@
package com.android.server.location.injector;
+import static com.android.server.location.LocationManagerService.TAG;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -23,6 +25,7 @@
import android.os.SystemClock;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
+import android.util.Log;
import com.android.server.FgThread;
@@ -67,8 +70,12 @@
}
synchronized (SystemEmergencyHelper.this) {
- mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(
- intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+ try {
+ mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(
+ intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Failed to call TelephonyManager.isEmergencyNumber().", e);
+ }
}
}
}, new IntentFilter(Intent.ACTION_NEW_OUTGOING_CALL));
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index e37222f..8b04c3a 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -22,6 +22,7 @@
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
@@ -1123,6 +1124,9 @@
if (options.isApplyMultipleTaskFlagForShortcut()) {
intents[0].addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
}
+ if (options.isApplyNoUserActionFlagForShortcut()) {
+ intents[0].addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
+ }
}
intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intents[0].setSourceBounds(sourceBounds);
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 651bb93..5d1a581 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3262,8 +3262,7 @@
}
final PowerGroup powerGroup = mPowerGroups.get(groupId);
wakefulness = powerGroup.getWakefulnessLocked();
- if ((wakefulness == WAKEFULNESS_DREAMING || wakefulness == WAKEFULNESS_DOZING) &&
- powerGroup.isSandmanSummonedLocked() && powerGroup.isReadyLocked()) {
+ if (powerGroup.isSandmanSummonedLocked() && powerGroup.isReadyLocked()) {
startDreaming = canDreamLocked(powerGroup) || canDozeLocked(powerGroup);
powerGroup.setSandmanSummonedLocked(/* isSandmanSummoned= */ false);
} else {
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/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 0ea6157..87f985a 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -181,6 +181,42 @@
|| !transitionGoodToGoForTaskFragments()) {
return;
}
+ final boolean isRecentsInOpening = mDisplayContent.mOpeningApps.stream().anyMatch(
+ ConfigurationContainer::isActivityTypeRecents);
+ // In order to avoid visual clutter caused by a conflict between app transition
+ // animation and recents animation, app transition is delayed until recents finishes.
+ // One exceptional case. When 3P launcher is used and a user taps a task screenshot in
+ // task switcher (isRecentsInOpening=true), app transition must start even though
+ // recents is running. Otherwise app transition is blocked until timeout (b/232984498).
+ // When 1P launcher is used, this animation is controlled by the launcher outside of
+ // the app transition, so delaying app transition doesn't cause visible delay. After
+ // recents finishes, app transition is handled just to commit visibility on apps.
+ if (!isRecentsInOpening) {
+ final ArraySet<WindowContainer> participants = new ArraySet<>();
+ participants.addAll(mDisplayContent.mOpeningApps);
+ participants.addAll(mDisplayContent.mChangingContainers);
+ boolean deferForRecents = false;
+ for (int i = 0; i < participants.size(); i++) {
+ WindowContainer wc = participants.valueAt(i);
+ final ActivityRecord activity = getAppFromContainer(wc);
+ if (activity == null) {
+ continue;
+ }
+ // Don't defer recents animation if one of activity isn't running for it, that one
+ // might be started from quickstep.
+ if (!activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+ deferForRecents = false;
+ break;
+ }
+ deferForRecents = true;
+ }
+ if (deferForRecents) {
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+ "Delaying app transition for recents animation to finish");
+ return;
+ }
+ }
+
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");
@@ -1249,27 +1285,12 @@
"Delaying app transition for screen rotation animation to finish");
return false;
}
- final boolean isRecentsInOpening = mDisplayContent.mOpeningApps.stream().anyMatch(
- ConfigurationContainer::isActivityTypeRecents);
for (int i = 0; i < apps.size(); i++) {
WindowContainer wc = apps.valueAt(i);
final ActivityRecord activity = getAppFromContainer(wc);
if (activity == null) {
continue;
}
- // In order to avoid visual clutter caused by a conflict between app transition
- // animation and recents animation, app transition is delayed until recents finishes.
- // One exceptional case. When 3P launcher is used and a user taps a task screenshot in
- // task switcher (isRecentsInOpening=true), app transition must start even though
- // recents is running. Otherwise app transition is blocked until timeout (b/232984498).
- // When 1P launcher is used, this animation is controlled by the launcher outside of
- // the app transition, so delaying app transition doesn't cause visible delay. After
- // recents finishes, app transition is handled just to commit visibility on apps.
- if (!isRecentsInOpening && activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Delaying app transition for recents animation to finish");
- return false;
- }
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Check opening app=%s: allDrawn=%b startingDisplayed=%b "
+ "startingMoved=%b isRelaunching()=%b startingWindow=%s",
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 71a7e26..b0eeceb 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3099,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.
*/
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index cf7d5d9..8e9a214 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2200,7 +2200,7 @@
* Called when an app has started replacing its main window.
*/
void addRelaunchingApp(ActivityRecord app) {
- if (mSystemBarColorApps.contains(app)) {
+ if (mSystemBarColorApps.contains(app) && !app.hasStartingWindow()) {
mRelaunchingSystemBarColorApps.add(app);
}
}
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/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 9681789..5a481f4 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -81,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;
@@ -104,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
@@ -114,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;
@@ -641,10 +648,9 @@
if (mLetterbox != null) {
outBounds.set(mLetterbox.getInnerFrame());
final WindowState w = mActivityRecord.findMainWindow();
- if (w == null) {
- return;
+ if (w != null) {
+ adjustBoundsForTaskbar(w, outBounds);
}
- adjustBoundsIfNeeded(w, outBounds);
} else {
outBounds.setEmpty();
}
@@ -994,7 +1000,7 @@
@VisibleForTesting
boolean shouldShowLetterboxUi(WindowState mainWindow) {
- return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
+ return isSurfaceVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
// activity is using blurred wallpaper for letterbox background.
@@ -1002,11 +1008,8 @@
}
@VisibleForTesting
- boolean isSurfaceReadyAndVisible(WindowState mainWindow) {
- boolean surfaceReady = mainWindow.isDrawn() // Regular case
- // Waiting for relayoutWindow to call preserveSurface
- || mainWindow.isDragResizeChanged();
- return surfaceReady && (mActivityRecord.isVisible()
+ boolean isSurfaceVisible(WindowState mainWindow) {
+ return mainWindow.isOnScreen() && (mActivityRecord.isVisible()
|| mActivityRecord.isVisibleRequested());
}
@@ -1079,7 +1082,12 @@
// It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo}
// because taskbar bounds used in {@link #adjustBoundsIfNeeded}
// are in screen coordinates
- adjustBoundsIfNeeded(mainWindow, cropBounds);
+ adjustBoundsForTaskbar(mainWindow, cropBounds);
+
+ final float scale = mainWindow.mInvGlobalScale;
+ if (scale != 1f && scale > 0f) {
+ cropBounds.scale(scale);
+ }
// ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface
// control is in the top left corner of an app window so offsetting bounds
@@ -1132,7 +1140,7 @@
return null;
}
- private void adjustBoundsIfNeeded(final WindowState mainWindow, final Rect bounds) {
+ private void adjustBoundsForTaskbar(final WindowState mainWindow, final Rect bounds) {
// Rounded corners should be displayed above the taskbar. When taskbar is hidden,
// an insets frame is equal to a navigation bar which shouldn't affect position of
// rounded corners since apps are expected to handle navigation bar inset.
@@ -1146,11 +1154,6 @@
// Rounded corners should be displayed above the expanded taskbar.
bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top);
}
-
- final float scale = mainWindow.mInvGlobalScale;
- if (scale != 1f && scale > 0f) {
- bounds.scale(scale);
- }
}
private int getInsetsStateCornerRadius(
@@ -1390,7 +1393,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
@@ -1466,6 +1470,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/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 00e3188..db8079a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -45,6 +45,7 @@
import static android.view.WindowManager.TransitionFlags;
import static android.view.WindowManager.TransitionType;
import static android.view.WindowManager.transitTypeToString;
+import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -1736,7 +1737,7 @@
? activityRecord.getOrganizedTaskFragment()
: taskFragment.getOrganizedTaskFragment();
if (organizedTf != null && organizedTf.getAnimationParams()
- .getAnimationBackgroundColor() != 0) {
+ .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
// This window is embedded and has an animation background color set on the
// TaskFragment. Pass this color with this window, so the handler can use it as
// the animation background color if needed,
@@ -1748,10 +1749,11 @@
final Task parentTask = activityRecord != null
? activityRecord.getTask()
: taskFragment.getTask();
- backgroundColor = ColorUtils.setAlphaComponent(
- parentTask.getTaskDescription().getBackgroundColor(), 255);
+ backgroundColor = parentTask.getTaskDescription().getBackgroundColor();
}
- change.setBackgroundColor(backgroundColor);
+ // Set to opaque for animation background to prevent it from exposing the blank
+ // background or content below.
+ change.setBackgroundColor(ColorUtils.setAlphaComponent(backgroundColor, 255));
}
change.setRotation(info.mRotation, endRotation);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9a20354..ce03244 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -33,6 +33,7 @@
import static android.view.SurfaceControl.Transaction;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -3168,7 +3169,7 @@
? activityRecord.getOrganizedTaskFragment()
: taskFragment.getOrganizedTaskFragment();
if (organizedTf != null && organizedTf.getAnimationParams()
- .getAnimationBackgroundColor() != 0) {
+ .getAnimationBackgroundColor() != DEFAULT_ANIMATION_BACKGROUND_COLOR) {
// This window is embedded and has an animation background color set on the
// TaskFragment. Pass this color with this window, so the handler can use it
// as the animation background color if needed,
@@ -3181,11 +3182,14 @@
final Task parentTask = activityRecord != null
? activityRecord.getTask()
: taskFragment.getTask();
- backgroundColorForTransition = ColorUtils.setAlphaComponent(
- parentTask.getTaskDescription().getBackgroundColor(), 255);
+ backgroundColorForTransition = parentTask.getTaskDescription()
+ .getBackgroundColor();
}
}
- animationRunnerBuilder.setTaskBackgroundColor(backgroundColorForTransition);
+ // Set to opaque for animation background to prevent it from exposing the blank
+ // background or content below.
+ animationRunnerBuilder.setTaskBackgroundColor(ColorUtils.setAlphaComponent(
+ backgroundColorForTransition, 255));
}
animationRunnerBuilder.build()
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 45dacbb..52f2b63 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;
}
@@ -6037,7 +6029,7 @@
Slog.i(TAG, "finishDrawing of orientation change: " + this + " " + duration + "ms");
mOrientationChangeRedrawRequestTime = 0;
} else if (mActivityRecord != null && mActivityRecord.mRelaunchStartTime != 0
- && mActivityRecord.findMainWindow() == this) {
+ && mActivityRecord.findMainWindow(false /* includeStartingApp */) == this) {
final long duration =
SystemClock.elapsedRealtime() - mActivityRecord.mRelaunchStartTime;
Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms");
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index abe48f8..91a1138 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -472,6 +472,14 @@
minOccurs="0" maxOccurs="1">
<xs:annotation name="final"/>
</xs:element>
+ <xs:element name="defaultRefreshRateInHbmHdr" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element name="defaultRefreshRateInHbmSunlight" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
<xs:element name="lowerBlockingZoneConfigs" type="blockingZoneConfig"
minOccurs="0" maxOccurs="1">
<xs:annotation name="final"/>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 2c97af5..1110d86 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -188,10 +188,14 @@
ctor public RefreshRateConfigs();
method public final java.math.BigInteger getDefaultPeakRefreshRate();
method public final java.math.BigInteger getDefaultRefreshRate();
+ method public final java.math.BigInteger getDefaultRefreshRateInHbmHdr();
+ method public final java.math.BigInteger getDefaultRefreshRateInHbmSunlight();
method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs();
method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs();
method public final void setDefaultPeakRefreshRate(java.math.BigInteger);
method public final void setDefaultRefreshRate(java.math.BigInteger);
+ method public final void setDefaultRefreshRateInHbmHdr(java.math.BigInteger);
+ method public final void setDefaultRefreshRateInHbmSunlight(java.math.BigInteger);
method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig);
}
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/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 3c735e3..4915c64 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,6 +37,7 @@
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
+import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
@@ -54,6 +57,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -335,6 +339,21 @@
showHideOverlay(c -> c.onLockoutPermanent());
}
+ @Test
+ public void testPowerPressForwardsErrorMessage() throws RemoteException {
+ final FingerprintAuthenticationClient client = createClient();
+ final int testVendorPowerPressCode = 1;
+ when(mContext.getOrCreateTestableResources().getResources()
+ .getBoolean(R.bool.config_powerPressMapping)).thenReturn(true);
+ when(mContext.getOrCreateTestableResources().getResources()
+ .getInteger(R.integer.config_powerPressCode)).thenReturn(testVendorPowerPressCode);
+
+ client.onError(FINGERPRINT_ERROR_VENDOR, testVendorPowerPressCode);
+
+ verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+ eq(BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED), anyInt());
+ }
+
private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block)
throws RemoteException {
final FingerprintAuthenticationClient client = createClient();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 837b553..7e29a76 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -16,7 +16,7 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
-import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import static com.google.common.truth.Truth.assertThat;
@@ -28,10 +28,10 @@
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.PointerContext;
@@ -48,6 +48,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -66,7 +67,6 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.ArrayList;
import java.util.function.Consumer;
@Presubmit
@@ -258,11 +258,16 @@
@Test
public void testPowerPressForwardsAcquireMessage() throws RemoteException {
final FingerprintEnrollClient client = createClient();
- client.start(mCallback);
- client.onPowerPressed();
+ final int testVendorPowerPressCode = 1;
+ when(mContext.getOrCreateTestableResources().getResources()
+ .getBoolean(R.bool.config_powerPressMapping)).thenReturn(true);
+ when(mContext.getOrCreateTestableResources().getResources()
+ .getInteger(R.integer.config_powerPressCode)).thenReturn(testVendorPowerPressCode);
+
+ client.onAcquired(FINGERPRINT_ACQUIRED_VENDOR, testVendorPowerPressCode);
verify(mClientMonitorCallbackConverter).onAcquired(anyInt(),
- eq(FINGERPRINT_ACQUIRED_POWER_PRESSED), anyInt());
+ eq(BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_POWER_PRESSED), anyInt());
}
private void showHideOverlay(Consumer<FingerprintEnrollClient> block)
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..8f70617 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};
@@ -156,6 +158,8 @@
assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
assertEquals(85, mDisplayDeviceConfig.getDefaultPeakRefreshRate());
assertEquals(45, mDisplayDeviceConfig.getDefaultRefreshRate());
+ assertEquals(82, mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr());
+ assertEquals(83, mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight());
assertArrayEquals(new int[]{45, 55},
mDisplayDeviceConfig.getLowDisplayBrightnessThresholds());
assertArrayEquals(new int[]{50, 60},
@@ -240,6 +244,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(),
@@ -459,6 +467,8 @@
+ "<refreshRate>\n"
+ "<defaultRefreshRate>45</defaultRefreshRate>\n"
+ "<defaultPeakRefreshRate>85</defaultPeakRefreshRate>\n"
+ + "<defaultRefreshRateInHbmHdr>82</defaultRefreshRateInHbmHdr>\n"
+ + "<defaultRefreshRateInHbmSunlight>83</defaultRefreshRateInHbmSunlight>\n"
+ "<lowerBlockingZoneConfigs>\n"
+ "<defaultRefreshRate>75</defaultRefreshRate>\n"
+ "<blockingZoneThreshold>\n"
@@ -578,6 +588,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 2cc6bd5..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
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/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 0d20f17..656c07b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -494,6 +494,7 @@
doReturn(insets).when(mainWindow).getInsetsState();
doReturn(attrs).when(mainWindow).getAttrs();
doReturn(true).when(mainWindow).isDrawn();
+ doReturn(true).when(mainWindow).isOnScreen();
doReturn(false).when(mainWindow).isLetterboxedForDisplayCutout();
doReturn(true).when(mainWindow).areAppWindowBoundsLetterboxed();
doReturn(true).when(mLetterboxConfiguration).isLetterboxActivityCornersRounded();
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 fb5fda1..de84655 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);
@@ -457,7 +502,7 @@
spyOn(mActivity.mLetterboxUiController);
doReturn(true).when(mActivity.mLetterboxUiController)
- .isSurfaceReadyAndVisible(any());
+ .isSurfaceVisible(any());
assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi(
mActivity.findMainWindow()));
@@ -1389,6 +1434,65 @@
}
@Test
+ public void testGetLetterboxInnerBounds_noScalingApplied() {
+ // Set up a display in portrait and ignoring orientation request.
+ final int dw = 1400;
+ final int dh = 2800;
+ setUpDisplaySizeWithApp(dw, dh);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ // Rotate display to landscape.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+ // Portrait fixed app without max aspect.
+ prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE);
+
+ // Need a window to call adjustBoundsForTaskbar with.
+ addWindowToActivity(mActivity);
+
+ // App should launch in fullscreen.
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(mActivity.inSizeCompatMode());
+
+ // Activity inherits max bounds from TaskDisplayArea.
+ assertMaxBoundsInheritDisplayAreaBounds();
+
+ // Rotate display to portrait.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_0);
+
+ final Rect rotatedDisplayBounds = new Rect(mActivity.mDisplayContent.getBounds());
+ final Rect rotatedActivityBounds = new Rect(mActivity.getBounds());
+ assertTrue(rotatedDisplayBounds.width() < rotatedDisplayBounds.height());
+
+ // App should be in size compat.
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertScaled();
+ assertThat(mActivity.inSizeCompatMode()).isTrue();
+ assertActivityMaxBoundsSandboxed();
+
+
+ final int scale = dh / dw;
+
+ // App bounds should be dh / scale x dw / scale
+ assertEquals(dw, rotatedDisplayBounds.width());
+ assertEquals(dh, rotatedDisplayBounds.height());
+
+ assertEquals(dh / scale, rotatedActivityBounds.width());
+ assertEquals(dw / scale, rotatedActivityBounds.height());
+
+ // Compute the frames of the window and invoke {@link ActivityRecord#layoutLetterbox}.
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ LetterboxDetails letterboxDetails = mActivity.mLetterboxUiController.getLetterboxDetails();
+
+ assertEquals(dh / scale, letterboxDetails.getLetterboxInnerBounds().width());
+ assertEquals(dw / scale, letterboxDetails.getLetterboxInnerBounds().height());
+
+ assertEquals(dw, letterboxDetails.getLetterboxFullBounds().width());
+ assertEquals(dh, letterboxDetails.getLetterboxFullBounds().height());
+ }
+
+ @Test
public void testLaunchWithFixedRotationTransform() {
final int dw = 1000;
final int dh = 2500;
@@ -3323,14 +3427,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,