Merge "Use `UnreleasedFlag` instead of `SysPropBooleanFlag`." into tm-qpr-dev
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index f623295d..6e02390 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -402,7 +402,7 @@
mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
mCallingUid);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
+ startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT);
return;
}
} catch (OperationCanceledException e) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 81c3e89..d24b677 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1684,7 +1684,7 @@
.isOnBackInvokedCallbackEnabled(this);
if (aheadOfTimeBack) {
// Add onBackPressed as default back behavior.
- mDefaultBackCallback = this::navigateBack;
+ mDefaultBackCallback = this::onBackInvoked;
getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
}
}
@@ -4002,22 +4002,19 @@
if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) {
return;
}
- navigateBack();
+ onBackInvoked();
}
- private void navigateBack() {
- if (!isTaskRoot()) {
- // If the activity is not the root of the task, allow finish to proceed normally.
- finishAfterTransition();
- return;
- }
- // Inform activity task manager that the activity received a back press while at the
- // root of the task. This call allows ActivityTaskManager to intercept or move the task
- // to the back.
- ActivityClient.getInstance().onBackPressedOnTaskRoot(mToken,
+ private void onBackInvoked() {
+ // Inform activity task manager that the activity received a back press.
+ // This call allows ActivityTaskManager to intercept or move the task
+ // to the back when needed.
+ ActivityClient.getInstance().onBackPressed(mToken,
new RequestFinishCallback(new WeakReference<>(this)));
- getAutofillClientController().onActivityBackPressed(mIntent);
+ if (isTaskRoot()) {
+ getAutofillClientController().onActivityBackPressed(mIntent);
+ }
}
/**
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index d1e6780..324b8e7 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -227,12 +227,13 @@
}
/**
- * Returns the windowing mode of the task that hosts the activity, or {@code -1} if task is not
- * found.
+ * Returns the {@link Configuration} of the task which hosts the Activity, or {@code null} if
+ * the task {@link Configuration} cannot be obtained.
*/
- public int getTaskWindowingMode(IBinder activityToken) {
+ @Nullable
+ public Configuration getTaskConfiguration(IBinder activityToken) {
try {
- return getActivityClientController().getTaskWindowingMode(activityToken);
+ return getActivityClientController().getTaskConfiguration(activityToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -525,9 +526,9 @@
}
}
- void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) {
+ void onBackPressed(IBinder token, IRequestFinishCallback callback) {
try {
- getActivityClientController().onBackPressedOnTaskRoot(token, callback);
+ getActivityClientController().onBackPressed(token, callback);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 51efdba..ef6c5a6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -40,7 +40,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
import android.app.RemoteServiceException.CrashedByAdbException;
import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
@@ -1975,9 +1974,6 @@
case ForegroundServiceDidNotStartInTimeException.TYPE_ID:
throw generateForegroundServiceDidNotStartInTimeException(message, extras);
- case CannotDeliverBroadcastException.TYPE_ID:
- throw new CannotDeliverBroadcastException(message);
-
case CannotPostForegroundServiceNotificationException.TYPE_ID:
throw new CannotPostForegroundServiceNotificationException(message);
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 5517c57..871d15e 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -407,6 +407,15 @@
*/
public static final int SUBREASON_PACKAGE_UPDATE = 25;
+ /**
+ * The process was killed because of undelivered broadcasts; this would be set only when the
+ * reason is {@link #REASON_OTHER}.
+ *
+ * For internal use only.
+ * @hide
+ */
+ public static final int SUBREASON_UNDELIVERED_BROADCAST = 26;
+
// If there is any OEM code which involves additional app kill reasons, it should
// be categorized in {@link #REASON_OTHER}, with subreason code starting from 1000.
@@ -579,6 +588,7 @@
SUBREASON_STOP_APP,
SUBREASON_KILL_BACKGROUND,
SUBREASON_PACKAGE_UPDATE,
+ SUBREASON_UNDELIVERED_BROADCAST,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SubReason {}
@@ -1283,6 +1293,8 @@
return "KILL BACKGROUND";
case SUBREASON_PACKAGE_UPDATE:
return "PACKAGE UPDATE";
+ case SUBREASON_UNDELIVERED_BROADCAST:
+ return "UNDELIVERED BROADCAST";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 0d14c0b..fae6887 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -188,17 +188,17 @@
}
@Override
- public WallpaperInfo getWallpaperInfo(int userId) {
+ public WallpaperInfo getWallpaperInfoForUser(int userId) {
return unsupported();
}
@Override
- public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which) {
+ public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which) {
return unsupported();
}
@Override
- public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
+ public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which, int userId) {
return unsupported();
}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 9aa67bc..8b655b9 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -78,7 +78,11 @@
boolean willActivityBeVisible(in IBinder token);
int getDisplayId(in IBinder activityToken);
int getTaskForActivity(in IBinder token, in boolean onlyRoot);
- int getTaskWindowingMode(in IBinder activityToken);
+ /**
+ * Returns the {@link Configuration} of the task which hosts the Activity, or {@code null} if
+ * the task {@link Configuration} cannot be obtained.
+ */
+ Configuration getTaskConfiguration(in IBinder activityToken);
IBinder getActivityTokenBelow(IBinder token);
ComponentName getCallingActivity(in IBinder token);
String getCallingPackage(in IBinder token);
@@ -145,10 +149,9 @@
void unregisterRemoteAnimations(in IBinder token);
/**
- * Reports that an Activity received a back key press when there were no additional activities
- * on the back stack.
+ * Reports that an Activity received a back key press.
*/
- oneway void onBackPressedOnTaskRoot(in IBinder activityToken,
+ oneway void onBackPressed(in IBinder activityToken,
in IRequestFinishCallback callback);
/** Reports that the splash screen view has attached to activity. */
diff --git a/core/java/android/app/IRequestFinishCallback.aidl b/core/java/android/app/IRequestFinishCallback.aidl
index 22c20c8..72426df 100644
--- a/core/java/android/app/IRequestFinishCallback.aidl
+++ b/core/java/android/app/IRequestFinishCallback.aidl
@@ -18,7 +18,7 @@
/**
* This callback allows ActivityTaskManager to ask the calling Activity
- * to finish in response to a call to onBackPressedOnTaskRoot.
+ * to finish in response to a call to onBackPressed.
*
* {@hide}
*/
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 2e83308..99f864c 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -57,8 +57,7 @@
* @deprecated IntentService is subject to all the
* <a href="{@docRoot}about/versions/oreo/background.html">background execution limits</a>
* imposed with Android 8.0 (API level 26). Consider using {@link androidx.work.WorkManager}
- * or {@link androidx.core.app.JobIntentService}, which uses jobs
- * instead of services when running on Android 8.0 or higher.
+ * instead.
*/
@Deprecated
public abstract class IntentService extends Service {
diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java
index e220627..620adbe 100644
--- a/core/java/android/app/RemoteServiceException.java
+++ b/core/java/android/app/RemoteServiceException.java
@@ -72,21 +72,6 @@
/**
* Exception used to crash an app process when the system received a RemoteException
- * while delivering a broadcast to an app process.
- *
- * @hide
- */
- public static class CannotDeliverBroadcastException extends RemoteServiceException {
- /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
- public static final int TYPE_ID = 2;
-
- public CannotDeliverBroadcastException(String msg) {
- super(msg);
- }
- }
-
- /**
- * Exception used to crash an app process when the system received a RemoteException
* while posting a notification of a foreground service.
*
* @hide
@@ -94,7 +79,7 @@
public static class CannotPostForegroundServiceNotificationException
extends RemoteServiceException {
/** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
- public static final int TYPE_ID = 3;
+ public static final int TYPE_ID = 2;
public CannotPostForegroundServiceNotificationException(String msg) {
super(msg);
@@ -109,7 +94,7 @@
*/
public static class BadForegroundServiceNotificationException extends RemoteServiceException {
/** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
- public static final int TYPE_ID = 4;
+ public static final int TYPE_ID = 3;
public BadForegroundServiceNotificationException(String msg) {
super(msg);
@@ -125,7 +110,7 @@
public static class MissingRequestPasswordComplexityPermissionException
extends RemoteServiceException {
/** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
- public static final int TYPE_ID = 5;
+ public static final int TYPE_ID = 4;
public MissingRequestPasswordComplexityPermissionException(String msg) {
super(msg);
@@ -139,7 +124,7 @@
*/
public static class CrashedByAdbException extends RemoteServiceException {
/** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
- public static final int TYPE_ID = 6;
+ public static final int TYPE_ID = 5;
public CrashedByAdbException(String msg) {
super(msg);
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index ae0fc09..719b5b6 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -229,6 +229,8 @@
public static final int CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = 1;
/** @hide */
public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2;
+ /** @hide */
+ public static final int CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE = 3;
/**
* Session flag for {@link #registerSessionListener} indicating the listener
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index b618969..ec100c2 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -115,6 +115,7 @@
import android.location.ICountryDetector;
import android.location.ILocationManager;
import android.location.LocationManager;
+import android.media.AudioDeviceVolumeManager;
import android.media.AudioManager;
import android.media.MediaFrameworkInitializer;
import android.media.MediaFrameworkPlatformInitializer;
@@ -339,6 +340,13 @@
return new AudioManager(ctx);
}});
+ registerService(Context.AUDIO_DEVICE_VOLUME_SERVICE, AudioDeviceVolumeManager.class,
+ new CachedServiceFetcher<AudioDeviceVolumeManager>() {
+ @Override
+ public AudioDeviceVolumeManager createService(ContextImpl ctx) {
+ return new AudioDeviceVolumeManager(ctx);
+ }});
+
registerService(Context.MEDIA_ROUTER_SERVICE, MediaRouter.class,
new CachedServiceFetcher<MediaRouter>() {
@Override
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index c99fa3d..3e6283e 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1132,12 +1132,26 @@
* @return the dimensions of system wallpaper
* @hide
*/
+ @Nullable
public Rect peekBitmapDimensions() {
return sGlobals.peekWallpaperDimensions(
mContext, true /* returnDefault */, mContext.getUserId());
}
/**
+ * Peek the dimensions of given wallpaper of the user without decoding it.
+ *
+ * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or
+ * {@link #FLAG_LOCK}.
+ * @return the dimensions of system wallpaper
+ * @hide
+ */
+ @Nullable
+ public Rect peekBitmapDimensions(@SetWallpaperFlags int which) {
+ return peekBitmapDimensions();
+ }
+
+ /**
* Get an open, readable file descriptor to the given wallpaper image file.
* The caller is responsible for closing the file descriptor when done ingesting the file.
*
@@ -1324,7 +1338,7 @@
* wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
*/
public WallpaperInfo getWallpaperInfo() {
- return getWallpaperInfo(mContext.getUserId());
+ return getWallpaperInfoForUser(mContext.getUserId());
}
/**
@@ -1334,7 +1348,7 @@
* @param userId Owner of the wallpaper.
* @hide
*/
- public WallpaperInfo getWallpaperInfo(int userId) {
+ public WallpaperInfo getWallpaperInfoForUser(int userId) {
try {
if (sGlobals.mService == null) {
Log.w(TAG, "WallpaperService not running");
@@ -1349,25 +1363,31 @@
/**
* Returns the information about the home screen wallpaper if its current wallpaper is a live
- * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
+ * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
+ * returns null.
*
- * @param which Specifies wallpaper destination (home or lock).
+ * @param which Specifies wallpaper to request (home or lock).
+ * @throws IllegalArgumentException if {@code which} is not exactly one of
+ * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
* @hide
*/
- public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which) {
+ public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which) {
return getWallpaperInfo();
}
/**
* Returns the information about the designated wallpaper if its current wallpaper is a live
- * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
+ * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, this
+ * returns null.
*
- * @param which Specifies wallpaper destination (home or lock).
+ * @param which Specifies wallpaper to request (home or lock).
* @param userId Owner of the wallpaper.
+ * @throws IllegalArgumentException if {@code which} is not exactly one of
+ * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
* @hide
*/
- public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
- return getWallpaperInfo(userId);
+ public WallpaperInfo getWallpaperInfo(@SetWallpaperFlags int which, int userId) {
+ return getWallpaperInfoForUser(userId);
}
/**
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index db3a192..d736a21 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -30,6 +30,8 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import dalvik.system.CloseGuard;
import java.util.List;
@@ -74,12 +76,12 @@
private static final String TAG = AppPredictor.class.getSimpleName();
-
private final IPredictionManager mPredictionManager;
private final CloseGuard mCloseGuard = CloseGuard.get();
private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
private final AppPredictionSessionId mSessionId;
+ @GuardedBy("itself")
private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
private final IBinder mToken = new Binder();
@@ -97,7 +99,7 @@
IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE);
mPredictionManager = IPredictionManager.Stub.asInterface(b);
mSessionId = new AppPredictionSessionId(
- context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
+ context.getPackageName() + ":" + UUID.randomUUID(), context.getUserId());
try {
mPredictionManager.createPredictionSession(predictionContext, mSessionId, mToken);
} catch (RemoteException e) {
@@ -158,6 +160,15 @@
*/
public void registerPredictionUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull AppPredictor.Callback callback) {
+ synchronized (mRegisteredCallbacks) {
+ registerPredictionUpdatesLocked(callbackExecutor, callback);
+ }
+ }
+
+ @GuardedBy("mRegisteredCallbacks")
+ private void registerPredictionUpdatesLocked(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull AppPredictor.Callback callback) {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
@@ -186,6 +197,13 @@
* @param callback The callback to be unregistered.
*/
public void unregisterPredictionUpdates(@NonNull AppPredictor.Callback callback) {
+ synchronized (mRegisteredCallbacks) {
+ unregisterPredictionUpdatesLocked(callback);
+ }
+ }
+
+ @GuardedBy("mRegisteredCallbacks")
+ private void unregisterPredictionUpdatesLocked(@NonNull AppPredictor.Callback callback) {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
@@ -238,7 +256,7 @@
}
try {
- mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice(targets),
+ mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice<>(targets),
new CallbackWrapper(callbackExecutor, callback));
} catch (RemoteException e) {
Log.e(TAG, "Failed to sort targets", e);
@@ -254,19 +272,25 @@
if (!mIsClosed.getAndSet(true)) {
mCloseGuard.close();
- // Do destroy;
- try {
- mPredictionManager.onDestroyPredictionSession(mSessionId);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to notify app target event", e);
- e.rethrowAsRuntimeException();
+ synchronized (mRegisteredCallbacks) {
+ destroySessionLocked();
}
- mRegisteredCallbacks.clear();
} else {
throw new IllegalStateException("This client has already been destroyed.");
}
}
+ @GuardedBy("mRegisteredCallbacks")
+ private void destroySessionLocked() {
+ try {
+ mPredictionManager.onDestroyPredictionSession(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify app target event", e);
+ e.rethrowAsRuntimeException();
+ }
+ mRegisteredCallbacks.clear();
+ }
+
@Override
protected void finalize() throws Throwable {
try {
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index 2cd1d96..10db337 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -23,9 +23,11 @@
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.util.Log;
import dalvik.system.CloseGuard;
@@ -70,7 +72,7 @@
* @hide
*/
@SystemApi
-public final class SearchSession implements AutoCloseable{
+public final class SearchSession implements AutoCloseable {
private static final String TAG = SearchSession.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -229,7 +231,14 @@
if (DEBUG) {
Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
}
- mExecutor.execute(() -> mCallback.accept(result.getList()));
+ List<SearchTarget> list = result.getList();
+ if (list.size() > 0) {
+ Bundle bundle = list.get(0).getExtras();
+ if (bundle != null) {
+ bundle.putLong("key_ipc_start", SystemClock.elapsedRealtime());
+ }
+ }
+ mExecutor.execute(() -> mCallback.accept(list));
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fce23cf..77ca48a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3846,6 +3846,7 @@
WIFI_RTT_RANGING_SERVICE,
NSD_SERVICE,
AUDIO_SERVICE,
+ AUDIO_DEVICE_VOLUME_SERVICE,
AUTH_SERVICE,
FINGERPRINT_SERVICE,
//@hide: FACE_SERVICE,
@@ -4687,6 +4688,17 @@
public static final String AUDIO_SERVICE = "audio";
/**
+ * @hide
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.media.AudioDeviceVolumeManager} for handling management of audio device
+ * (e.g. speaker, USB headset) volume.
+ *
+ * @see #getSystemService(String)
+ * @see android.media.AudioDeviceVolumeManager
+ */
+ public static final String AUDIO_DEVICE_VOLUME_SERVICE = "audio_device_volume";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a {@link
* android.media.MediaTranscodingManager} for transcoding media.
*
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 861a850..14d0a56 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2609,31 +2609,31 @@
* <table>
* <thead>
* <tr>
- * <th align="left">Input Format</th>
- * <th align="left">Output Format</th>
- * <th align="left">Capability</th>
+ * <th style="text-align: left;">Input Format</th>
+ * <th style="text-align: left;">Output Format</th>
+ * <th style="text-align: left;">Capability</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#PRIVATE }</td>
- * <td align="left">{@link android.graphics.ImageFormat#JPEG }</td>
- * <td align="left">PRIVATE_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: left;">PRIVATE_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#PRIVATE }</td>
- * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="left">PRIVATE_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: left;">PRIVATE_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="left">{@link android.graphics.ImageFormat#JPEG }</td>
- * <td align="left">YUV_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: left;">YUV_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="left">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="left">YUV_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: left;">YUV_REPROCESSING</td>
* </tr>
* </tbody>
* </table>
@@ -2650,26 +2650,26 @@
* <table>
* <thead>
* <tr>
- * <th align="left">Input Format</th>
- * <th align="left">Output Format</th>
- * <th align="left">Capability</th>
+ * <th style="text-align: left;">Input Format</th>
+ * <th style="text-align: left;">Output Format</th>
+ * <th style="text-align: left;">Capability</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#PRIVATE }</td>
- * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
- * <td align="left">PRIVATE_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+ * <td style="text-align: left;">PRIVATE_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
- * <td align="left">{@link android.graphics.ImageFormat#JPEG }</td>
- * <td align="left">YUV_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: left;">YUV_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
- * <td align="left">{@link android.graphics.ImageFormat#Y8 }</td>
- * <td align="left">YUV_REPROCESSING</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+ * <td style="text-align: left;">{@link android.graphics.ImageFormat#Y8 }</td>
+ * <td style="text-align: left;">YUV_REPROCESSING</td>
* </tr>
* </tbody>
* </table>
@@ -2705,60 +2705,60 @@
* <table>
* <thead>
* <tr>
- * <th align="center">Format</th>
- * <th align="center">Size</th>
- * <th align="center">Hardware Level</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">Format</th>
+ * <th style="text-align: center;">Size</th>
+ * <th style="text-align: center;">Hardware Level</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">1920x1080 (1080p)</td>
- * <td align="center">Any</td>
- * <td align="center">if 1080p <= activeArraySize</td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">1920x1080 (1080p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 1080p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">1280x720 (720)</td>
- * <td align="center">Any</td>
- * <td align="center">if 720p <= activeArraySize</td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">1280x720 (720)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 720p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">640x480 (480p)</td>
- * <td align="center">Any</td>
- * <td align="center">if 480p <= activeArraySize</td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">640x480 (480p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 480p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">320x240 (240p)</td>
- * <td align="center">Any</td>
- * <td align="center">if 240p <= activeArraySize</td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">320x240 (240p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 240p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">all output sizes available for JPEG</td>
- * <td align="center">FULL</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">all output sizes available for JPEG</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">all output sizes available for JPEG, up to the maximum video size</td>
- * <td align="center">LIMITED</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">all output sizes available for JPEG, up to the maximum video size</td>
+ * <td style="text-align: center;">LIMITED</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">IMPLEMENTATION_DEFINED</td>
- * <td align="center">same as YUV_420_888</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">IMPLEMENTATION_DEFINED</td>
+ * <td style="text-align: center;">same as YUV_420_888</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* </tbody>
* </table>
@@ -2773,66 +2773,66 @@
* <table>
* <thead>
* <tr>
- * <th align="center">Format</th>
- * <th align="center">Size</th>
- * <th align="center">Hardware Level</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">Format</th>
+ * <th style="text-align: center;">Size</th>
+ * <th style="text-align: center;">Hardware Level</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">JPEG</td>
- * <td align="center">1920x1080 (1080p)</td>
- * <td align="center">Any</td>
- * <td align="center">if 1080p <= activeArraySize</td>
+ * <td style="text-align: center;">JPEG</td>
+ * <td style="text-align: center;">1920x1080 (1080p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 1080p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
- * <td align="center">FULL</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">1920x1080 (1080p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 1080p <= activeArraySize</td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">1920x1080 (1080p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 1080p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">1280x720 (720)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 720p <= activeArraySize</td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">1280x720 (720)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 720p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">640x480 (480p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 480p <= activeArraySize</td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">640x480 (480p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 480p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">320x240 (240p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 240p <= activeArraySize</td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">320x240 (240p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 240p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">YUV_420_888</td>
- * <td align="center">all output sizes available for FULL hardware level, up to the maximum video size</td>
- * <td align="center">LIMITED</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">YUV_420_888</td>
+ * <td style="text-align: center;">all output sizes available for FULL hardware level, up to the maximum video size</td>
+ * <td style="text-align: center;">LIMITED</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">IMPLEMENTATION_DEFINED</td>
- * <td align="center">same as YUV_420_888</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">IMPLEMENTATION_DEFINED</td>
+ * <td style="text-align: center;">same as YUV_420_888</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* </tbody>
* </table>
@@ -2965,17 +2965,67 @@
* check if it limits the maximum size for image data.</p>
* <p>For applications targeting SDK version older than 31, the following table
* describes the minimum required output stream configurations based on the
- * hardware level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel}):
- * Format | Size | Hardware Level | Notes
- * :-------------------------------------------------:|:--------------------------------------------:|:--------------:|:--------------:
- * {@link android.graphics.ImageFormat#JPEG } | {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} (*1) | Any |
- * {@link android.graphics.ImageFormat#JPEG } | 1920x1080 (1080p) | Any | if 1080p <= activeArraySize
- * {@link android.graphics.ImageFormat#JPEG } | 1280x720 (720p) | Any | if 720p <= activeArraySize
- * {@link android.graphics.ImageFormat#JPEG } | 640x480 (480p) | Any | if 480p <= activeArraySize
- * {@link android.graphics.ImageFormat#JPEG } | 320x240 (240p) | Any | if 240p <= activeArraySize
- * {@link android.graphics.ImageFormat#YUV_420_888 } | all output sizes available for JPEG | FULL |
- * {@link android.graphics.ImageFormat#YUV_420_888 } | all output sizes available for JPEG, up to the maximum video size | LIMITED |
- * {@link android.graphics.ImageFormat#PRIVATE } | same as YUV_420_888 | Any |</p>
+ * hardware level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel}):</p>
+ * <table>
+ * <thead>
+ * <tr>
+ * <th style="text-align: center;">Format</th>
+ * <th style="text-align: center;">Size</th>
+ * <th style="text-align: center;">Hardware Level</th>
+ * <th style="text-align: center;">Notes</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} (*1)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: center;">1920x1080 (1080p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 1080p <= activeArraySize</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: center;">1280x720 (720p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 720p <= activeArraySize</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: center;">640x480 (480p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 480p <= activeArraySize</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: center;">320x240 (240p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 240p <= activeArraySize</td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">all output sizes available for JPEG</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;"></td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">all output sizes available for JPEG, up to the maximum video size</td>
+ * <td style="text-align: center;">LIMITED</td>
+ * <td style="text-align: center;"></td>
+ * </tr>
+ * <tr>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+ * <td style="text-align: center;">same as YUV_420_888</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
+ * </tr>
+ * </tbody>
+ * </table>
* <p>For applications targeting SDK version 31 or newer, if the mobile device declares to be
* media performance class 12 or higher by setting
* {@link android.os.Build.VERSION#MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
@@ -2987,66 +3037,66 @@
* <table>
* <thead>
* <tr>
- * <th align="center">Format</th>
- * <th align="center">Size</th>
- * <th align="center">Hardware Level</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">Format</th>
+ * <th style="text-align: center;">Size</th>
+ * <th style="text-align: center;">Hardware Level</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#JPEG }</td>
- * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} (*1)</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} (*1)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#JPEG }</td>
- * <td align="center">1920x1080 (1080p)</td>
- * <td align="center">Any</td>
- * <td align="center">if 1080p <= activeArraySize</td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#JPEG }</td>
+ * <td style="text-align: center;">1920x1080 (1080p)</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;">if 1080p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
- * <td align="center">FULL</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">{@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">1920x1080 (1080p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 1080p <= activeArraySize</td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">1920x1080 (1080p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 1080p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">1280x720 (720)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 720p <= activeArraySize</td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">1280x720 (720)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 720p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">640x480 (480p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 480p <= activeArraySize</td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">640x480 (480p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 480p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">320x240 (240p)</td>
- * <td align="center">FULL</td>
- * <td align="center">if 240p <= activeArraySize</td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">320x240 (240p)</td>
+ * <td style="text-align: center;">FULL</td>
+ * <td style="text-align: center;">if 240p <= activeArraySize</td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
- * <td align="center">all output sizes available for FULL hardware level, up to the maximum video size</td>
- * <td align="center">LIMITED</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#YUV_420_888 }</td>
+ * <td style="text-align: center;">all output sizes available for FULL hardware level, up to the maximum video size</td>
+ * <td style="text-align: center;">LIMITED</td>
+ * <td style="text-align: center;"></td>
* </tr>
* <tr>
- * <td align="center">{@link android.graphics.ImageFormat#PRIVATE }</td>
- * <td align="center">same as YUV_420_888</td>
- * <td align="center">Any</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">{@link android.graphics.ImageFormat#PRIVATE }</td>
+ * <td style="text-align: center;">same as YUV_420_888</td>
+ * <td style="text-align: center;">Any</td>
+ * <td style="text-align: center;"></td>
* </tr>
* </tbody>
* </table>
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 3e1deb2..1a15596 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -990,18 +990,18 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center"></td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device auto exposure algorithm is disabled</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device auto exposure algorithm is disabled</td>
* </tr>
* </tbody>
* </table>
@@ -1009,120 +1009,120 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device initiates AE scan</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values changing</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device initiates AE scan</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values changing</td>
* </tr>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">SEARCHING</td>
- * <td align="center">Camera device finishes AE scan</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Good values, not changing</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Camera device finishes AE scan</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Good values, not changing</td>
* </tr>
* <tr>
- * <td align="center">SEARCHING</td>
- * <td align="center">Camera device finishes AE scan</td>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Converged but too dark w/o flash</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Camera device finishes AE scan</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Converged but too dark w/o flash</td>
* </tr>
* <tr>
- * <td align="center">SEARCHING</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">CONVERGED</td>
- * <td align="center">Camera device initiates AE scan</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values changing</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Camera device initiates AE scan</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values changing</td>
* </tr>
* <tr>
- * <td align="center">CONVERGED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Camera device initiates AE scan</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values changing</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Camera device initiates AE scan</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values changing</td>
* </tr>
* <tr>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values not good after unlock</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values not good after unlock</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Values good after unlock</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Values good after unlock</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Exposure good, but too dark</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Exposure good, but too dark</td>
* </tr>
* <tr>
- * <td align="center">PRECAPTURE</td>
- * <td align="center">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Ready for high-quality capture</td>
+ * <td style="text-align: center;">PRECAPTURE</td>
+ * <td style="text-align: center;">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is OFF</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Ready for high-quality capture</td>
* </tr>
* <tr>
- * <td align="center">PRECAPTURE</td>
- * <td align="center">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Ready for high-quality capture</td>
+ * <td style="text-align: center;">PRECAPTURE</td>
+ * <td style="text-align: center;">Sequence done. {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Ready for high-quality capture</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">aeLock is ON and aePrecaptureTrigger is START</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Precapture trigger is ignored when AE is already locked</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">aeLock is ON and aePrecaptureTrigger is START</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Precapture trigger is ignored when AE is already locked</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">aeLock is ON and aePrecaptureTrigger is CANCEL</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Precapture trigger is ignored when AE is already locked</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">aeLock is ON and aePrecaptureTrigger is CANCEL</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Precapture trigger is ignored when AE is already locked</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START</td>
- * <td align="center">PRECAPTURE</td>
- * <td align="center">Start AE precapture metering sequence</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START</td>
+ * <td style="text-align: center;">PRECAPTURE</td>
+ * <td style="text-align: center;">Start AE precapture metering sequence</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Currently active precapture metering sequence is canceled</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Currently active precapture metering sequence is canceled</td>
* </tr>
* </tbody>
* </table>
@@ -1138,54 +1138,54 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device finished AE scan</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Values are already good, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device finished AE scan</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Values are already good, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Converged but too dark w/o flash after a precapture sequence, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Converged but too dark w/o flash after a precapture sequence, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Converged after a precapture sequence, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Converged after a precapture sequence, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Converged but too dark w/o flash after a precapture sequence is canceled, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Converged but too dark w/o flash after a precapture sequence is canceled, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state (excluding LOCKED)</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Converged after a precapture sequences canceled, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">Any state (excluding LOCKED)</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Converged after a precapture sequences canceled, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">CONVERGED</td>
- * <td align="center">Camera device finished AE scan</td>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Converged but too dark w/o flash after a new scan, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Camera device finished AE scan</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Converged but too dark w/o flash after a new scan, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">FLASH_REQUIRED</td>
- * <td align="center">Camera device finished AE scan</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Converged after a new scan, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">FLASH_REQUIRED</td>
+ * <td style="text-align: center;">Camera device finished AE scan</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Converged after a new scan, transient states are skipped by camera device.</td>
* </tr>
* </tbody>
* </table>
@@ -1413,18 +1413,18 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center"></td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Never changes</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Never changes</td>
* </tr>
* </tbody>
* </table>
@@ -1432,66 +1432,66 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">Start AF sweep, Lens now moving</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF sweep, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">AF sweep done</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Focused, Lens now locked</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">AF sweep done</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Focused, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">AF sweep done</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Not focused, Lens now locked</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">AF sweep done</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Not focused, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Cancel/reset AF, Lens now locked</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Cancel/reset AF, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Cancel/reset AF</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Cancel/reset AF</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">Start new sweep, Lens now moving</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">Start new sweep, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Cancel/reset AF</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Cancel/reset AF</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">ACTIVE_SCAN</td>
- * <td align="center">Start new sweep, Lens now moving</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">ACTIVE_SCAN</td>
+ * <td style="text-align: center;">Start new sweep, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">Any state</td>
- * <td align="center">Mode change</td>
- * <td align="center">INACTIVE</td>
- * <td align="center"></td>
+ * <td style="text-align: center;">Any state</td>
+ * <td style="text-align: center;">Mode change</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;"></td>
* </tr>
* </tbody>
* </table>
@@ -1504,36 +1504,36 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Focus is already good or good after a scan, lens is now locked.</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Focus is already good or good after a scan, lens is now locked.</td>
* </tr>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Focus failed after a scan, lens is now locked.</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Focus failed after a scan, lens is now locked.</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Focus is already good or good after a scan, lens is now locked.</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Focus is already good or good after a scan, lens is now locked.</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Focus is good after a scan, lens is not locked.</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Focus is good after a scan, lens is not locked.</td>
* </tr>
* </tbody>
* </table>
@@ -1541,102 +1541,102 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF state query, Lens now locked</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF state query, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Camera device completes current scan</td>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">End AF scan, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Camera device completes current scan</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">End AF scan, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Camera device fails current scan</td>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">End AF scan, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Camera device fails current scan</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">End AF scan, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Immediate transition, if focus is good. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate transition, if focus is good. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Immediate transition, if focus is bad. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate transition, if focus is bad. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Reset lens position, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Reset lens position, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Immediate transition, lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate transition, lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Immediate transition, lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate transition, lens now locked</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">No effect</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">No effect</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Restart AF scan</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Restart AF scan</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">No effect</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">No effect</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Restart AF scan</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Restart AF scan</td>
* </tr>
* </tbody>
* </table>
@@ -1644,102 +1644,102 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF state query, Lens now locked</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF state query, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Camera device completes current scan</td>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">End AF scan, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Camera device completes current scan</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">End AF scan, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Camera device fails current scan</td>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">End AF scan, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Camera device fails current scan</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">End AF scan, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Eventual transition once the focus is good. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Eventual transition once the focus is good. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Eventual transition if cannot find focus. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Eventual transition if cannot find focus. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Reset lens position, Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Reset lens position, Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">Camera device initiates new scan</td>
- * <td align="center">PASSIVE_SCAN</td>
- * <td align="center">Start AF scan, Lens now moving</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">Camera device initiates new scan</td>
+ * <td style="text-align: center;">PASSIVE_SCAN</td>
+ * <td style="text-align: center;">Start AF scan, Lens now moving</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_FOCUSED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">Immediate trans. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_FOCUSED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate trans. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">PASSIVE_UNFOCUSED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">Immediate trans. Lens now locked</td>
+ * <td style="text-align: center;">PASSIVE_UNFOCUSED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">Immediate trans. Lens now locked</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">No effect</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">No effect</td>
* </tr>
* <tr>
- * <td align="center">FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Restart AF scan</td>
+ * <td style="text-align: center;">FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Restart AF scan</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_TRIGGER</td>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">No effect</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_TRIGGER</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">No effect</td>
* </tr>
* <tr>
- * <td align="center">NOT_FOCUSED_LOCKED</td>
- * <td align="center">AF_CANCEL</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Restart AF scan</td>
+ * <td style="text-align: center;">NOT_FOCUSED_LOCKED</td>
+ * <td style="text-align: center;">AF_CANCEL</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Restart AF scan</td>
* </tr>
* </tbody>
* </table>
@@ -1751,30 +1751,30 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">any state</td>
- * <td align="center">CAF-->AUTO mode switch</td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Mode switch without trigger, initial state must be INACTIVE</td>
+ * <td style="text-align: center;">any state</td>
+ * <td style="text-align: center;">CAF-->AUTO mode switch</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Mode switch without trigger, initial state must be INACTIVE</td>
* </tr>
* <tr>
- * <td align="center">any state</td>
- * <td align="center">CAF-->AUTO mode switch with AF_TRIGGER</td>
- * <td align="center">trigger-reachable states from INACTIVE</td>
- * <td align="center">Mode switch with trigger, INACTIVE is skipped</td>
+ * <td style="text-align: center;">any state</td>
+ * <td style="text-align: center;">CAF-->AUTO mode switch with AF_TRIGGER</td>
+ * <td style="text-align: center;">trigger-reachable states from INACTIVE</td>
+ * <td style="text-align: center;">Mode switch with trigger, INACTIVE is skipped</td>
* </tr>
* <tr>
- * <td align="center">any state</td>
- * <td align="center">AUTO-->CAF mode switch</td>
- * <td align="center">passively reachable states from INACTIVE</td>
- * <td align="center">Mode switch without trigger, passive transient state is skipped</td>
+ * <td style="text-align: center;">any state</td>
+ * <td style="text-align: center;">AUTO-->CAF mode switch</td>
+ * <td style="text-align: center;">passively reachable states from INACTIVE</td>
+ * <td style="text-align: center;">Mode switch without trigger, passive transient state is skipped</td>
* </tr>
* </tbody>
* </table>
@@ -2053,18 +2053,18 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center"></td>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device auto white balance algorithm is disabled</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;"></td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device auto white balance algorithm is disabled</td>
* </tr>
* </tbody>
* </table>
@@ -2072,54 +2072,54 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device initiates AWB scan</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values changing</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device initiates AWB scan</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values changing</td>
* </tr>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">SEARCHING</td>
- * <td align="center">Camera device finishes AWB scan</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Good values, not changing</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Camera device finishes AWB scan</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Good values, not changing</td>
* </tr>
* <tr>
- * <td align="center">SEARCHING</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">CONVERGED</td>
- * <td align="center">Camera device initiates AWB scan</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values changing</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Camera device initiates AWB scan</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values changing</td>
* </tr>
* <tr>
- * <td align="center">CONVERGED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
- * <td align="center">LOCKED</td>
- * <td align="center">Values locked</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is ON</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">Values locked</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
- * <td align="center">SEARCHING</td>
- * <td align="center">Values not good after unlock</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
+ * <td style="text-align: center;">SEARCHING</td>
+ * <td style="text-align: center;">Values not good after unlock</td>
* </tr>
* </tbody>
* </table>
@@ -2132,24 +2132,24 @@
* <table>
* <thead>
* <tr>
- * <th align="center">State</th>
- * <th align="center">Transition Cause</th>
- * <th align="center">New State</th>
- * <th align="center">Notes</th>
+ * <th style="text-align: center;">State</th>
+ * <th style="text-align: center;">Transition Cause</th>
+ * <th style="text-align: center;">New State</th>
+ * <th style="text-align: center;">Notes</th>
* </tr>
* </thead>
* <tbody>
* <tr>
- * <td align="center">INACTIVE</td>
- * <td align="center">Camera device finished AWB scan</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Values are already good, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">INACTIVE</td>
+ * <td style="text-align: center;">Camera device finished AWB scan</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Values are already good, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">LOCKED</td>
- * <td align="center">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
- * <td align="center">CONVERGED</td>
- * <td align="center">Values good after unlock, transient states are skipped by camera device.</td>
+ * <td style="text-align: center;">LOCKED</td>
+ * <td style="text-align: center;">{@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} is OFF</td>
+ * <td style="text-align: center;">CONVERGED</td>
+ * <td style="text-align: center;">Values good after unlock, transient states are skipped by camera device.</td>
* </tr>
* </tbody>
* </table>
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index bdd45e6..dba1a5e 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -52,6 +52,22 @@
/** The maximum allowed device state identifier. */
public static final int MAXIMUM_DEVICE_STATE = 255;
+ /**
+ * Intent needed to launch the rear display overlay activity from SysUI
+ *
+ * @hide
+ */
+ public static final String ACTION_SHOW_REAR_DISPLAY_OVERLAY =
+ "com.android.intent.action.SHOW_REAR_DISPLAY_OVERLAY";
+
+ /**
+ * Intent extra sent to the rear display overlay activity of the current base state
+ *
+ * @hide
+ */
+ public static final String EXTRA_ORIGINAL_DEVICE_BASE_STATE =
+ "original_device_base_state";
+
private final DeviceStateManagerGlobal mGlobal;
/** @hide */
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 738045d..7756b9c 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -51,7 +51,7 @@
* connection with the device state service couldn't be established.
*/
@Nullable
- static DeviceStateManagerGlobal getInstance() {
+ public static DeviceStateManagerGlobal getInstance() {
synchronized (DeviceStateManagerGlobal.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
@@ -259,6 +259,22 @@
}
}
+ /**
+ * Provides notification to the system server that a device state feature overlay
+ * was dismissed. This should only be called from the {@link android.app.Activity} that
+ * was showing the overlay corresponding to the feature.
+ *
+ * Validation of there being an overlay visible and pending state request is handled on the
+ * system server.
+ */
+ public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) {
+ try {
+ mDeviceStateManager.onStateRequestOverlayDismissed(shouldCancelRequest);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private void registerCallbackIfNeededLocked() {
if (mCallback == null) {
mCallback = new DeviceStateManagerCallback();
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index 7175eae..0993160 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -103,4 +103,15 @@
@JavaPassthrough(annotation=
"@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
void cancelBaseStateOverride();
+
+ /**
+ * Notifies the system service that the educational overlay that was launched
+ * before entering a requested state was dismissed or closed, and provides
+ * the system information on if the pairing mode should be canceled or not.
+ *
+ * This should only be called from the overlay itself.
+ */
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
+ void onStateRequestOverlayDismissed(boolean shouldCancelRequest);
}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 085bfca..5c1da11 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -938,7 +938,7 @@
public void onPointerDown(long requestId, int sensorId, int x, int y,
float minor, float major) {
if (mService == null) {
- Slog.w(TAG, "onFingerDown: no fingerprint service");
+ Slog.w(TAG, "onPointerDown: no fingerprint service");
return;
}
@@ -955,7 +955,7 @@
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
public void onPointerUp(long requestId, int sensorId) {
if (mService == null) {
- Slog.w(TAG, "onFingerDown: no fingerprint service");
+ Slog.w(TAG, "onPointerUp: no fingerprint service");
return;
}
@@ -967,6 +967,58 @@
}
/**
+ * TODO(b/218388821): The parameter list should be replaced with PointerContext.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void onPointerDown(
+ long requestId,
+ int sensorId,
+ int pointerId,
+ float x,
+ float y,
+ float minor,
+ float major,
+ float orientation,
+ long time,
+ long gestureStart,
+ boolean isAod) {
+ if (mService == null) {
+ Slog.w(TAG, "onPointerDown: no fingerprint service");
+ return;
+ }
+
+ // TODO(b/218388821): Propagate all the parameters to FingerprintService.
+ Slog.e(TAG, "onPointerDown: not implemented!");
+ }
+
+ /**
+ * TODO(b/218388821): The parameter list should be replaced with PointerContext.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void onPointerUp(
+ long requestId,
+ int sensorId,
+ int pointerId,
+ float x,
+ float y,
+ float minor,
+ float major,
+ float orientation,
+ long time,
+ long gestureStart,
+ boolean isAod) {
+ if (mService == null) {
+ Slog.w(TAG, "onPointerUp: no fingerprint service");
+ return;
+ }
+
+ // TODO(b/218388821): Propagate all the parameters to FingerprintService.
+ Slog.e(TAG, "onPointerUp: not implemented!");
+ }
+
+ /**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index a645ae4..3a07bb0 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -160,4 +160,9 @@
void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
+
+ @EnforcePermission("MONITOR_INPUT")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.MONITOR_INPUT)")
+ void pilferPointers(IBinder inputChannelToken);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 97812ce..d8ed124 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -30,6 +30,7 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.BatteryState;
import android.hardware.SensorManager;
import android.hardware.lights.Light;
@@ -1819,6 +1820,34 @@
}
/**
+ * Pilfer pointers from an input channel.
+ *
+ * Takes all the current pointer event streams that are currently being sent to the given
+ * input channel and generates appropriate cancellations for all other windows that are
+ * receiving these pointers.
+ *
+ * This API is intended to be used in conjunction with spy windows. When a spy window pilfers
+ * pointers, the foreground windows and all other spy windows that are receiving any of the
+ * pointers that are currently being dispatched to the pilfering window will have those pointers
+ * canceled. Only the pilfering window will continue to receive events for the affected pointers
+ * until the pointer is lifted.
+ *
+ * This method should be used with caution as unexpected pilfering can break fundamental user
+ * interactions.
+ *
+ * @see android.os.InputConfig#SPY
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MONITOR_INPUT)
+ public void pilferPointers(IBinder inputChannelToken) {
+ try {
+ mIm.pilferPointers(inputChannelToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Removes a previously registered battery listener for an input device.
* @see #addInputDeviceBatteryListener(int, Executor, InputDeviceBatteryListener)
* @hide
@@ -1866,6 +1895,31 @@
}
/**
+ * Whether stylus has ever been used on device (false by default).
+ * @hide
+ */
+ public boolean isStylusEverUsed(@NonNull Context context) {
+ return Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.STYLUS_EVER_USED, 0) == 1;
+ }
+
+ /**
+ * Set whether stylus has ever been used on device.
+ * Should only ever be set to true once after stylus first usage.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void setStylusEverUsed(@NonNull Context context, boolean stylusEverUsed) {
+ if (context.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need WRITE_SECURE_SETTINGS permission "
+ + "to set stylus ever used.");
+ }
+ Settings.Global.putInt(context.getContentResolver(),
+ Settings.Global.STYLUS_EVER_USED, stylusEverUsed ? 1 : 0);
+ }
+
+ /**
* A callback used to be notified about battery state changes for an input device. The
* {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
* listener is successfully registered to provide the initial battery state of the device.
diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java
index 97ec594..9bad0de 100644
--- a/core/java/android/os/BatteryManagerInternal.java
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -47,6 +47,14 @@
public abstract int getBatteryLevel();
/**
+ * Returns battery health status as an integer representing the current battery health constant.
+ *
+ * This is a simple accessor that's safe to be called from any locks, but internally it may
+ * wait on the battery service lock.
+ */
+ public abstract int getBatteryHealth();
+
+ /**
* Instantaneous battery capacity in uA-h, as defined in the HealthInfo HAL struct.
* Please note apparently it could be bigger than {@link #getBatteryFullCharge}.
*
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 6091bf9..bf72b1d 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.hardware.vibrator.IVibrator;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Range;
@@ -313,8 +314,14 @@
private static final float EPSILON = 1e-5f;
public MultiVibratorInfo(VibratorInfo[] vibrators) {
+ // Need to use an extra constructor to share the computation in super initialization.
+ this(vibrators, frequencyProfileIntersection(vibrators));
+ }
+
+ private MultiVibratorInfo(VibratorInfo[] vibrators,
+ VibratorInfo.FrequencyProfile mergedProfile) {
super(/* id= */ -1,
- capabilitiesIntersection(vibrators),
+ capabilitiesIntersection(vibrators, mergedProfile.isEmpty()),
supportedEffectsIntersection(vibrators),
supportedBrakingIntersection(vibrators),
supportedPrimitivesAndDurationsIntersection(vibrators),
@@ -323,14 +330,19 @@
integerLimitIntersection(vibrators, VibratorInfo::getPwlePrimitiveDurationMax),
integerLimitIntersection(vibrators, VibratorInfo::getPwleSizeMax),
floatPropertyIntersection(vibrators, VibratorInfo::getQFactor),
- frequencyProfileIntersection(vibrators));
+ mergedProfile);
}
- private static int capabilitiesIntersection(VibratorInfo[] infos) {
+ private static int capabilitiesIntersection(VibratorInfo[] infos,
+ boolean frequencyProfileIsEmpty) {
int intersection = ~0;
for (VibratorInfo info : infos) {
intersection &= info.getCapabilities();
}
+ if (frequencyProfileIsEmpty) {
+ // Revoke frequency control if the merged frequency profile ended up empty.
+ intersection &= ~IVibrator.CAP_FREQUENCY_CONTROL;
+ }
return intersection;
}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 98fe666..516d80e 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -313,6 +313,13 @@
public static final String NAMESPACE_MEDIA_NATIVE = "media_native";
/**
+ * Namespace for all Kernel Multi-Gen LRU feature.
+ *
+ * @hide
+ */
+ public static final String NAMESPACE_MGLRU_NATIVE = "mglru_native";
+
+ /**
* Namespace for all netd related features.
*
* @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a955dbb..ed59456 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7776,6 +7776,13 @@
"high_text_contrast_enabled";
/**
+ * The color contrast, float in [-1, 1], 1 being the highest contrast.
+ *
+ * @hide
+ */
+ public static final String CONTRAST_LEVEL = "contrast_level";
+
+ /**
* Setting that specifies whether the display magnification is enabled via a system-wide
* triple tap gesture. Display magnifications allows the user to zoom in the display content
* and is targeted to low vision users. The current magnification scale is controlled by
@@ -10309,11 +10316,11 @@
public static final String QS_AUTO_ADDED_TILES = "qs_auto_tiles";
/**
- * The duration of timeout, in milliseconds, to switch from a non-primary user to the
- * primary user when the device is docked.
+ * The duration of timeout, in milliseconds, to switch from a non-Dock User to the
+ * Dock User when the device is docked.
* @hide
*/
- public static final String TIMEOUT_TO_USER_ZERO = "timeout_to_user_zero";
+ public static final String TIMEOUT_TO_DOCK_USER = "timeout_to_dock_user";
/**
* Backup manager behavioral parameters.
@@ -15788,6 +15795,15 @@
public static final String STYLUS_HANDWRITING_ENABLED = "stylus_handwriting_enabled";
/**
+ * Indicates whether a stylus has ever been used on the device.
+ *
+ * @hide
+ */
+ @Readable
+ @SuppressLint("NoSettingsProvider")
+ public static final String STYLUS_EVER_USED = "stylus_ever_used";
+
+ /**
* Exemptions to the hidden API blacklist.
*
* @hide
@@ -18441,6 +18457,9 @@
/**
* Activity Action: For system or preinstalled apps to show their {@link Activity} embedded
* in Settings app on large screen devices.
+ *
+ * Developers should resolve the Intent action before using it.
+ *
* <p>
* Input: {@link #EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI} must be included to
* specify the intent for the activity which will be embedded in Settings app.
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index cd38e8a..7c2f952 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -58,4 +58,39 @@
* @param isScreenOn True if the screen is currently on.
*/
public abstract boolean canStartDreaming(boolean isScreenOn);
+
+ /**
+ * Return whether dreams can continue when undocking by default. Even if the default is true,
+ * it can be overridden temporarily, in which case {@link DreamManagerStateListener} will be
+ * informed of any changes.
+ */
+ public abstract boolean keepDreamingWhenUndockedDefault();
+
+ /**
+ * Register a {@link DreamManagerStateListener}, which will be called when there are changes to
+ * dream state.
+ *
+ * @param listener The listener to register.
+ */
+ public abstract void registerDreamManagerStateListener(DreamManagerStateListener listener);
+
+ /**
+ * Unregister a {@link DreamManagerStateListener}, which will be called when there are changes
+ * to dream state.
+ *
+ * @param listener The listener to unregister.
+ */
+ public abstract void unregisterDreamManagerStateListener(DreamManagerStateListener listener);
+
+ /**
+ * Called when there are changes to dream state.
+ */
+ public interface DreamManagerStateListener {
+ /**
+ * Called when keep dreaming when undocked has changed.
+ *
+ * @param keepDreaming True if the current dream should continue when undocking.
+ */
+ void onKeepDreamingWhenUndockedChanged(boolean keepDreaming);
+ }
}
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
index aa45c20..6e8198b 100644
--- a/core/java/android/service/dreams/DreamOverlayService.java
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -49,6 +49,17 @@
mShowComplications = shouldShowComplications;
onStartDream(layoutParams);
}
+
+ @Override
+ public void wakeUp() {
+ onWakeUp(() -> {
+ try {
+ mDreamOverlayCallback.onWakeUpComplete();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not notify dream of wakeUp:" + e);
+ }
+ });
+ }
};
IDreamOverlayCallback mDreamOverlayCallback;
@@ -71,6 +82,17 @@
public abstract void onStartDream(@NonNull WindowManager.LayoutParams layoutParams);
/**
+ * This method is overridden by implementations to handle when the dream has been requested
+ * to wakeup. This allows any overlay animations to run.
+ *
+ * @param onCompleteCallback The callback to trigger to notify the dream service that the
+ * overlay has completed waking up.
+ * @hide
+ */
+ public void onWakeUp(@NonNull Runnable onCompleteCallback) {
+ }
+
+ /**
* This method is invoked to request the dream exit.
*/
public final void requestExit() {
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 32bdf79..8b9852a 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -312,7 +312,14 @@
@Override
public void onExitRequested() {
// Simply finish dream when exit is requested.
- finish();
+ mHandler.post(() -> finish());
+ }
+
+ @Override
+ public void onWakeUpComplete() {
+ // Finish the dream once overlay animations are complete. Execute on handler since
+ // this is coming in on the overlay binder.
+ mHandler.post(() -> finish());
}
};
@@ -975,7 +982,18 @@
* </p>
*/
public void onWakeUp() {
- finish();
+ if (mOverlayConnection != null) {
+ mOverlayConnection.addConsumer(overlay -> {
+ try {
+ overlay.wakeUp();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error waking the overlay service", e);
+ finish();
+ }
+ });
+ } else {
+ finish();
+ }
}
/** {@inheritDoc} */
@@ -1294,7 +1312,7 @@
if (!mWindowless) {
Intent i = new Intent(this, DreamActivity.class);
i.setPackage(getApplicationContext().getPackageName());
- i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
i.putExtra(DreamActivity.EXTRA_CALLBACK, new DreamActivityCallbacks(mDreamToken));
final ServiceInfo serviceInfo = fetchServiceInfo(this,
new ComponentName(this, getClass()));
diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl
index 05ebbfe..7aeceb2c 100644
--- a/core/java/android/service/dreams/IDreamOverlay.aidl
+++ b/core/java/android/service/dreams/IDreamOverlay.aidl
@@ -38,4 +38,7 @@
*/
void startDream(in LayoutParams params, in IDreamOverlayCallback callback,
in String dreamComponent, in boolean shouldShowComplications);
+
+ /** Called when the dream is waking, to do any exit animations */
+ void wakeUp();
}
diff --git a/core/java/android/service/dreams/IDreamOverlayCallback.aidl b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
index ec76a33..4ad63f1 100644
--- a/core/java/android/service/dreams/IDreamOverlayCallback.aidl
+++ b/core/java/android/service/dreams/IDreamOverlayCallback.aidl
@@ -28,4 +28,7 @@
* Invoked to request the dream exit.
*/
void onExitRequested();
+
+ /** Invoked when the dream overlay wakeUp animation is complete. */
+ void onWakeUpComplete();
}
\ No newline at end of file
diff --git a/core/java/android/service/voice/HotwordAudioStream.aidl b/core/java/android/service/voice/HotwordAudioStream.aidl
new file mode 100644
index 0000000..9550c83
--- /dev/null
+++ b/core/java/android/service/voice/HotwordAudioStream.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.service.voice;
+
+parcelable HotwordAudioStream;
diff --git a/core/java/android/service/voice/HotwordAudioStream.java b/core/java/android/service/voice/HotwordAudioStream.java
new file mode 100644
index 0000000..5442860
--- /dev/null
+++ b/core/java/android/service/voice/HotwordAudioStream.java
@@ -0,0 +1,444 @@
+/*
+ * 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 android.service.voice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.AudioTimestamp;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import java.util.Objects;
+
+/**
+ * Represents an audio stream supporting the hotword detection.
+ *
+ * @hide
+ */
+public final class HotwordAudioStream implements Parcelable {
+
+ /**
+ * The {@link AudioFormat} of the audio stream.
+ */
+ @NonNull
+ @UnsupportedAppUsage
+ private final AudioFormat mAudioFormat;
+
+ /**
+ * This stream starts with the audio bytes used for hotword detection, but continues streaming
+ * the audio until the stream is shutdown by the {@link HotwordDetectionService}.
+ */
+ @NonNull
+ @UnsupportedAppUsage
+ private final ParcelFileDescriptor mAudioStreamParcelFileDescriptor;
+
+ /**
+ * The timestamp when the audio stream was captured by the Audio platform.
+ *
+ * <p>
+ * The {@link HotwordDetectionService} egressing the audio is the owner of the underlying
+ * AudioRecord. The {@link HotwordDetectionService} is expected to optionally populate this
+ * field by {@link AudioRecord#getTimestamp}.
+ * </p>
+ *
+ * <p>
+ * This timestamp can be used in conjunction with the
+ * {@link HotwordDetectedResult#getHotwordOffsetMillis()} and
+ * {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to
+ * timestamps.
+ * </p>
+ *
+ * @see #getAudioStreamParcelFileDescriptor()
+ */
+ @Nullable
+ @UnsupportedAppUsage
+ private final AudioTimestamp mTimestamp;
+
+ private static AudioTimestamp defaultTimestamp() {
+ return null;
+ }
+
+ /**
+ * The metadata associated with the audio stream.
+ */
+ @NonNull
+ @UnsupportedAppUsage
+ private final PersistableBundle mMetadata;
+
+ private static PersistableBundle defaultMetadata() {
+ return new PersistableBundle();
+ }
+
+ private String timestampToString() {
+ if (mTimestamp == null) {
+ return "";
+ }
+ return "TimeStamp:"
+ + " framePos=" + mTimestamp.framePosition
+ + " nanoTime=" + mTimestamp.nanoTime;
+ }
+
+ private void parcelTimestamp(Parcel dest, int flags) {
+ if (mTimestamp != null) {
+ // mTimestamp is not null, we write it to the parcel, set true.
+ dest.writeBoolean(true);
+ dest.writeLong(mTimestamp.framePosition);
+ dest.writeLong(mTimestamp.nanoTime);
+ } else {
+ // mTimestamp is null, we don't write any value out, set false.
+ dest.writeBoolean(false);
+ }
+ }
+
+ @Nullable
+ private static AudioTimestamp unparcelTimestamp(Parcel in) {
+ // If it is true, it means we wrote the value to the parcel before, parse it.
+ // Otherwise, return null.
+ if (in.readBoolean()) {
+ final AudioTimestamp timeStamp = new AudioTimestamp();
+ timeStamp.framePosition = in.readLong();
+ timeStamp.nanoTime = in.readLong();
+ return timeStamp;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Provides an instance of {@link Builder} with state corresponding to this instance.
+ * @hide
+ */
+ public Builder buildUpon() {
+ return new Builder(mAudioFormat, mAudioStreamParcelFileDescriptor)
+ .setTimestamp(mTimestamp)
+ .setMetadata(mMetadata);
+ }
+
+ /* package-private */
+ HotwordAudioStream(
+ @NonNull AudioFormat audioFormat,
+ @NonNull ParcelFileDescriptor audioStreamParcelFileDescriptor,
+ @Nullable AudioTimestamp timestamp,
+ @NonNull PersistableBundle metadata) {
+ this.mAudioFormat = audioFormat;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioFormat);
+ this.mAudioStreamParcelFileDescriptor = audioStreamParcelFileDescriptor;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioStreamParcelFileDescriptor);
+ this.mTimestamp = timestamp;
+ this.mMetadata = metadata;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMetadata);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The {@link AudioFormat} of the audio stream.
+ */
+ @UnsupportedAppUsage
+ @NonNull
+ public AudioFormat getAudioFormat() {
+ return mAudioFormat;
+ }
+
+ /**
+ * This stream starts with the audio bytes used for hotword detection, but continues streaming
+ * the audio until the stream is shutdown by the {@link HotwordDetectionService}.
+ */
+ @UnsupportedAppUsage
+ @NonNull
+ public ParcelFileDescriptor getAudioStreamParcelFileDescriptor() {
+ return mAudioStreamParcelFileDescriptor;
+ }
+
+ /**
+ * The timestamp when the audio stream was captured by the Audio platform.
+ *
+ * <p>
+ * The {@link HotwordDetectionService} egressing the audio is the owner of the underlying
+ * AudioRecord. The {@link HotwordDetectionService} is expected to optionally populate this
+ * field by {@link AudioRecord#getTimestamp}.
+ * </p>
+ *
+ * <p>
+ * This timestamp can be used in conjunction with the
+ * {@link HotwordDetectedResult#getHotwordOffsetMillis()} and
+ * {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to
+ * timestamps.
+ * </p>
+ *
+ * @see #getAudioStreamParcelFileDescriptor()
+ */
+ @UnsupportedAppUsage
+ @Nullable
+ public AudioTimestamp getTimestamp() {
+ return mTimestamp;
+ }
+
+ /**
+ * The metadata associated with the audio stream.
+ */
+ @UnsupportedAppUsage
+ @NonNull
+ public PersistableBundle getMetadata() {
+ return mMetadata;
+ }
+
+ @Override
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "HotwordAudioStream { "
+ + "audioFormat = " + mAudioFormat + ", "
+ + "audioStreamParcelFileDescriptor = " + mAudioStreamParcelFileDescriptor + ", "
+ + "timestamp = " + timestampToString() + ", "
+ + "metadata = " + mMetadata + " }";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(HotwordAudioStream other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ HotwordAudioStream that = (HotwordAudioStream) o;
+ //noinspection PointlessBooleanExpression
+ return Objects.equals(mAudioFormat, that.mAudioFormat)
+ && Objects.equals(mAudioStreamParcelFileDescriptor,
+ that.mAudioStreamParcelFileDescriptor)
+ && Objects.equals(mTimestamp, that.mTimestamp)
+ && Objects.equals(mMetadata, that.mMetadata);
+ }
+
+ @Override
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Objects.hashCode(mAudioFormat);
+ _hash = 31 * _hash + Objects.hashCode(mAudioStreamParcelFileDescriptor);
+ _hash = 31 * _hash + Objects.hashCode(mTimestamp);
+ _hash = 31 * _hash + Objects.hashCode(mMetadata);
+ return _hash;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mTimestamp != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeTypedObject(mAudioFormat, flags);
+ dest.writeTypedObject(mAudioStreamParcelFileDescriptor, flags);
+ parcelTimestamp(dest, flags);
+ dest.writeTypedObject(mMetadata, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ /* package-private */
+ HotwordAudioStream(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ AudioFormat audioFormat = (AudioFormat) in.readTypedObject(AudioFormat.CREATOR);
+ ParcelFileDescriptor audioStreamParcelFileDescriptor =
+ (ParcelFileDescriptor) in.readTypedObject(ParcelFileDescriptor.CREATOR);
+ AudioTimestamp timestamp = unparcelTimestamp(in);
+ PersistableBundle metadata = (PersistableBundle) in.readTypedObject(
+ PersistableBundle.CREATOR);
+
+ this.mAudioFormat = audioFormat;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioFormat);
+ this.mAudioStreamParcelFileDescriptor = audioStreamParcelFileDescriptor;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioStreamParcelFileDescriptor);
+ this.mTimestamp = timestamp;
+ this.mMetadata = metadata;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMetadata);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<HotwordAudioStream> CREATOR =
+ new Parcelable.Creator<HotwordAudioStream>() {
+ @Override
+ public HotwordAudioStream[] newArray(int size) {
+ return new HotwordAudioStream[size];
+ }
+
+ @Override
+ public HotwordAudioStream createFromParcel(@NonNull Parcel in) {
+ return new HotwordAudioStream(in);
+ }
+ };
+
+ /**
+ * A builder for {@link HotwordAudioStream}
+ */
+ @SuppressWarnings("WeakerAccess")
+ public static final class Builder {
+
+ @NonNull
+ private AudioFormat mAudioFormat;
+ @NonNull
+ private ParcelFileDescriptor mAudioStreamParcelFileDescriptor;
+ @Nullable
+ private AudioTimestamp mTimestamp;
+ @NonNull
+ private PersistableBundle mMetadata;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param audioFormat The {@link AudioFormat} of the audio stream.
+ * @param audioStreamParcelFileDescriptor This stream starts with the audio bytes used for
+ * hotword detection, but continues streaming
+ * the audio until the stream is shutdown by the
+ * {@link HotwordDetectionService}.
+ */
+ @UnsupportedAppUsage
+ public Builder(
+ @NonNull AudioFormat audioFormat,
+ @NonNull ParcelFileDescriptor audioStreamParcelFileDescriptor) {
+ mAudioFormat = audioFormat;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioFormat);
+ mAudioStreamParcelFileDescriptor = audioStreamParcelFileDescriptor;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioStreamParcelFileDescriptor);
+ }
+
+ /**
+ * The {@link AudioFormat} of the audio stream.
+ */
+ @UnsupportedAppUsage
+ @NonNull
+ public Builder setAudioFormat(@NonNull AudioFormat value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mAudioFormat = value;
+ return this;
+ }
+
+ /**
+ * This stream starts with the audio bytes used for hotword detection, but continues
+ * streaming
+ * the audio until the stream is shutdown by the {@link HotwordDetectionService}.
+ */
+ @UnsupportedAppUsage
+ @NonNull
+ public Builder setAudioStreamParcelFileDescriptor(@NonNull ParcelFileDescriptor value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mAudioStreamParcelFileDescriptor = value;
+ return this;
+ }
+
+ /**
+ * The timestamp when the audio stream was captured by the Audio platform.
+ *
+ * <p>
+ * The {@link HotwordDetectionService} egressing the audio is the owner of the underlying
+ * AudioRecord. The {@link HotwordDetectionService} is expected to optionally populate this
+ * field by {@link AudioRecord#getTimestamp}.
+ * </p>
+ *
+ * <p>
+ * This timestamp can be used in conjunction with the
+ * {@link HotwordDetectedResult#getHotwordOffsetMillis()} and
+ * {@link HotwordDetectedResult#getHotwordDurationMillis()} to translate these durations to
+ * timestamps.
+ * </p>
+ *
+ * @see #getAudioStreamParcelFileDescriptor()
+ */
+ @UnsupportedAppUsage
+ @NonNull
+ public Builder setTimestamp(@NonNull AudioTimestamp value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mTimestamp = value;
+ return this;
+ }
+
+ /**
+ * The metadata associated with the audio stream.
+ */
+ @UnsupportedAppUsage
+ @NonNull
+ public Builder setMetadata(@NonNull PersistableBundle value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mMetadata = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ @UnsupportedAppUsage
+ @NonNull
+ public HotwordAudioStream build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mTimestamp = defaultTimestamp();
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mMetadata = defaultMetadata();
+ }
+ HotwordAudioStream o = new HotwordAudioStream(
+ mAudioFormat,
+ mAudioStreamParcelFileDescriptor,
+ mTimestamp,
+ mMetadata);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x10) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index ab71459..c3d58b7 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.media.AudioRecord;
import android.media.MediaSyncEvent;
@@ -31,6 +32,11 @@
import com.android.internal.util.DataClass;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
/**
@@ -96,14 +102,42 @@
private static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE = 63;
/**
- * The bundle key for proximity value
+ * The bundle key for proximity
*
* TODO(b/238896013): Move the proximity logic out of bundle to proper API.
+ */
+ private static final String EXTRA_PROXIMITY =
+ "android.service.voice.extra.PROXIMITY";
+
+ /**
+ * Users’ proximity is unknown (proximity sensing was inconclusive and is unsupported).
*
* @hide
*/
- public static final String EXTRA_PROXIMITY_METERS =
- "android.service.voice.extra.PROXIMITY_METERS";
+ public static final int PROXIMITY_UNKNOWN = -1;
+
+ /**
+ * Proximity value that represents that the object is near.
+ *
+ * @hide
+ */
+ public static final int PROXIMITY_NEAR = 1;
+
+ /**
+ * Proximity value that represents that the object is far.
+ *
+ * @hide
+ */
+ public static final int PROXIMITY_FAR = 2;
+
+ /** @hide */
+ @IntDef(prefix = {"PROXIMITY"}, value = {
+ PROXIMITY_UNKNOWN,
+ PROXIMITY_NEAR,
+ PROXIMITY_FAR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProximityValue {}
/** Confidence level in the trigger outcome. */
@HotwordConfidenceLevelValue
@@ -196,6 +230,17 @@
}
/**
+ * The list of the audio streams containing audio bytes that were used for hotword detection.
+ *
+ * @hide
+ */
+ @NonNull
+ private final List<HotwordAudioStream> mAudioStreams;
+ private static List<HotwordAudioStream> defaultAudioStreams() {
+ return Collections.emptyList();
+ }
+
+ /**
* App-specific extras to support trigger.
*
* <p>The size of this bundle will be limited to {@link #getMaxBundleSize}. Results will larger
@@ -208,12 +253,14 @@
* versions of Android.
*
* <p>After the trigger happens, a special case of proximity-related extra, with the key of
- * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
- * will be stored to enable proximity logic. The proximity meters is provided by the system,
- * on devices that support detecting proximity of nearby users, to help disambiguate which
- * nearby device should respond. When the proximity is unknown, the proximity value will not
- * be stored. This mapping will be excluded from the max bundle size calculation because this
- * mapping is included after the result is returned from the hotword detector service.
+ * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+ * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+ * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+ * proximity. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+ * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+ * from the max bundle size calculation because this mapping is included after the result is
+ * returned from the hotword detector service.
*
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
@@ -336,16 +383,16 @@
// Remove the proximity key from the bundle before checking the bundle size. The
// proximity value is added after the privileged module and can avoid the
// maxBundleSize limitation.
- if (mExtras.containsKey(EXTRA_PROXIMITY_METERS)) {
- double proximityMeters = mExtras.getDouble(EXTRA_PROXIMITY_METERS);
- mExtras.remove(EXTRA_PROXIMITY_METERS);
+ if (mExtras.containsKey(EXTRA_PROXIMITY)) {
+ int proximityValue = mExtras.getInt(EXTRA_PROXIMITY);
+ mExtras.remove(EXTRA_PROXIMITY);
// Skip checking parcelable size if the new bundle size is 0. Newly empty bundle
// has parcelable size of 4, but the default bundle has parcelable size of 0.
if (mExtras.size() > 0) {
Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
getMaxBundleSize(), "extras");
}
- mExtras.putDouble(EXTRA_PROXIMITY_METERS, proximityMeters);
+ mExtras.putInt(EXTRA_PROXIMITY, proximityValue);
} else {
Preconditions.checkArgumentInRange(getParcelableSize(mExtras), 0,
getMaxBundleSize(), "extras");
@@ -353,6 +400,90 @@
}
}
+ /**
+ * The list of the audio streams containing audio bytes that were used for hotword detection.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @NonNull List<HotwordAudioStream> getAudioStreams() {
+ return List.copyOf(mAudioStreams);
+ }
+
+ @DataClass.Suppress("addAudioStreams")
+ abstract static class BaseBuilder {
+ /**
+ * The list of the audio streams containing audio bytes that were used for hotword
+ * detection.
+ *
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public @NonNull Builder setAudioStreams(@NonNull List<HotwordAudioStream> value) {
+ Objects.requireNonNull(value, "value should not be null");
+ final Builder builder = (Builder) this;
+ // If the code gen flag in build() is changed, we must update the flag e.g. 0x200 here.
+ builder.mBuilderFieldsSet |= 0x200;
+ builder.mAudioStreams = List.copyOf(value);
+ return builder;
+ }
+ }
+
+ /**
+ * Provides an instance of {@link Builder} with state corresponding to this instance.
+ * @hide
+ */
+ public Builder buildUpon() {
+ return new Builder()
+ .setConfidenceLevel(mConfidenceLevel)
+ .setMediaSyncEvent(mMediaSyncEvent)
+ .setHotwordOffsetMillis(mHotwordOffsetMillis)
+ .setHotwordDurationMillis(mHotwordDurationMillis)
+ .setAudioChannel(mAudioChannel)
+ .setHotwordDetectionPersonalized(mHotwordDetectionPersonalized)
+ .setScore(mScore)
+ .setPersonalizedScore(mPersonalizedScore)
+ .setHotwordPhraseId(mHotwordPhraseId)
+ .setAudioStreams(mAudioStreams)
+ .setExtras(mExtras);
+ }
+
+ /**
+ * Adds proximity level, either near or far, that is mapped for the given distance into
+ * the bundle. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond.
+ * This mapping will be excluded from the max bundle size calculation because this mapping is
+ * included after the result is returned from the hotword detector service. The value will not
+ * be included if the proximity was unknown.
+ *
+ * @hide
+ */
+ public void setProximity(double distance) {
+ int proximityLevel = convertToProximityLevel(distance);
+ if (proximityLevel != PROXIMITY_UNKNOWN) {
+ mExtras.putInt(EXTRA_PROXIMITY, proximityLevel);
+ }
+ }
+
+ /**
+ * Mapping of the proximity distance (meters) to proximity values, unknown, near, and far.
+ * Currently, this mapping is handled by HotwordDetectedResult because it handles just
+ * HotwordDetectionConnection which we know the mapping of. However, the mapping will need to
+ * move to a more centralized place once there are more clients.
+ *
+ * TODO(b/258531144): Move the proximity mapping to a central location
+ */
+ @ProximityValue
+ private int convertToProximityLevel(double distance) {
+ if (distance < 0) {
+ return PROXIMITY_UNKNOWN;
+ } else if (distance <= 3) {
+ return PROXIMITY_NEAR;
+ } else {
+ return PROXIMITY_FAR;
+ }
+ }
+
// Code below generated by codegen v1.0.23.
@@ -378,7 +509,7 @@
CONFIDENCE_LEVEL_HIGH,
CONFIDENCE_LEVEL_VERY_HIGH
})
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
public @interface ConfidenceLevel {}
@@ -409,7 +540,7 @@
LIMIT_HOTWORD_OFFSET_MAX_VALUE,
LIMIT_AUDIO_CHANNEL_MAX_VALUE
})
- @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @Retention(RetentionPolicy.SOURCE)
@DataClass.Generated.Member
/* package-private */ @interface Limit {}
@@ -425,6 +556,30 @@
}
}
+ /** @hide */
+ @IntDef(prefix = "PROXIMITY_", value = {
+ PROXIMITY_UNKNOWN,
+ PROXIMITY_NEAR,
+ PROXIMITY_FAR
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Proximity {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String proximityToString(@Proximity int value) {
+ switch (value) {
+ case PROXIMITY_UNKNOWN:
+ return "PROXIMITY_UNKNOWN";
+ case PROXIMITY_NEAR:
+ return "PROXIMITY_NEAR";
+ case PROXIMITY_FAR:
+ return "PROXIMITY_FAR";
+ default: return Integer.toHexString(value);
+ }
+ }
+
@DataClass.Generated.Member
/* package-private */ HotwordDetectedResult(
@HotwordConfidenceLevelValue int confidenceLevel,
@@ -436,6 +591,7 @@
int score,
int personalizedScore,
int hotwordPhraseId,
+ @NonNull List<HotwordAudioStream> audioStreams,
@NonNull PersistableBundle extras) {
this.mConfidenceLevel = confidenceLevel;
com.android.internal.util.AnnotationValidations.validate(
@@ -448,6 +604,9 @@
this.mScore = score;
this.mPersonalizedScore = personalizedScore;
this.mHotwordPhraseId = hotwordPhraseId;
+ this.mAudioStreams = audioStreams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioStreams);
this.mExtras = extras;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mExtras);
@@ -547,12 +706,14 @@
* versions of Android.
*
* <p>After the trigger happens, a special case of proximity-related extra, with the key of
- * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
- * will be stored to enable proximity logic. The proximity meters is provided by the system,
- * on devices that support detecting proximity of nearby users, to help disambiguate which
- * nearby device should respond. When the proximity is unknown, the proximity value will not
- * be stored. This mapping will be excluded from the max bundle size calculation because this
- * mapping is included after the result is returned from the hotword detector service.
+ * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+ * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+ * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+ * proximity. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+ * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+ * from the max bundle size calculation because this mapping is included after the result is
+ * returned from the hotword detector service.
*
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
@@ -578,6 +739,7 @@
"score = " + mScore + ", " +
"personalizedScore = " + mPersonalizedScore + ", " +
"hotwordPhraseId = " + mHotwordPhraseId + ", " +
+ "audioStreams = " + mAudioStreams + ", " +
"extras = " + mExtras +
" }";
}
@@ -604,6 +766,7 @@
&& mScore == that.mScore
&& mPersonalizedScore == that.mPersonalizedScore
&& mHotwordPhraseId == that.mHotwordPhraseId
+ && Objects.equals(mAudioStreams, that.mAudioStreams)
&& Objects.equals(mExtras, that.mExtras);
}
@@ -623,6 +786,7 @@
_hash = 31 * _hash + mScore;
_hash = 31 * _hash + mPersonalizedScore;
_hash = 31 * _hash + mHotwordPhraseId;
+ _hash = 31 * _hash + Objects.hashCode(mAudioStreams);
_hash = 31 * _hash + Objects.hashCode(mExtras);
return _hash;
}
@@ -645,6 +809,7 @@
dest.writeInt(mScore);
dest.writeInt(mPersonalizedScore);
dest.writeInt(mHotwordPhraseId);
+ dest.writeParcelableList(mAudioStreams, flags);
dest.writeTypedObject(mExtras, flags);
}
@@ -669,6 +834,8 @@
int score = in.readInt();
int personalizedScore = in.readInt();
int hotwordPhraseId = in.readInt();
+ List<HotwordAudioStream> audioStreams = new ArrayList<>();
+ in.readParcelableList(audioStreams, HotwordAudioStream.class.getClassLoader());
PersistableBundle extras = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
this.mConfidenceLevel = confidenceLevel;
@@ -682,6 +849,9 @@
this.mScore = score;
this.mPersonalizedScore = personalizedScore;
this.mHotwordPhraseId = hotwordPhraseId;
+ this.mAudioStreams = audioStreams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioStreams);
this.mExtras = extras;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mExtras);
@@ -708,7 +878,7 @@
*/
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
- public static final class Builder {
+ public static final class Builder extends BaseBuilder {
private @HotwordConfidenceLevelValue int mConfidenceLevel;
private @Nullable MediaSyncEvent mMediaSyncEvent;
@@ -719,6 +889,7 @@
private int mScore;
private int mPersonalizedScore;
private int mHotwordPhraseId;
+ private @NonNull List<HotwordAudioStream> mAudioStreams;
private @NonNull PersistableBundle mExtras;
private long mBuilderFieldsSet = 0L;
@@ -855,12 +1026,14 @@
* versions of Android.
*
* <p>After the trigger happens, a special case of proximity-related extra, with the key of
- * 'android.service.voice.extra.PROXIMITY_METERS' and the value of distance in meters (double),
- * will be stored to enable proximity logic. The proximity meters is provided by the system,
- * on devices that support detecting proximity of nearby users, to help disambiguate which
- * nearby device should respond. When the proximity is unknown, the proximity value will not
- * be stored. This mapping will be excluded from the max bundle size calculation because this
- * mapping is included after the result is returned from the hotword detector service.
+ * 'android.service.voice.extra.PROXIMITY_VALUE' and the value of proximity value (integer)
+ * will be stored to enable proximity logic. {@link HotwordDetectedResult#PROXIMITY_NEAR} will
+ * indicate 'NEAR' proximity and {@link HotwordDetectedResult#PROXIMITY_FAR} will indicate 'FAR'
+ * proximity. The proximity value is provided by the system, on devices that support detecting
+ * proximity of nearby users, to help disambiguate which nearby device should respond. When the
+ * proximity is unknown, the proximity value will not be stored. This mapping will be excluded
+ * from the max bundle size calculation because this mapping is included after the result is
+ * returned from the hotword detector service.
*
* <p>This is a PersistableBundle so it doesn't allow any remotable objects or other contents
* that can be used to communicate with other processes.
@@ -868,7 +1041,7 @@
@DataClass.Generated.Member
public @NonNull Builder setExtras(@NonNull PersistableBundle value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x200;
+ mBuilderFieldsSet |= 0x400;
mExtras = value;
return this;
}
@@ -876,7 +1049,7 @@
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull HotwordDetectedResult build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x400; // Mark builder used
+ mBuilderFieldsSet |= 0x800; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mConfidenceLevel = defaultConfidenceLevel();
@@ -906,6 +1079,9 @@
mHotwordPhraseId = defaultHotwordPhraseId();
}
if ((mBuilderFieldsSet & 0x200) == 0) {
+ mAudioStreams = defaultAudioStreams();
+ }
+ if ((mBuilderFieldsSet & 0x400) == 0) {
mExtras = defaultExtras();
}
HotwordDetectedResult o = new HotwordDetectedResult(
@@ -918,12 +1094,13 @@
mScore,
mPersonalizedScore,
mHotwordPhraseId,
+ mAudioStreams,
mExtras);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x400) != 0) {
+ if ((mBuilderFieldsSet & 0x800) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -931,10 +1108,10 @@
}
@DataClass.Generated(
- time = 1658357814396L,
+ time = 1668528946960L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java",
- inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\npublic static final java.lang.String EXTRA_PROXIMITY_METERS\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+ inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final java.lang.String EXTRA_PROXIMITY\npublic static final int PROXIMITY_UNKNOWN\npublic static final int PROXIMITY_NEAR\npublic static final int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final int mHotwordPhraseId\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\nprivate static int defaultHotwordPhraseId()\npublic static int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nprivate static int bitCount(long)\nprivate void onConstructed()\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\npublic void setProximity(double)\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index eac3bee..302966a 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -92,7 +92,7 @@
* TODO(b/247920386): Add TestApi annotation
* @hide
*/
- public static final boolean ENABLE_PROXIMITY_RESULT = false;
+ public static final boolean ENABLE_PROXIMITY_RESULT = true;
/**
* Indicates that the updated status is successful.
diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java
index c54d9b6..3e7c67e 100644
--- a/core/java/android/util/RotationUtils.java
+++ b/core/java/android/util/RotationUtils.java
@@ -25,6 +25,7 @@
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
@@ -193,6 +194,29 @@
}
/**
+ * Same as {@link #rotatePoint}, but for float coordinates.
+ */
+ public static void rotatePointF(PointF inOutPoint, @Rotation int rotation,
+ float parentW, float parentH) {
+ float origX = inOutPoint.x;
+ switch (rotation) {
+ case ROTATION_0:
+ return;
+ case ROTATION_90:
+ inOutPoint.x = inOutPoint.y;
+ inOutPoint.y = parentW - origX;
+ return;
+ case ROTATION_180:
+ inOutPoint.x = parentW - inOutPoint.x;
+ inOutPoint.y = parentH - inOutPoint.y;
+ return;
+ case ROTATION_270:
+ inOutPoint.x = parentH - inOutPoint.y;
+ inOutPoint.y = origX;
+ }
+ }
+
+ /**
* Sets a matrix such that given a rotation, it transforms physical display
* coordinates to that rotation's logical coordinates.
*
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index bb26c46..1f095ac 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -727,9 +727,8 @@
* If invoked through a package other than a launcher app, returns an empty list.
*
* @param displayId the id of the logical display
- * @param packageName the name of the calling package
*/
- List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName);
+ List<DisplayInfo> getPossibleDisplayInfo(int displayId);
/**
* Called to show global actions.
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 20cdad4..baf9925 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -357,7 +357,7 @@
List<DisplayInfo> possibleDisplayInfos;
try {
possibleDisplayInfos = WindowManagerGlobal.getWindowManagerService()
- .getPossibleDisplayInfo(displayId, mContext.getPackageName());
+ .getPossibleDisplayInfo(displayId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 3250dd8..d25c8a8 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -39,13 +39,13 @@
* animations if the transition only contains windows that belong to the organized
* TaskFragments in the given Task.
*/
- void registerRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId,
+ void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
in RemoteAnimationDefinition definition);
/**
* Unregisters remote animations per transition type for the organizer.
*/
- void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer, int taskId);
+ void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
/**
* Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index e2c8a31..dc60edd 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -83,6 +83,12 @@
private final boolean mIsTaskFragmentClearedForPip;
/**
+ * Whether the last running activity of the TaskFragment was removed because it was reordered to
+ * front of the Task.
+ */
+ private final boolean mIsClearedForReorderActivityToFront;
+
+ /**
* The maximum {@link ActivityInfo.WindowLayout#minWidth} and
* {@link ActivityInfo.WindowLayout#minHeight} aggregated from the TaskFragment's child
* activities.
@@ -96,7 +102,7 @@
@NonNull Configuration configuration, int runningActivityCount,
boolean isVisible, @NonNull List<IBinder> activities, @NonNull Point positionInParent,
boolean isTaskClearedForReuse, boolean isTaskFragmentClearedForPip,
- @NonNull Point minimumDimensions) {
+ boolean isClearedForReorderActivityToFront, @NonNull Point minimumDimensions) {
mFragmentToken = requireNonNull(fragmentToken);
mToken = requireNonNull(token);
mConfiguration.setTo(configuration);
@@ -106,6 +112,7 @@
mPositionInParent.set(positionInParent);
mIsTaskClearedForReuse = isTaskClearedForReuse;
mIsTaskFragmentClearedForPip = isTaskFragmentClearedForPip;
+ mIsClearedForReorderActivityToFront = isClearedForReorderActivityToFront;
mMinimumDimensions.set(minimumDimensions);
}
@@ -160,6 +167,11 @@
return mIsTaskFragmentClearedForPip;
}
+ /** @hide */
+ public boolean isClearedForReorderActivityToFront() {
+ return mIsClearedForReorderActivityToFront;
+ }
+
@WindowingMode
public int getWindowingMode() {
return mConfiguration.windowConfiguration.getWindowingMode();
@@ -207,6 +219,7 @@
&& mPositionInParent.equals(that.mPositionInParent)
&& mIsTaskClearedForReuse == that.mIsTaskClearedForReuse
&& mIsTaskFragmentClearedForPip == that.mIsTaskFragmentClearedForPip
+ && mIsClearedForReorderActivityToFront == that.mIsClearedForReorderActivityToFront
&& mMinimumDimensions.equals(that.mMinimumDimensions);
}
@@ -220,6 +233,7 @@
mPositionInParent.readFromParcel(in);
mIsTaskClearedForReuse = in.readBoolean();
mIsTaskFragmentClearedForPip = in.readBoolean();
+ mIsClearedForReorderActivityToFront = in.readBoolean();
mMinimumDimensions.readFromParcel(in);
}
@@ -235,6 +249,7 @@
mPositionInParent.writeToParcel(dest, flags);
dest.writeBoolean(mIsTaskClearedForReuse);
dest.writeBoolean(mIsTaskFragmentClearedForPip);
+ dest.writeBoolean(mIsClearedForReorderActivityToFront);
mMinimumDimensions.writeToParcel(dest, flags);
}
@@ -262,8 +277,9 @@
+ " activities=" + mActivities
+ " positionInParent=" + mPositionInParent
+ " isTaskClearedForReuse=" + mIsTaskClearedForReuse
- + " isTaskFragmentClearedForPip" + mIsTaskFragmentClearedForPip
- + " minimumDimensions" + mMinimumDimensions
+ + " isTaskFragmentClearedForPip=" + mIsTaskFragmentClearedForPip
+ + " mIsClearedForReorderActivityToFront=" + mIsClearedForReorderActivityToFront
+ + " minimumDimensions=" + mMinimumDimensions
+ "}";
}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 648541b..ab7d616 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -140,16 +140,13 @@
/**
* Registers remote animations per transition type for the organizer. It will override the
* animations if the transition only contains windows that belong to the organized
- * TaskFragments in the given Task.
- *
- * @param taskId overrides if the transition only contains windows belonging to this Task.
+ * TaskFragments, and at least one of the transition window is embedded (not filling the Task).
* @hide
*/
@CallSuper
- public void registerRemoteAnimations(int taskId,
- @NonNull RemoteAnimationDefinition definition) {
+ public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
try {
- getController().registerRemoteAnimations(mInterface, taskId, definition);
+ getController().registerRemoteAnimations(mInterface, definition);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -160,9 +157,9 @@
* @hide
*/
@CallSuper
- public void unregisterRemoteAnimations(int taskId) {
+ public void unregisterRemoteAnimations() {
try {
- getController().unregisterRemoteAnimations(mInterface, taskId);
+ getController().unregisterRemoteAnimations(mInterface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 0956a71..666f316 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -414,6 +414,53 @@
return false;
}
+ /**
+ * Releases temporary-for-animation surfaces referenced by this to potentially free up memory.
+ * This includes root-leash and snapshots.
+ */
+ public void releaseAnimSurfaces() {
+ for (int i = mChanges.size() - 1; i >= 0; --i) {
+ final Change c = mChanges.get(i);
+ if (c.mSnapshot != null) {
+ c.mSnapshot.release();
+ c.mSnapshot = null;
+ }
+ }
+ if (mRootLeash != null) {
+ mRootLeash.release();
+ }
+ }
+
+ /**
+ * Releases ALL the surfaces referenced by this to potentially free up memory. Do NOT use this
+ * if the surface-controls get stored and used elsewhere in the process. To just release
+ * temporary-for-animation surfaces, use {@link #releaseAnimSurfaces}.
+ */
+ public void releaseAllSurfaces() {
+ releaseAnimSurfaces();
+ for (int i = mChanges.size() - 1; i >= 0; --i) {
+ mChanges.get(i).getLeash().release();
+ }
+ }
+
+ /**
+ * Makes a copy of this as if it were parcel'd and unparcel'd. This implies that surfacecontrol
+ * refcounts are incremented which allows the "remote" receiver to release them without breaking
+ * the caller's references. Use this only if you need to "send" this to a local function which
+ * assumes it is being called from a remote caller.
+ */
+ public TransitionInfo localRemoteCopy() {
+ final TransitionInfo out = new TransitionInfo(mType, mFlags);
+ for (int i = 0; i < mChanges.size(); ++i) {
+ out.mChanges.add(mChanges.get(i).localRemoteCopy());
+ }
+ out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null;
+ // Doesn't have any native stuff, so no need for actual copy
+ out.mOptions = mOptions;
+ out.mRootOffset.set(mRootOffset);
+ return out;
+ }
+
/** Represents the change a WindowContainer undergoes during a transition */
public static final class Change implements Parcelable {
private final WindowContainerToken mContainer;
@@ -466,6 +513,27 @@
mSnapshotLuma = in.readFloat();
}
+ private Change localRemoteCopy() {
+ final Change out = new Change(mContainer, new SurfaceControl(mLeash, "localRemote"));
+ out.mParent = mParent;
+ out.mLastParent = mLastParent;
+ out.mMode = mMode;
+ out.mFlags = mFlags;
+ out.mStartAbsBounds.set(mStartAbsBounds);
+ out.mEndAbsBounds.set(mEndAbsBounds);
+ out.mEndRelOffset.set(mEndRelOffset);
+ out.mTaskInfo = mTaskInfo;
+ out.mAllowEnterPip = mAllowEnterPip;
+ out.mStartRotation = mStartRotation;
+ out.mEndRotation = mEndRotation;
+ out.mEndFixedRotation = mEndFixedRotation;
+ out.mRotationAnimation = mRotationAnimation;
+ out.mBackgroundColor = mBackgroundColor;
+ out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null;
+ out.mSnapshotLuma = mSnapshotLuma;
+ return out;
+ }
+
/** Sets the parent of this change's container. The parent must be a participant or null. */
public void setParent(@Nullable WindowContainerToken parent) {
mParent = parent;
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index cfad1af..15be5f5 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -467,6 +467,23 @@
}
/**
+ * Sets whether a container is being drag-resized.
+ * When {@code true}, the client will reuse a single (larger) surface size to avoid
+ * continuous allocations on every size change.
+ *
+ * @param container WindowContainerToken of the task that changed its drag resizing state
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setDragResizing(@NonNull WindowContainerToken container,
+ boolean dragResizing) {
+ final Change change = getOrCreateChange(container.asBinder());
+ change.mChangeMask |= Change.CHANGE_DRAG_RESIZING;
+ change.mDragResizing = dragResizing;
+ return this;
+ }
+
+ /**
* Sends a pending intent in sync.
* @param sender The PendingIntent sender.
* @param intent The fillIn intent to patch over the sender's base intent.
@@ -723,6 +740,29 @@
}
/**
+ * Sets the TaskFragment {@code container} to have a companion TaskFragment {@code companion}.
+ * This indicates that the organizer will remove the TaskFragment when the companion
+ * TaskFragment is removed.
+ *
+ * @param container the TaskFragment container
+ * @param companion the companion TaskFragment. If it is {@code null}, the transaction will
+ * reset the companion TaskFragment.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setCompanionTaskFragment(@NonNull IBinder container,
+ @Nullable IBinder companion) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT)
+ .setContainer(container)
+ .setReparentContainer(companion)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* Sets/removes the always on top flag for this {@code windowContainer}. See
* {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
* Please note that this method is only intended to be used for a
@@ -906,12 +946,14 @@
public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5;
public static final int CHANGE_FORCE_NO_PIP = 1 << 6;
public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7;
+ public static final int CHANGE_DRAG_RESIZING = 1 << 8;
private final Configuration mConfiguration = new Configuration();
private boolean mFocusable = true;
private boolean mHidden = false;
private boolean mIgnoreOrientationRequest = false;
private boolean mForceTranslucent = false;
+ private boolean mDragResizing = false;
private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
@@ -932,6 +974,7 @@
mHidden = in.readBoolean();
mIgnoreOrientationRequest = in.readBoolean();
mForceTranslucent = in.readBoolean();
+ mDragResizing = in.readBoolean();
mChangeMask = in.readInt();
mConfigSetMask = in.readInt();
mWindowSetMask = in.readInt();
@@ -980,6 +1023,9 @@
if ((other.mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) {
mForceTranslucent = other.mForceTranslucent;
}
+ if ((other.mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
+ mDragResizing = other.mDragResizing;
+ }
mChangeMask |= other.mChangeMask;
if (other.mActivityWindowingMode >= 0) {
mActivityWindowingMode = other.mActivityWindowingMode;
@@ -1039,6 +1085,15 @@
return mForceTranslucent;
}
+ /** Gets the requested drag resizing state. */
+ public boolean getDragResizing() {
+ if ((mChangeMask & CHANGE_DRAG_RESIZING) == 0) {
+ throw new RuntimeException("Drag resizing not set. "
+ + "Check CHANGE_DRAG_RESIZING first");
+ }
+ return mDragResizing;
+ }
+
public int getChangeMask() {
return mChangeMask;
}
@@ -1100,6 +1155,9 @@
if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
sb.append("focusable:" + mFocusable + ",");
}
+ if ((mChangeMask & CHANGE_DRAG_RESIZING) != 0) {
+ sb.append("dragResizing:" + mDragResizing + ",");
+ }
if (mBoundsChangeTransaction != null) {
sb.append("hasBoundsTransaction,");
}
@@ -1117,6 +1175,7 @@
dest.writeBoolean(mHidden);
dest.writeBoolean(mIgnoreOrientationRequest);
dest.writeBoolean(mForceTranslucent);
+ dest.writeBoolean(mDragResizing);
dest.writeInt(mChangeMask);
dest.writeInt(mConfigSetMask);
dest.writeInt(mWindowSetMask);
@@ -1181,6 +1240,7 @@
public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19;
public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20;
public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21;
+ public static final int HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT = 22;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1395,6 +1455,11 @@
}
@NonNull
+ public IBinder getCompanionContainer() {
+ return mReparent;
+ }
+
+ @NonNull
public IBinder getCallingActivity() {
return mReparent;
}
@@ -1504,6 +1569,9 @@
return "{RemoveTask: task=" + mContainer + "}";
case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
return "{finishActivity: activity=" + mContainer + "}";
+ case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT:
+ return "{setCompanionTaskFragment: container = " + mContainer + " companion = "
+ + mReparent + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
diff --git a/core/java/android/window/WindowProvider.java b/core/java/android/window/WindowProvider.java
index b078b93..dbdc68f 100644
--- a/core/java/android/window/WindowProvider.java
+++ b/core/java/android/window/WindowProvider.java
@@ -15,8 +15,10 @@
*/
package android.window;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
+import android.os.IBinder;
import android.view.WindowManager.LayoutParams.WindowType;
/**
@@ -36,4 +38,11 @@
/** Gets the launch options of this provider */
@Nullable
Bundle getWindowContextOptions();
+
+ /**
+ * Gets the WindowContextToken of this provider.
+ * @see android.content.Context#getWindowContextToken
+ */
+ @NonNull
+ IBinder getWindowContextToken();
}
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index 2d2c8de..f2ae973 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -27,7 +27,10 @@
import android.app.ActivityThread;
import android.app.LoadedApk;
import android.app.Service;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacksController;
import android.content.Context;
+import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.IBinder;
@@ -54,6 +57,8 @@
private final WindowContextController mController = new WindowContextController(mWindowToken);
private WindowManager mWindowManager;
private boolean mInitialized;
+ private final ComponentCallbacksController mCallbacksController =
+ new ComponentCallbacksController();
/**
* Returns {@code true} if the {@code windowContextOptions} declares that it is a
@@ -118,6 +123,48 @@
return mOptions;
}
+ @SuppressLint({"OnNameExpected", "ExecutorRegistration"})
+ // Suppress lint because this is a legacy named function and doesn't have an optional param
+ // for executor.
+ // TODO(b/259347943): Update documentation for U.
+ /**
+ * Here we override to prevent WindowProviderService from invoking
+ * {@link Application.registerComponentCallback}, which will result in callback registered
+ * for process-level Configuration change updates.
+ */
+ @Override
+ public void registerComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ // For broadcasting Configuration Changes.
+ mCallbacksController.registerCallbacks(callback);
+ }
+
+ @SuppressLint("OnNameExpected")
+ @Override
+ public void unregisterComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.unregisterCallbacks(callback);
+ }
+
+ @SuppressLint("OnNameExpected")
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration configuration) {
+ // This is only called from WindowTokenClient.
+ mCallbacksController.dispatchConfigurationChanged(configuration);
+ }
+
+ /**
+ * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for
+ * low memory and trim memory events.
+ */
+ @Override
+ public void onLowMemory() {
+ mCallbacksController.dispatchLowMemory();
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ mCallbacksController.dispatchTrimMemory(level);
+ }
+
/**
* Returns the display ID to launch this {@link WindowProviderService}.
*
@@ -181,5 +228,6 @@
public void onDestroy() {
super.onDestroy();
mController.detachIfNeeded();
+ mCallbacksController.clearCallbacks();
}
}
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index fc52620..2316738 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -17,18 +17,15 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.AppGlobals;
-import android.app.admin.DevicePolicyEventLogger;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.AsyncTask;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.UserManager;
-import android.stats.devicepolicy.DevicePolicyEnums;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
@@ -60,73 +57,31 @@
private final Context mContext;
private int mCurrentPage;
private OnProfileSelectedListener mOnProfileSelectedListener;
- private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
private Set<Integer> mLoadedPages;
- private final UserHandle mPersonalProfileUserHandle;
+ private final EmptyStateProvider mEmptyStateProvider;
private final UserHandle mWorkProfileUserHandle;
- private Injector mInjector;
- private boolean mIsWaitingToEnableWorkProfile;
+ private final QuietModeManager mQuietModeManager;
AbstractMultiProfilePagerAdapter(Context context, int currentPage,
- UserHandle personalProfileUserHandle,
+ EmptyStateProvider emptyStateProvider,
+ QuietModeManager quietModeManager,
UserHandle workProfileUserHandle) {
mContext = Objects.requireNonNull(context);
mCurrentPage = currentPage;
mLoadedPages = new HashSet<>();
- mPersonalProfileUserHandle = personalProfileUserHandle;
mWorkProfileUserHandle = workProfileUserHandle;
- UserManager userManager = context.getSystemService(UserManager.class);
- mInjector = new Injector() {
- @Override
- public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
- int targetUserId) {
- return AbstractMultiProfilePagerAdapter.this
- .hasCrossProfileIntents(intents, sourceUserId, targetUserId);
- }
-
- @Override
- public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
- return userManager.isQuietModeEnabled(workProfileUserHandle);
- }
-
- @Override
- public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
- AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
- userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
- });
- mIsWaitingToEnableWorkProfile = true;
- }
- };
+ mEmptyStateProvider = emptyStateProvider;
+ mQuietModeManager = quietModeManager;
}
- protected void markWorkProfileEnabledBroadcastReceived() {
- mIsWaitingToEnableWorkProfile = false;
- }
-
- protected boolean isWaitingToEnableWorkProfile() {
- return mIsWaitingToEnableWorkProfile;
- }
-
- /**
- * Overrides the default {@link Injector} for testing purposes.
- */
- @VisibleForTesting
- public void setInjector(Injector injector) {
- mInjector = injector;
- }
-
- protected boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
- return mInjector.isQuietModeEnabled(workProfileUserHandle);
+ private boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+ return mQuietModeManager.isQuietModeEnabled(workProfileUserHandle);
}
void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
mOnProfileSelectedListener = listener;
}
- void setOnSwitchOnWorkSelectedListener(OnSwitchOnWorkSelectedListener listener) {
- mOnSwitchOnWorkSelectedListener = listener;
- }
-
Context getContext() {
return mContext;
}
@@ -280,8 +235,6 @@
abstract @Nullable ViewGroup getInactiveAdapterView();
- abstract String getMetricsCategory();
-
/**
* Rebuilds the tab that is currently visible to the user.
* <p>Returns {@code true} if rebuild has completed.
@@ -317,41 +270,18 @@
}
private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) {
- if (shouldShowNoCrossProfileIntentsEmptyState(activeListAdapter)) {
+ if (shouldSkipRebuild(activeListAdapter)) {
activeListAdapter.postListReadyRunnable(doPostProcessing, /* rebuildCompleted */ true);
return false;
}
return activeListAdapter.rebuildList(doPostProcessing);
}
- private boolean shouldShowNoCrossProfileIntentsEmptyState(
- ResolverListAdapter activeListAdapter) {
- UserHandle listUserHandle = activeListAdapter.getUserHandle();
- return UserHandle.myUserId() != listUserHandle.getIdentifier()
- && allowShowNoCrossProfileIntentsEmptyState()
- && !mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(),
- UserHandle.myUserId(), listUserHandle.getIdentifier());
+ private boolean shouldSkipRebuild(ResolverListAdapter activeListAdapter) {
+ EmptyState emptyState = mEmptyStateProvider.getEmptyState(activeListAdapter);
+ return emptyState != null && emptyState.shouldSkipDataRebuild();
}
- boolean allowShowNoCrossProfileIntentsEmptyState() {
- return true;
- }
-
- protected abstract void showWorkProfileOffEmptyState(
- ResolverListAdapter activeListAdapter, View.OnClickListener listener);
-
- protected abstract void showNoPersonalToWorkIntentsEmptyState(
- ResolverListAdapter activeListAdapter);
-
- protected abstract void showNoPersonalAppsAvailableEmptyState(
- ResolverListAdapter activeListAdapter);
-
- protected abstract void showNoWorkAppsAvailableEmptyState(
- ResolverListAdapter activeListAdapter);
-
- protected abstract void showNoWorkToPersonalIntentsEmptyState(
- ResolverListAdapter activeListAdapter);
-
/**
* The empty state screens are shown according to their priority:
* <ol>
@@ -366,103 +296,88 @@
* anyway.
*/
void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) {
- if (maybeShowNoCrossProfileIntentsEmptyState(listAdapter)) {
- return;
- }
- if (maybeShowWorkProfileOffEmptyState(listAdapter)) {
- return;
- }
- maybeShowNoAppsAvailableEmptyState(listAdapter);
- }
+ final EmptyState emptyState = mEmptyStateProvider.getEmptyState(listAdapter);
- private boolean maybeShowNoCrossProfileIntentsEmptyState(ResolverListAdapter listAdapter) {
- if (!shouldShowNoCrossProfileIntentsEmptyState(listAdapter)) {
- return false;
+ if (emptyState == null) {
+ return;
}
- if (listAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
- DevicePolicyEventLogger.createEvent(
- DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL)
- .setStrings(getMetricsCategory())
- .write();
- showNoWorkToPersonalIntentsEmptyState(listAdapter);
- } else {
- DevicePolicyEventLogger.createEvent(
- DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK)
- .setStrings(getMetricsCategory())
- .write();
- showNoPersonalToWorkIntentsEmptyState(listAdapter);
+
+ emptyState.onEmptyStateShown();
+
+ View.OnClickListener clickListener = null;
+
+ if (emptyState.getButtonClickListener() != null) {
+ clickListener = v -> emptyState.getButtonClickListener().onClick(() -> {
+ ProfileDescriptor descriptor = getItem(
+ userHandleToPageIndex(listAdapter.getUserHandle()));
+ AbstractMultiProfilePagerAdapter.this.showSpinner(descriptor.getEmptyStateView());
+ });
}
- return true;
+
+ showEmptyState(listAdapter, emptyState, clickListener);
}
/**
- * Returns {@code true} if the work profile off empty state screen is shown.
+ * Class to get user id of the current process
*/
- private boolean maybeShowWorkProfileOffEmptyState(ResolverListAdapter listAdapter) {
- UserHandle listUserHandle = listAdapter.getUserHandle();
- if (!listUserHandle.equals(mWorkProfileUserHandle)
- || !mInjector.isQuietModeEnabled(mWorkProfileUserHandle)
- || listAdapter.getCount() == 0) {
- return false;
- }
- DevicePolicyEventLogger
- .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED)
- .setStrings(getMetricsCategory())
- .write();
- showWorkProfileOffEmptyState(listAdapter,
- v -> {
- ProfileDescriptor descriptor = getItem(
- userHandleToPageIndex(listAdapter.getUserHandle()));
- showSpinner(descriptor.getEmptyStateView());
- if (mOnSwitchOnWorkSelectedListener != null) {
- mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
- }
- mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle);
- });
- return true;
- }
-
- private void maybeShowNoAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- UserHandle listUserHandle = listAdapter.getUserHandle();
- if (mWorkProfileUserHandle != null
- && (UserHandle.myUserId() == listUserHandle.getIdentifier()
- || !hasAppsInOtherProfile(listAdapter))) {
- DevicePolicyEventLogger.createEvent(
- DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED)
- .setStrings(getMetricsCategory())
- .setBoolean(/*isPersonalProfile*/ listUserHandle == mPersonalProfileUserHandle)
- .write();
- if (listUserHandle == mPersonalProfileUserHandle) {
- showNoPersonalAppsAvailableEmptyState(listAdapter);
- } else {
- showNoWorkAppsAvailableEmptyState(listAdapter);
- }
- } else if (mWorkProfileUserHandle == null) {
- showConsumerUserNoAppsAvailableEmptyState(listAdapter);
+ public static class MyUserIdProvider {
+ /**
+ * @return user id of the current process
+ */
+ public int getMyUserId() {
+ return UserHandle.myUserId();
}
}
- protected void showEmptyState(ResolverListAdapter activeListAdapter, String title,
- String subtitle) {
- showEmptyState(activeListAdapter, title, subtitle, /* buttonOnClick */ null);
+ /**
+ * Utility class to check if there are cross profile intents, it is in a separate class so
+ * it could be mocked in tests
+ */
+ public static class CrossProfileIntentsChecker {
+
+ private final ContentResolver mContentResolver;
+
+ public CrossProfileIntentsChecker(@NonNull ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
+ }
+
+ /**
+ * Returns {@code true} if at least one of the provided {@code intents} can be forwarded
+ * from {@code source} (user id) to {@code target} (user id).
+ */
+ public boolean hasCrossProfileIntents(List<Intent> intents, @UserIdInt int source,
+ @UserIdInt int target) {
+ IPackageManager packageManager = AppGlobals.getPackageManager();
+
+ return intents.stream().anyMatch(intent ->
+ null != IntentForwarderActivity.canForward(intent, source, target,
+ packageManager, mContentResolver));
+ }
}
- protected void showEmptyState(ResolverListAdapter activeListAdapter,
- String title, String subtitle, View.OnClickListener buttonOnClick) {
+ protected void showEmptyState(ResolverListAdapter activeListAdapter, EmptyState emptyState,
+ View.OnClickListener buttonOnClick) {
ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(activeListAdapter.getUserHandle()));
descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
ViewGroup emptyStateView = descriptor.getEmptyStateView();
- resetViewVisibilitiesForWorkProfileEmptyState(emptyStateView);
+ resetViewVisibilitiesForEmptyState(emptyStateView);
emptyStateView.setVisibility(View.VISIBLE);
View container = emptyStateView.findViewById(R.id.resolver_empty_state_container);
setupContainerPadding(container);
TextView titleView = emptyStateView.findViewById(R.id.resolver_empty_state_title);
- titleView.setText(title);
+ String title = emptyState.getTitle();
+ if (title != null) {
+ titleView.setVisibility(View.VISIBLE);
+ titleView.setText(title);
+ } else {
+ titleView.setVisibility(View.GONE);
+ }
TextView subtitleView = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle);
+ String subtitle = emptyState.getSubtitle();
if (subtitle != null) {
subtitleView.setVisibility(View.VISIBLE);
subtitleView.setText(subtitle);
@@ -470,6 +385,9 @@
subtitleView.setVisibility(View.GONE);
}
+ View defaultEmptyText = emptyStateView.findViewById(R.id.empty);
+ defaultEmptyText.setVisibility(emptyState.useDefaultEmptyView() ? View.VISIBLE : View.GONE);
+
Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button);
button.setVisibility(buttonOnClick != null ? View.VISIBLE : View.GONE);
button.setOnClickListener(buttonOnClick);
@@ -483,22 +401,6 @@
*/
protected void setupContainerPadding(View container) {}
- private void showConsumerUserNoAppsAvailableEmptyState(ResolverListAdapter activeListAdapter) {
- ProfileDescriptor descriptor = getItem(
- userHandleToPageIndex(activeListAdapter.getUserHandle()));
- descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE);
- View emptyStateView = descriptor.getEmptyStateView();
- resetViewVisibilitiesForConsumerUserEmptyState(emptyStateView);
- emptyStateView.setVisibility(View.VISIBLE);
-
- activeListAdapter.markTabLoaded();
- }
-
- private boolean isSpinnerShowing(View emptyStateView) {
- return emptyStateView.findViewById(R.id.resolver_empty_state_progress).getVisibility()
- == View.VISIBLE;
- }
-
private void showSpinner(View emptyStateView) {
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.INVISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
@@ -506,7 +408,7 @@
emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
}
- private void resetViewVisibilitiesForWorkProfileEmptyState(View emptyStateView) {
+ private void resetViewVisibilitiesForEmptyState(View emptyStateView) {
emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.VISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.VISIBLE);
emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.INVISIBLE);
@@ -514,14 +416,6 @@
emptyStateView.findViewById(R.id.empty).setVisibility(View.GONE);
}
- private void resetViewVisibilitiesForConsumerUserEmptyState(View emptyStateView) {
- emptyStateView.findViewById(R.id.resolver_empty_state_title).setVisibility(View.GONE);
- emptyStateView.findViewById(R.id.resolver_empty_state_subtitle).setVisibility(View.GONE);
- emptyStateView.findViewById(R.id.resolver_empty_state_button).setVisibility(View.GONE);
- emptyStateView.findViewById(R.id.resolver_empty_state_progress).setVisibility(View.GONE);
- emptyStateView.findViewById(R.id.empty).setVisibility(View.VISIBLE);
- }
-
protected void showListView(ResolverListAdapter activeListAdapter) {
ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(activeListAdapter.getUserHandle()));
@@ -530,33 +424,6 @@
emptyStateView.setVisibility(View.GONE);
}
- private boolean hasCrossProfileIntents(List<Intent> intents, int source, int target) {
- IPackageManager packageManager = AppGlobals.getPackageManager();
- ContentResolver contentResolver = mContext.getContentResolver();
- for (Intent intent : intents) {
- if (IntentForwarderActivity.canForward(intent, source, target, packageManager,
- contentResolver) != null) {
- return true;
- }
- }
- return false;
- }
-
- private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) {
- if (mWorkProfileUserHandle == null) {
- return false;
- }
- List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
- adapter.getResolversForUser(UserHandle.of(UserHandle.myUserId()));
- for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
- ResolveInfo resolveInfo = info.getResolveInfoAt(0);
- if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
- return true;
- }
- }
- return false;
- }
-
boolean shouldShowEmptyStateScreen(ResolverListAdapter listAdapter) {
int count = listAdapter.getUnfilteredCount();
return (count == 0 && listAdapter.getPlaceholderCount() == 0)
@@ -600,6 +467,98 @@
}
/**
+ * Returns an empty state to show for the current profile page (tab) if necessary.
+ * This could be used e.g. to show a blocker on a tab if device management policy doesn't
+ * allow to use it or there are no apps available.
+ */
+ public interface EmptyStateProvider {
+ /**
+ * When a non-null empty state is returned the corresponding profile page will show
+ * this empty state
+ * @param resolverListAdapter the current adapter
+ */
+ @Nullable
+ default EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+ return null;
+ }
+ }
+
+ /**
+ * Empty state provider that combines multiple providers. Providers earlier in the list have
+ * priority, that is if there is a provider that returns non-null empty state then all further
+ * providers will be ignored.
+ */
+ public static class CompositeEmptyStateProvider implements EmptyStateProvider {
+
+ private final EmptyStateProvider[] mProviders;
+
+ public CompositeEmptyStateProvider(EmptyStateProvider... providers) {
+ mProviders = providers;
+ }
+
+ @Nullable
+ @Override
+ public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+ for (EmptyStateProvider provider : mProviders) {
+ EmptyState emptyState = provider.getEmptyState(resolverListAdapter);
+ if (emptyState != null) {
+ return emptyState;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Describes how the blocked empty state should look like for a profile tab
+ */
+ public interface EmptyState {
+ /**
+ * Title that will be shown on the empty state
+ */
+ @Nullable
+ default String getTitle() { return null; }
+
+ /**
+ * Subtitle that will be shown underneath the title on the empty state
+ */
+ @Nullable
+ default String getSubtitle() { return null; }
+
+ /**
+ * If non-null then a button will be shown and this listener will be called
+ * when the button is clicked
+ */
+ @Nullable
+ default ClickListener getButtonClickListener() { return null; }
+
+ /**
+ * If true then default text ('No apps can perform this action') and style for the empty
+ * state will be applied, title and subtitle will be ignored.
+ */
+ default boolean useDefaultEmptyView() { return false; }
+
+ /**
+ * Returns true if for this empty state we should skip rebuilding of the apps list
+ * for this tab.
+ */
+ default boolean shouldSkipDataRebuild() { return false; }
+
+ /**
+ * Called when empty state is shown, could be used e.g. to track analytics events
+ */
+ default void onEmptyStateShown() {}
+
+ interface ClickListener {
+ void onClick(TabControl currentTab);
+ }
+
+ interface TabControl {
+ void showSpinner();
+ }
+ }
+
+ /**
* Listener for when the user switches on the work profile from the work tab.
*/
interface OnSwitchOnWorkSelectedListener {
@@ -612,14 +571,7 @@
/**
* Describes an injector to be used for cross profile functionality. Overridable for testing.
*/
- @VisibleForTesting
- public interface Injector {
- /**
- * Returns {@code true} if at least one of the provided {@code intents} can be forwarded
- * from {@code sourceUserId} to {@code targetUserId}.
- */
- boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId, int targetUserId);
-
+ public interface QuietModeManager {
/**
* Returns whether the given profile is in quiet mode or not.
*/
@@ -629,5 +581,15 @@
* Enables or disables quiet mode for a managed profile.
*/
void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle);
+
+ /**
+ * Should be called when the work profile enabled broadcast received
+ */
+ void markWorkProfileEnabledBroadcastReceived();
+
+ /**
+ * Returns true if enabling of work profile is in progress
+ */
+ boolean isWaitingToEnableWorkProfile();
}
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 2676396..1fcfe7d 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -16,6 +16,14 @@
package com.android.internal.app;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
+import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
+import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
+
import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -114,6 +122,9 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
+import com.android.internal.app.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
import com.android.internal.app.ResolverListAdapter.ViewHolder;
import com.android.internal.app.chooser.ChooserTargetInfo;
@@ -830,6 +841,41 @@
return mChooserMultiProfilePagerAdapter;
}
+ @Override
+ protected EmptyStateProvider createBlockerEmptyStateProvider() {
+ final boolean isSendAction = isSendAction(getTargetIntent());
+
+ final EmptyState noWorkToPersonalEmptyState =
+ new DevicePolicyBlockerEmptyState(
+ /* context= */ this,
+ /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+ /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+ /* devicePolicyStringSubtitleId= */
+ isSendAction ? RESOLVER_CANT_SHARE_WITH_PERSONAL : RESOLVER_CANT_ACCESS_PERSONAL,
+ /* defaultSubtitleResource= */
+ isSendAction ? R.string.resolver_cant_share_with_personal_apps_explanation
+ : R.string.resolver_cant_access_personal_apps_explanation,
+ /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
+ /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
+
+ final EmptyState noPersonalToWorkEmptyState =
+ new DevicePolicyBlockerEmptyState(
+ /* context= */ this,
+ /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+ /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+ /* devicePolicyStringSubtitleId= */
+ isSendAction ? RESOLVER_CANT_SHARE_WITH_WORK : RESOLVER_CANT_ACCESS_WORK,
+ /* defaultSubtitleResource= */
+ isSendAction ? R.string.resolver_cant_share_with_work_apps_explanation
+ : R.string.resolver_cant_access_work_apps_explanation,
+ /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
+ /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
+
+ return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
+ noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
+ createCrossProfileIntentsChecker(), createMyUserIdProvider());
+ }
+
private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
Intent[] initialIntents,
List<ResolveInfo> rList,
@@ -844,9 +890,10 @@
return new ChooserMultiProfilePagerAdapter(
/* context */ this,
adapter,
- getPersonalProfileUserHandle(),
+ createEmptyStateProvider(/* workProfileUserHandle= */ null),
+ mQuietModeManager,
/* workProfileUserHandle= */ null,
- isSendAction(getTargetIntent()), mMaxTargetsPerRow);
+ mMaxTargetsPerRow);
}
private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
@@ -872,10 +919,11 @@
/* context */ this,
personalAdapter,
workAdapter,
+ createEmptyStateProvider(/* workProfileUserHandle= */ getWorkProfileUserHandle()),
+ mQuietModeManager,
selectedProfile,
- getPersonalProfileUserHandle(),
getWorkProfileUserHandle(),
- isSendAction(getTargetIntent()), mMaxTargetsPerRow);
+ mMaxTargetsPerRow);
}
private int findSelectedProfile() {
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index df1130b..0509b67 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -16,17 +16,7 @@
package com.android.internal.app;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
-
import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.os.UserHandle;
import android.view.LayoutInflater;
@@ -47,37 +37,37 @@
private static final int SINGLE_CELL_SPAN_SIZE = 1;
private final ChooserProfileDescriptor[] mItems;
- private final boolean mIsSendAction;
private int mBottomOffset;
private int mMaxTargetsPerRow;
ChooserMultiProfilePagerAdapter(Context context,
ChooserActivity.ChooserGridAdapter adapter,
- UserHandle personalProfileUserHandle,
+ EmptyStateProvider emptyStateProvider,
+ QuietModeManager quietModeManager,
UserHandle workProfileUserHandle,
- boolean isSendAction, int maxTargetsPerRow) {
- super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle);
+ int maxTargetsPerRow) {
+ super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
+ workProfileUserHandle);
mItems = new ChooserProfileDescriptor[] {
createProfileDescriptor(adapter)
};
- mIsSendAction = isSendAction;
mMaxTargetsPerRow = maxTargetsPerRow;
}
ChooserMultiProfilePagerAdapter(Context context,
ChooserActivity.ChooserGridAdapter personalAdapter,
ChooserActivity.ChooserGridAdapter workAdapter,
+ EmptyStateProvider emptyStateProvider,
+ QuietModeManager quietModeManager,
@Profile int defaultProfile,
- UserHandle personalProfileUserHandle,
UserHandle workProfileUserHandle,
- boolean isSendAction, int maxTargetsPerRow) {
- super(context, /* currentPage */ defaultProfile, personalProfileUserHandle,
- workProfileUserHandle);
+ int maxTargetsPerRow) {
+ super(context, /* currentPage */ defaultProfile, emptyStateProvider,
+ quietModeManager, workProfileUserHandle);
mItems = new ChooserProfileDescriptor[] {
createProfileDescriptor(personalAdapter),
createProfileDescriptor(workAdapter)
};
- mIsSendAction = isSendAction;
mMaxTargetsPerRow = maxTargetsPerRow;
}
@@ -192,112 +182,6 @@
return getListViewForIndex(1 - getCurrentPage());
}
- @Override
- String getMetricsCategory() {
- return ResolverActivity.METRICS_CATEGORY_CHOOSER;
- }
-
- @Override
- protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter,
- View.OnClickListener listener) {
- showEmptyState(activeListAdapter,
- getWorkAppPausedTitle(),
- /* subtitle = */ null,
- listener);
- }
-
- @Override
- protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
- if (mIsSendAction) {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantShareWithWorkMessage());
- } else {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantAccessWorkMessage());
- }
- }
-
- @Override
- protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
- if (mIsSendAction) {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantShareWithPersonalMessage());
- } else {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantAccessPersonalMessage());
- }
- }
-
- @Override
- protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- showEmptyState(listAdapter, getNoPersonalAppsAvailableMessage(), /* subtitle= */ null);
-
- }
-
- @Override
- protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- showEmptyState(listAdapter, getNoWorkAppsAvailableMessage(), /* subtitle = */ null);
- }
-
- private String getWorkAppPausedTitle() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_WORK_PAUSED_TITLE,
- () -> getContext().getString(R.string.resolver_turn_on_work_apps));
- }
-
- private String getCrossProfileBlockedTitle() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
- () -> getContext().getString(R.string.resolver_cross_profile_blocked));
- }
-
- private String getCantShareWithWorkMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_SHARE_WITH_WORK,
- () -> getContext().getString(
- R.string.resolver_cant_share_with_work_apps_explanation));
- }
-
- private String getCantShareWithPersonalMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_SHARE_WITH_PERSONAL,
- () -> getContext().getString(
- R.string.resolver_cant_share_with_personal_apps_explanation));
- }
-
- private String getCantAccessWorkMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_ACCESS_WORK,
- () -> getContext().getString(
- R.string.resolver_cant_access_work_apps_explanation));
- }
-
- private String getCantAccessPersonalMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_ACCESS_PERSONAL,
- () -> getContext().getString(
- R.string.resolver_cant_access_personal_apps_explanation));
- }
-
- private String getNoWorkAppsAvailableMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_NO_WORK_APPS,
- () -> getContext().getString(
- R.string.resolver_no_work_apps_available));
- }
-
- private String getNoPersonalAppsAvailableMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_NO_PERSONAL_APPS,
- () -> getContext().getString(
- R.string.resolver_no_personal_apps_available));
- }
-
-
void setEmptyStateBottomOffset(int bottomOffset) {
mBottomOffset = bottomOffset;
}
diff --git a/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java b/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
new file mode 100644
index 0000000..34249f2
--- /dev/null
+++ b/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
@@ -0,0 +1,154 @@
+/*
+ * 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.app;
+
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+import android.stats.devicepolicy.nano.DevicePolicyEnums;
+
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.internal.R;
+
+import java.util.List;
+
+/**
+ * Chooser/ResolverActivity empty state provider that returns empty state which is shown when
+ * there are no apps available.
+ */
+public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider {
+
+ @NonNull
+ private final Context mContext;
+ @Nullable
+ private final UserHandle mWorkProfileUserHandle;
+ @Nullable
+ private final UserHandle mPersonalProfileUserHandle;
+ @NonNull
+ private final String mMetricsCategory;
+ @NonNull
+ private final MyUserIdProvider mMyUserIdProvider;
+
+ public NoAppsAvailableEmptyStateProvider(Context context, UserHandle workProfileUserHandle,
+ UserHandle personalProfileUserHandle, String metricsCategory,
+ MyUserIdProvider myUserIdProvider) {
+ mContext = context;
+ mWorkProfileUserHandle = workProfileUserHandle;
+ mPersonalProfileUserHandle = personalProfileUserHandle;
+ mMetricsCategory = metricsCategory;
+ mMyUserIdProvider = myUserIdProvider;
+ }
+
+ @Nullable
+ @Override
+ @SuppressWarnings("ReferenceEquality")
+ public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+ UserHandle listUserHandle = resolverListAdapter.getUserHandle();
+
+ if (mWorkProfileUserHandle != null
+ && (mMyUserIdProvider.getMyUserId() == listUserHandle.getIdentifier()
+ || !hasAppsInOtherProfile(resolverListAdapter))) {
+
+ String title;
+ if (listUserHandle == mPersonalProfileUserHandle) {
+ title = mContext.getSystemService(
+ DevicePolicyManager.class).getResources().getString(
+ RESOLVER_NO_PERSONAL_APPS,
+ () -> mContext.getString(R.string.resolver_no_personal_apps_available));
+ } else {
+ title = mContext.getSystemService(
+ DevicePolicyManager.class).getResources().getString(
+ RESOLVER_NO_WORK_APPS,
+ () -> mContext.getString(R.string.resolver_no_work_apps_available));
+ }
+
+ return new NoAppsAvailableEmptyState(
+ title, mMetricsCategory,
+ /* isPersonalProfile= */ listUserHandle == mPersonalProfileUserHandle
+ );
+ } else if (mWorkProfileUserHandle == null) {
+ // Return default empty state without tracking
+ return new DefaultEmptyState();
+ }
+
+ return null;
+ }
+
+ private boolean hasAppsInOtherProfile(ResolverListAdapter adapter) {
+ if (mWorkProfileUserHandle == null) {
+ return false;
+ }
+ List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
+ adapter.getResolversForUser(UserHandle.of(mMyUserIdProvider.getMyUserId()));
+ for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
+ ResolveInfo resolveInfo = info.getResolveInfoAt(0);
+ if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static class DefaultEmptyState implements EmptyState {
+ @Override
+ public boolean useDefaultEmptyView() {
+ return true;
+ }
+ }
+
+ public static class NoAppsAvailableEmptyState implements EmptyState {
+
+ @NonNull
+ private String mTitle;
+
+ @NonNull
+ private String mMetricsCategory;
+
+ private boolean mIsPersonalProfile;
+
+ public NoAppsAvailableEmptyState(String title, String metricsCategory,
+ boolean isPersonalProfile) {
+ mTitle = title;
+ mMetricsCategory = metricsCategory;
+ mIsPersonalProfile = isPersonalProfile;
+ }
+
+ @Nullable
+ @Override
+ public String getTitle() {
+ return mTitle;
+ }
+
+ @Override
+ public void onEmptyStateShown() {
+ DevicePolicyEventLogger.createEvent(
+ DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED)
+ .setStrings(mMetricsCategory)
+ .setBoolean(/*isPersonalProfile*/ mIsPersonalProfile)
+ .write();
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java b/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
new file mode 100644
index 0000000..2e7d5bf
--- /dev/null
+++ b/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
@@ -0,0 +1,137 @@
+/*
+ * 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.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserHandle;
+
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+
+/**
+ * Empty state provider that does not allow cross profile sharing, it will return a blocker
+ * in case if the profile of the current tab is not the same as the profile of the calling app.
+ */
+public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider {
+
+ private final UserHandle mPersonalProfileUserHandle;
+ private final EmptyState mNoWorkToPersonalEmptyState;
+ private final EmptyState mNoPersonalToWorkEmptyState;
+ private final CrossProfileIntentsChecker mCrossProfileIntentsChecker;
+ private final MyUserIdProvider mUserIdProvider;
+
+ public NoCrossProfileEmptyStateProvider(UserHandle personalUserHandle,
+ EmptyState noWorkToPersonalEmptyState,
+ EmptyState noPersonalToWorkEmptyState,
+ CrossProfileIntentsChecker crossProfileIntentsChecker,
+ MyUserIdProvider myUserIdProvider) {
+ mPersonalProfileUserHandle = personalUserHandle;
+ mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState;
+ mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState;
+ mCrossProfileIntentsChecker = crossProfileIntentsChecker;
+ mUserIdProvider = myUserIdProvider;
+ }
+
+ @Nullable
+ @Override
+ public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+ boolean shouldShowBlocker =
+ mUserIdProvider.getMyUserId() != resolverListAdapter.getUserHandle().getIdentifier()
+ && !mCrossProfileIntentsChecker
+ .hasCrossProfileIntents(resolverListAdapter.getIntents(),
+ mUserIdProvider.getMyUserId(),
+ resolverListAdapter.getUserHandle().getIdentifier());
+
+ if (!shouldShowBlocker) {
+ return null;
+ }
+
+ if (resolverListAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) {
+ return mNoWorkToPersonalEmptyState;
+ } else {
+ return mNoPersonalToWorkEmptyState;
+ }
+ }
+
+
+ /**
+ * Empty state that gets strings from the device policy manager and tracks events into
+ * event logger of the device policy events.
+ */
+ public static class DevicePolicyBlockerEmptyState implements EmptyState {
+
+ @NonNull
+ private final Context mContext;
+ private final String mDevicePolicyStringTitleId;
+ @StringRes
+ private final int mDefaultTitleResource;
+ private final String mDevicePolicyStringSubtitleId;
+ @StringRes
+ private final int mDefaultSubtitleResource;
+ private final int mEventId;
+ @NonNull
+ private final String mEventCategory;
+
+ public DevicePolicyBlockerEmptyState(Context context, String devicePolicyStringTitleId,
+ @StringRes int defaultTitleResource, String devicePolicyStringSubtitleId,
+ @StringRes int defaultSubtitleResource,
+ int devicePolicyEventId, String devicePolicyEventCategory) {
+ mContext = context;
+ mDevicePolicyStringTitleId = devicePolicyStringTitleId;
+ mDefaultTitleResource = defaultTitleResource;
+ mDevicePolicyStringSubtitleId = devicePolicyStringSubtitleId;
+ mDefaultSubtitleResource = defaultSubtitleResource;
+ mEventId = devicePolicyEventId;
+ mEventCategory = devicePolicyEventCategory;
+ }
+
+ @Nullable
+ @Override
+ public String getTitle() {
+ return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
+ mDevicePolicyStringTitleId,
+ () -> mContext.getString(mDefaultTitleResource));
+ }
+
+ @Nullable
+ @Override
+ public String getSubtitle() {
+ return mContext.getSystemService(DevicePolicyManager.class).getResources().getString(
+ mDevicePolicyStringSubtitleId,
+ () -> mContext.getString(mDefaultSubtitleResource));
+ }
+
+ @Override
+ public void onEmptyStateShown() {
+ DevicePolicyEventLogger.createEvent(mEventId)
+ .setStrings(mEventCategory)
+ .write();
+ }
+
+ @Override
+ public boolean shouldSkipDataRebuild() {
+ return true;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 47400de..f8b764b 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -19,6 +19,9 @@
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY;
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED;
@@ -26,6 +29,8 @@
import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.PermissionChecker.PID_UNKNOWN;
+import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
+import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.annotation.Nullable;
@@ -57,6 +62,7 @@
import android.graphics.Insets;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.PatternMatcher;
@@ -93,7 +99,14 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CompositeEmptyStateProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.Profile;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
+import com.android.internal.app.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
import com.android.internal.app.chooser.ChooserTargetInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.TargetInfo;
@@ -186,6 +199,8 @@
@VisibleForTesting
protected AbstractMultiProfilePagerAdapter mMultiProfilePagerAdapter;
+ protected QuietModeManager mQuietModeManager;
+
// Intent extra for connected audio devices
public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device";
@@ -217,6 +232,9 @@
private UserHandle mWorkProfileUserHandle;
+ @Nullable
+ private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
+
protected final LatencyTracker mLatencyTracker = getLatencyTracker();
private LatencyTracker getLatencyTracker() {
@@ -375,6 +393,8 @@
setTheme(appliedThemeResId());
super.onCreate(savedInstanceState);
+ mQuietModeManager = createQuietModeManager();
+
// Determine whether we should show that intent is forwarded
// from managed profile to owner or other way around.
setProfileSwitchMessage(intent.getContentUserHint());
@@ -475,6 +495,111 @@
return resolverMultiProfilePagerAdapter;
}
+ @VisibleForTesting
+ protected MyUserIdProvider createMyUserIdProvider() {
+ return new MyUserIdProvider();
+ }
+
+ @VisibleForTesting
+ protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+ return new CrossProfileIntentsChecker(getContentResolver());
+ }
+
+ @VisibleForTesting
+ protected QuietModeManager createQuietModeManager() {
+ UserManager userManager = getSystemService(UserManager.class);
+ return new QuietModeManager() {
+
+ private boolean mIsWaitingToEnableWorkProfile = false;
+
+ @Override
+ public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
+ return userManager.isQuietModeEnabled(workProfileUserHandle);
+ }
+
+ @Override
+ public void requestQuietModeEnabled(boolean enabled, UserHandle workProfileUserHandle) {
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
+ userManager.requestQuietModeEnabled(enabled, workProfileUserHandle);
+ });
+ mIsWaitingToEnableWorkProfile = true;
+ }
+
+ @Override
+ public void markWorkProfileEnabledBroadcastReceived() {
+ mIsWaitingToEnableWorkProfile = false;
+ }
+
+ @Override
+ public boolean isWaitingToEnableWorkProfile() {
+ return mIsWaitingToEnableWorkProfile;
+ }
+ };
+ }
+
+ protected EmptyStateProvider createBlockerEmptyStateProvider() {
+ final boolean shouldShowNoCrossProfileIntentsEmptyState = getUser().equals(getIntentUser());
+
+ if (!shouldShowNoCrossProfileIntentsEmptyState) {
+ // Implementation that doesn't show any blockers
+ return new EmptyStateProvider() {};
+ }
+
+ final AbstractMultiProfilePagerAdapter.EmptyState
+ noWorkToPersonalEmptyState =
+ new DevicePolicyBlockerEmptyState(/* context= */ this,
+ /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+ /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+ /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_PERSONAL,
+ /* defaultSubtitleResource= */
+ R.string.resolver_cant_access_personal_apps_explanation,
+ /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
+ /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER);
+
+ final AbstractMultiProfilePagerAdapter.EmptyState noPersonalToWorkEmptyState =
+ new DevicePolicyBlockerEmptyState(/* context= */ this,
+ /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
+ /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
+ /* devicePolicyStringSubtitleId= */ RESOLVER_CANT_ACCESS_WORK,
+ /* defaultSubtitleResource= */
+ R.string.resolver_cant_access_work_apps_explanation,
+ /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
+ /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER);
+
+ return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
+ noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
+ createCrossProfileIntentsChecker(), createMyUserIdProvider());
+ }
+
+ protected EmptyStateProvider createEmptyStateProvider(
+ @Nullable UserHandle workProfileUserHandle) {
+ final EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider();
+
+ final EmptyStateProvider workProfileOffEmptyStateProvider =
+ new WorkProfilePausedEmptyStateProvider(this, workProfileUserHandle,
+ mQuietModeManager,
+ /* onSwitchOnWorkSelectedListener= */
+ () -> { if (mOnSwitchOnWorkSelectedListener != null) {
+ mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
+ }},
+ getMetricsCategory());
+
+ final EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider(
+ this,
+ workProfileUserHandle,
+ getPersonalProfileUserHandle(),
+ getMetricsCategory(),
+ createMyUserIdProvider()
+ );
+
+ // Return composite provider, the order matters (the higher, the more priority)
+ return new CompositeEmptyStateProvider(
+ blockerEmptyStateProvider,
+ workProfileOffEmptyStateProvider,
+ noAppsEmptyStateProvider
+ );
+ }
+
private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile(
Intent[] initialIntents,
List<ResolveInfo> rList, boolean filterLastUsed) {
@@ -485,13 +610,21 @@
rList,
filterLastUsed,
/* userHandle */ UserHandle.of(UserHandle.myUserId()));
+ QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
adapter,
- getPersonalProfileUserHandle(),
+ createEmptyStateProvider(/* workProfileUserHandle= */ null),
+ quietModeManager,
/* workProfileUserHandle= */ null);
}
+ private UserHandle getIntentUser() {
+ return getIntent().hasExtra(EXTRA_CALLING_USER)
+ ? getIntent().getParcelableExtra(EXTRA_CALLING_USER)
+ : getUser();
+ }
+
private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
Intent[] initialIntents,
List<ResolveInfo> rList,
@@ -500,9 +633,7 @@
// the intent resolver is started in the other profile. Since this is the only case when
// this happens, we check for it here and set the current profile's tab.
int selectedProfile = getCurrentProfile();
- UserHandle intentUser = getIntent().hasExtra(EXTRA_CALLING_USER)
- ? getIntent().getParcelableExtra(EXTRA_CALLING_USER)
- : getUser();
+ UserHandle intentUser = getIntentUser();
if (!getUser().equals(intentUser)) {
if (getPersonalProfileUserHandle().equals(intentUser)) {
selectedProfile = PROFILE_PERSONAL;
@@ -535,14 +666,15 @@
(filterLastUsed && UserHandle.myUserId()
== workProfileUserHandle.getIdentifier()),
/* userHandle */ workProfileUserHandle);
+ QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
personalAdapter,
workAdapter,
+ createEmptyStateProvider(getWorkProfileUserHandle()),
+ quietModeManager,
selectedProfile,
- getPersonalProfileUserHandle(),
- getWorkProfileUserHandle(),
- /* shouldShowNoCrossProfileIntentsEmptyState= */ getUser().equals(intentUser));
+ getWorkProfileUserHandle());
}
protected int appliedThemeResId() {
@@ -849,9 +981,9 @@
}
mRegistered = true;
}
- if (shouldShowTabs() && mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) {
- if (mMultiProfilePagerAdapter.isQuietModeEnabled(getWorkProfileUserHandle())) {
- mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived();
+ if (shouldShowTabs() && mQuietModeManager.isWaitingToEnableWorkProfile()) {
+ if (mQuietModeManager.isQuietModeEnabled(getWorkProfileUserHandle())) {
+ mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
}
}
mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
@@ -1811,13 +1943,12 @@
onHorizontalSwipeStateChanged(state);
}
});
- mMultiProfilePagerAdapter.setOnSwitchOnWorkSelectedListener(
- () -> {
- final View workTab = tabHost.getTabWidget().getChildAt(1);
- workTab.setFocusable(true);
- workTab.setFocusableInTouchMode(true);
- workTab.requestFocus();
- });
+ mOnSwitchOnWorkSelectedListener = () -> {
+ final View workTab = tabHost.getTabWidget().getChildAt(1);
+ workTab.setFocusable(true);
+ workTab.setFocusableInTouchMode(true);
+ workTab.requestFocus();
+ };
}
private String getPersonalTabLabel() {
@@ -2078,7 +2209,7 @@
public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) {
if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle())
- && mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) {
+ && mQuietModeManager.isWaitingToEnableWorkProfile()) {
// We have just turned on the work profile and entered the pass code to start it,
// now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no
// point in reloading the list now, since the work profile user is still
@@ -2130,7 +2261,7 @@
}
mWorkProfileHasBeenEnabled = true;
- mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived();
+ mQuietModeManager.markWorkProfileEnabledBroadcastReceived();
} else {
// Must be an UNAVAILABLE broadcast, so we watch for the next availability
mWorkProfileHasBeenEnabled = false;
@@ -2146,7 +2277,6 @@
};
}
- @VisibleForTesting
public static final class ResolvedComponentInfo {
public final ComponentName name;
private final List<Intent> mIntents = new ArrayList<>();
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 0b33501..9922051 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -16,15 +16,7 @@
package com.android.internal.app;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS;
-import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
-
import android.annotation.Nullable;
-import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.os.UserHandle;
import android.view.LayoutInflater;
@@ -43,34 +35,33 @@
public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerAdapter {
private final ResolverProfileDescriptor[] mItems;
- private final boolean mShouldShowNoCrossProfileIntentsEmptyState;
private boolean mUseLayoutWithDefault;
ResolverMultiProfilePagerAdapter(Context context,
ResolverListAdapter adapter,
- UserHandle personalProfileUserHandle,
+ EmptyStateProvider emptyStateProvider,
+ QuietModeManager quietModeManager,
UserHandle workProfileUserHandle) {
- super(context, /* currentPage */ 0, personalProfileUserHandle, workProfileUserHandle);
+ super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
+ workProfileUserHandle);
mItems = new ResolverProfileDescriptor[] {
createProfileDescriptor(adapter)
};
- mShouldShowNoCrossProfileIntentsEmptyState = true;
}
ResolverMultiProfilePagerAdapter(Context context,
ResolverListAdapter personalAdapter,
ResolverListAdapter workAdapter,
+ EmptyStateProvider emptyStateProvider,
+ QuietModeManager quietModeManager,
@Profile int defaultProfile,
- UserHandle personalProfileUserHandle,
- UserHandle workProfileUserHandle,
- boolean shouldShowNoCrossProfileIntentsEmptyState) {
- super(context, /* currentPage */ defaultProfile, personalProfileUserHandle,
+ UserHandle workProfileUserHandle) {
+ super(context, /* currentPage */ defaultProfile, emptyStateProvider, quietModeManager,
workProfileUserHandle);
mItems = new ResolverProfileDescriptor[] {
createProfileDescriptor(personalAdapter),
createProfileDescriptor(workAdapter)
};
- mShouldShowNoCrossProfileIntentsEmptyState = shouldShowNoCrossProfileIntentsEmptyState;
}
private ResolverProfileDescriptor createProfileDescriptor(
@@ -170,93 +161,6 @@
return getListViewForIndex(1 - getCurrentPage());
}
- @Override
- String getMetricsCategory() {
- return ResolverActivity.METRICS_CATEGORY_RESOLVER;
- }
-
- @Override
- boolean allowShowNoCrossProfileIntentsEmptyState() {
- return mShouldShowNoCrossProfileIntentsEmptyState;
- }
-
- @Override
- protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter,
- View.OnClickListener listener) {
- showEmptyState(activeListAdapter,
- getWorkAppPausedTitle(),
- /* subtitle = */ null,
- listener);
- }
-
- @Override
- protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantAccessWorkMessage());
- }
-
- @Override
- protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
- showEmptyState(activeListAdapter,
- getCrossProfileBlockedTitle(),
- getCantAccessPersonalMessage());
- }
-
- @Override
- protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- showEmptyState(listAdapter,
- getNoPersonalAppsAvailableMessage(),
- /* subtitle = */ null);
- }
-
- @Override
- protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) {
- showEmptyState(listAdapter,
- getNoWorkAppsAvailableMessage(),
- /* subtitle= */ null);
- }
-
- private String getWorkAppPausedTitle() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_WORK_PAUSED_TITLE,
- () -> getContext().getString(R.string.resolver_turn_on_work_apps));
- }
-
- private String getCrossProfileBlockedTitle() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
- () -> getContext().getString(R.string.resolver_cross_profile_blocked));
- }
-
- private String getCantAccessWorkMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_ACCESS_WORK,
- () -> getContext().getString(
- R.string.resolver_cant_access_work_apps_explanation));
- }
-
- private String getCantAccessPersonalMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_CANT_ACCESS_PERSONAL,
- () -> getContext().getString(
- R.string.resolver_cant_access_personal_apps_explanation));
- }
-
- private String getNoWorkAppsAvailableMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_NO_WORK_APPS,
- () -> getContext().getString(
- R.string.resolver_no_work_apps_available));
- }
-
- private String getNoPersonalAppsAvailableMessage() {
- return getContext().getSystemService(DevicePolicyManager.class).getResources().getString(
- RESOLVER_NO_PERSONAL_APPS,
- () -> getContext().getString(
- R.string.resolver_no_personal_apps_available));
- }
-
void setUseLayoutWithDefault(boolean useLayoutWithDefault) {
mUseLayoutWithDefault = useLayoutWithDefault;
}
diff --git a/core/java/com/android/internal/app/WorkProfilePausedEmptyStateProvider.java b/core/java/com/android/internal/app/WorkProfilePausedEmptyStateProvider.java
new file mode 100644
index 0000000..6a88557
--- /dev/null
+++ b/core/java/com/android/internal/app/WorkProfilePausedEmptyStateProvider.java
@@ -0,0 +1,114 @@
+/*
+ * 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.app;
+
+import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.stats.devicepolicy.nano.DevicePolicyEnums;
+
+import com.android.internal.R;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
+
+/**
+ * Chooser/ResolverActivity empty state provider that returns empty state which is shown when
+ * work profile is paused and we need to show a button to enable it.
+ */
+public class WorkProfilePausedEmptyStateProvider implements EmptyStateProvider {
+
+ private final UserHandle mWorkProfileUserHandle;
+ private final QuietModeManager mQuietModeManager;
+ private final String mMetricsCategory;
+ private final OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
+ private final Context mContext;
+
+ public WorkProfilePausedEmptyStateProvider(@NonNull Context context,
+ @Nullable UserHandle workProfileUserHandle,
+ @NonNull QuietModeManager quietModeManager,
+ @Nullable OnSwitchOnWorkSelectedListener onSwitchOnWorkSelectedListener,
+ @NonNull String metricsCategory) {
+ mContext = context;
+ mWorkProfileUserHandle = workProfileUserHandle;
+ mQuietModeManager = quietModeManager;
+ mMetricsCategory = metricsCategory;
+ mOnSwitchOnWorkSelectedListener = onSwitchOnWorkSelectedListener;
+ }
+
+ @Nullable
+ @Override
+ public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
+ if (!resolverListAdapter.getUserHandle().equals(mWorkProfileUserHandle)
+ || !mQuietModeManager.isQuietModeEnabled(mWorkProfileUserHandle)
+ || resolverListAdapter.getCount() == 0) {
+ return null;
+ }
+
+ final String title = mContext.getSystemService(DevicePolicyManager.class)
+ .getResources().getString(RESOLVER_WORK_PAUSED_TITLE,
+ () -> mContext.getString(R.string.resolver_turn_on_work_apps));
+
+ return new WorkProfileOffEmptyState(title, (tab) -> {
+ tab.showSpinner();
+ if (mOnSwitchOnWorkSelectedListener != null) {
+ mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected();
+ }
+ mQuietModeManager.requestQuietModeEnabled(false, mWorkProfileUserHandle);
+ }, mMetricsCategory);
+ }
+
+ public static class WorkProfileOffEmptyState implements EmptyState {
+
+ private final String mTitle;
+ private final ClickListener mOnClick;
+ private final String mMetricsCategory;
+
+ public WorkProfileOffEmptyState(String title, @NonNull ClickListener onClick,
+ @NonNull String metricsCategory) {
+ mTitle = title;
+ mOnClick = onClick;
+ mMetricsCategory = metricsCategory;
+ }
+
+ @Nullable
+ @Override
+ public String getTitle() {
+ return mTitle;
+ }
+
+ @Nullable
+ @Override
+ public ClickListener getButtonClickListener() {
+ return mOnClick;
+ }
+
+ @Override
+ public void onEmptyStateShown() {
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED)
+ .setStrings(mMetricsCategory)
+ .write();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 0f64f6d..292a50b 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -584,6 +584,13 @@
public static final String CLIPBOARD_OVERLAY_SHOW_ACTIONS = "clipboard_overlay_show_actions";
/**
+ * (boolean) Whether to ignore the source package for determining whether to use remote copy
+ * behavior in the clipboard UI.
+ */
+ public static final String CLIPBOARD_IGNORE_REMOTE_COPY_SOURCE =
+ "clipboard_ignore_remote_copy_source";
+
+ /**
* (boolean) Whether to combine the broadcasts APPWIDGET_ENABLED and APPWIDGET_UPDATE
*/
public static final String COMBINED_BROADCAST_ENABLED = "combined_broadcast_enabled";
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index a352063..f998a69 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -2402,7 +2402,7 @@
return;
}
final ThreadedRenderer renderer = getThreadedRenderer();
- if (renderer != null) {
+ if (renderer != null && !CAPTION_ON_SHELL) {
loadBackgroundDrawablesIfNeeded();
WindowInsets rootInsets = getRootWindowInsets();
mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer,
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 1d4b246..56e1a87 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -325,4 +325,7 @@
/** Dump protos from SystemUI. The proto definition is defined there */
void dumpProto(in String[] args, in ParcelFileDescriptor pfd);
+
+ /** Shows rear display educational dialog */
+ void showRearDisplayDialog(int currentBaseState);
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index ef8f2db..e140ce8 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -226,4 +226,7 @@
/** Unregisters a nearby media devices provider. */
void unregisterNearbyMediaDevicesProvider(in INearbyMediaDevicesProvider provider);
+
+ /** Shows rear display educational dialog */
+ void showRearDisplayDialog(int currentBaseState);
}
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 21bbac0..c8b85c3 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1176,6 +1176,11 @@
closedir(dir);
}
+static bool is_sdk_sandbox_uid(uid_t uid) {
+ appid_t appId = multiuser_get_app_id(uid);
+ return appId >= AID_SDK_SANDBOX_PROCESS_START && appId <= AID_SDK_SANDBOX_PROCESS_END;
+}
+
/**
* Make other apps data directory not visible in CE, DE storage.
*
@@ -1262,68 +1267,77 @@
}
closedir(dir);
- // Prepare default dirs for user 0 as user 0 always exists.
- int result = symlink("/data/data", "/data/user/0");
- if (result != 0) {
- fail_fn(CREATE_ERROR("Failed to create symlink /data/user/0 %s", strerror(errno)));
- }
- PrepareDirIfNotPresent("/data/user_de/0", DEFAULT_DATA_DIR_PERMISSION,
- AID_ROOT, AID_ROOT, fail_fn);
-
- for (int i = 0; i < size; i += 3) {
- std::string const & packageName = merged_data_info_list[i];
- std::string const & volUuid = merged_data_info_list[i + 1];
- std::string const & inode = merged_data_info_list[i + 2];
-
- std::string::size_type sz;
- long long ceDataInode = std::stoll(inode, &sz);
-
- std::string actualCePath, actualDePath;
- if (volUuid.compare("null") != 0) {
- // Volume that is stored in /mnt/expand
- char volPath[PATH_MAX];
- char volCePath[PATH_MAX];
- char volDePath[PATH_MAX];
- char volCeUserPath[PATH_MAX];
- char volDeUserPath[PATH_MAX];
-
- snprintf(volPath, PATH_MAX, "/mnt/expand/%s", volUuid.c_str());
- snprintf(volCePath, PATH_MAX, "%s/user", volPath);
- snprintf(volDePath, PATH_MAX, "%s/user_de", volPath);
- snprintf(volCeUserPath, PATH_MAX, "%s/%d", volCePath, userId);
- snprintf(volDeUserPath, PATH_MAX, "%s/%d", volDePath, userId);
-
- PrepareDirIfNotPresent(volPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
- PrepareDirIfNotPresent(volCePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
- PrepareDirIfNotPresent(volDePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
- PrepareDirIfNotPresent(volCeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
- fail_fn);
- PrepareDirIfNotPresent(volDeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
- fail_fn);
-
- actualCePath = volCeUserPath;
- actualDePath = volDeUserPath;
- } else {
- // Internal volume that stored in /data
- char internalCeUserPath[PATH_MAX];
- char internalDeUserPath[PATH_MAX];
- snprintf(internalCeUserPath, PATH_MAX, "/data/user/%d", userId);
- snprintf(internalDeUserPath, PATH_MAX, "/data/user_de/%d", userId);
- // If it's not user 0, create /data/user/$USER.
- if (userId == 0) {
- actualCePath = internalLegacyCePath;
- } else {
- PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION,
- AID_ROOT, AID_ROOT, fail_fn);
- actualCePath = internalCeUserPath;
+ // No bind mounting of app data should occur in the case of a sandbox process since SDK sandboxes
+ // should not be able to read app data. Tmpfs was mounted however since a sandbox should not have
+ // access to app data.
+ if (!is_sdk_sandbox_uid(uid)) {
+ // Prepare default dirs for user 0 as user 0 always exists.
+ int result = symlink("/data/data", "/data/user/0");
+ if (result != 0) {
+ fail_fn(CREATE_ERROR("Failed to create symlink /data/user/0 %s", strerror(errno)));
}
- PrepareDirIfNotPresent(internalDeUserPath, DEFAULT_DATA_DIR_PERMISSION,
- AID_ROOT, AID_ROOT, fail_fn);
- actualDePath = internalDeUserPath;
- }
- isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode,
- actualCePath, actualDePath, fail_fn);
+ PrepareDirIfNotPresent("/data/user_de/0", DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+
+ for (int i = 0; i < size; i += 3) {
+ std::string const& packageName = merged_data_info_list[i];
+ std::string const& volUuid = merged_data_info_list[i + 1];
+ std::string const& inode = merged_data_info_list[i + 2];
+
+ std::string::size_type sz;
+ long long ceDataInode = std::stoll(inode, &sz);
+
+ std::string actualCePath, actualDePath;
+ if (volUuid.compare("null") != 0) {
+ // Volume that is stored in /mnt/expand
+ char volPath[PATH_MAX];
+ char volCePath[PATH_MAX];
+ char volDePath[PATH_MAX];
+ char volCeUserPath[PATH_MAX];
+ char volDeUserPath[PATH_MAX];
+
+ snprintf(volPath, PATH_MAX, "/mnt/expand/%s", volUuid.c_str());
+ snprintf(volCePath, PATH_MAX, "%s/user", volPath);
+ snprintf(volDePath, PATH_MAX, "%s/user_de", volPath);
+ snprintf(volCeUserPath, PATH_MAX, "%s/%d", volCePath, userId);
+ snprintf(volDeUserPath, PATH_MAX, "%s/%d", volDePath, userId);
+
+ PrepareDirIfNotPresent(volPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+ PrepareDirIfNotPresent(volCePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+ PrepareDirIfNotPresent(volDePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+ PrepareDirIfNotPresent(volCeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+ PrepareDirIfNotPresent(volDeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+
+ actualCePath = volCeUserPath;
+ actualDePath = volDeUserPath;
+ } else {
+ // Internal volume that stored in /data
+ char internalCeUserPath[PATH_MAX];
+ char internalDeUserPath[PATH_MAX];
+ snprintf(internalCeUserPath, PATH_MAX, "/data/user/%d", userId);
+ snprintf(internalDeUserPath, PATH_MAX, "/data/user_de/%d", userId);
+ // If it's not user 0, create /data/user/$USER.
+ if (userId == 0) {
+ actualCePath = internalLegacyCePath;
+ } else {
+ PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT,
+ AID_ROOT, fail_fn);
+ actualCePath = internalCeUserPath;
+ }
+ PrepareDirIfNotPresent(internalDeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT,
+ AID_ROOT, fail_fn);
+ actualDePath = internalDeUserPath;
+ }
+ isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode, actualCePath,
+ actualDePath, fail_fn);
+ }
}
+
// We set the label AFTER everything is done, as we are applying
// the file operations on tmpfs. If we set the label when we mount
// tmpfs, SELinux will not happy as we are changing system_data_files.
@@ -1363,6 +1377,167 @@
freecon(dataDataContext);
}
+/**
+ * Without sdk sandbox data isolation, the sandbox could detect if another app is installed on the
+ * system by "touching" other data directories like /data/misc_ce/0/sdksandbox/com.whatsapp, similar
+ * to apps without app data isolation (see {@link #isolateAppData()}).
+ *
+ * To prevent this, tmpfs is mounted onto misc_ce and misc_de directories on all possible volumes in
+ * a separate mount namespace. The sandbox directory path is then created containing the name of the
+ * client app package associated with the sdk sandbox. The contents for this (sdk level storage and
+ * shared sdk storage) are bind mounted from the sandbox data mirror.
+ */
+static void isolateSdkSandboxData(JNIEnv* env, jobjectArray pkg_data_info_list, uid_t uid,
+ const char* process_name, jstring managed_nice_name,
+ fail_fn_t fail_fn) {
+ const userid_t userId = multiuser_get_user_id(uid);
+
+ int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
+ // The sandbox should only have information of one associated client app (package, uuid, inode)
+ if (size != 3) {
+ fail_fn(CREATE_ERROR(
+ "Unable to isolate sandbox data, incorrect associated app information"));
+ }
+
+ auto extract_fn = [env, process_name, managed_nice_name,
+ pkg_data_info_list](int info_list_idx) {
+ jstring jstr = (jstring)(env->GetObjectArrayElement(pkg_data_info_list, info_list_idx));
+ return ExtractJString(env, process_name, managed_nice_name, jstr).value();
+ };
+ std::string packageName = extract_fn(0);
+ std::string volUuid = extract_fn(1);
+
+ char internalCePath[PATH_MAX];
+ char internalDePath[PATH_MAX];
+ char externalPrivateMountPath[PATH_MAX];
+ snprintf(internalCePath, PATH_MAX, "/data/misc_ce");
+ snprintf(internalDePath, PATH_MAX, "/data/misc_de");
+ snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand");
+
+ char ceUserPath[PATH_MAX];
+ char deUserPath[PATH_MAX];
+ if (volUuid != "null") {
+ snprintf(ceUserPath, PATH_MAX, "%s/%s/misc_ce/%d", externalPrivateMountPath,
+ volUuid.c_str(), userId);
+ snprintf(deUserPath, PATH_MAX, "%s/%s/misc_de/%d", externalPrivateMountPath,
+ volUuid.c_str(), userId);
+ } else {
+ snprintf(ceUserPath, PATH_MAX, "%s/%d", internalCePath, userId);
+ snprintf(deUserPath, PATH_MAX, "%s/%d", internalDePath, userId);
+ }
+
+ char ceSandboxPath[PATH_MAX];
+ char deSandboxPath[PATH_MAX];
+ snprintf(ceSandboxPath, PATH_MAX, "%s/sdksandbox", ceUserPath);
+ snprintf(deSandboxPath, PATH_MAX, "%s/sdksandbox", deUserPath);
+
+ // If the client app using the sandbox has been installed when the device is locked and the
+ // sandbox starts up when the device is locked, sandbox storage might not have been created.
+ // In that case, mount tmpfs for data isolation, but don't bind mount.
+ bool bindMountCeSandboxDataDirs = true;
+ bool bindMountDeSandboxDataDirs = true;
+ if (access(ceSandboxPath, F_OK) != 0) {
+ bindMountCeSandboxDataDirs = false;
+ }
+ if (access(deSandboxPath, F_OK) != 0) {
+ bindMountDeSandboxDataDirs = false;
+ }
+
+ char* context = nullptr;
+ char* userContext = nullptr;
+ char* sandboxContext = nullptr;
+ if (getfilecon(internalDePath, &context) < 0) {
+ fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", internalDePath, strerror(errno)));
+ }
+ if (bindMountDeSandboxDataDirs) {
+ if (getfilecon(deUserPath, &userContext) < 0) {
+ fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", deUserPath, strerror(errno)));
+ }
+ if (getfilecon(deSandboxPath, &sandboxContext) < 0) {
+ fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", deSandboxPath, strerror(errno)));
+ }
+ }
+
+ MountAppDataTmpFs(internalCePath, fail_fn);
+ MountAppDataTmpFs(internalDePath, fail_fn);
+
+ // Mount tmpfs on all external volumes
+ DIR* dir = opendir(externalPrivateMountPath);
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath));
+ }
+ struct dirent* ent;
+ while ((ent = readdir(dir))) {
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
+ if (ent->d_type != DT_DIR) {
+ fail_fn(CREATE_ERROR("Unexpected type: %d %s", ent->d_type, ent->d_name));
+ }
+ auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name);
+ auto externalCePath = StringPrintf("%s/misc_ce", volPath.c_str());
+ auto externalDePath = StringPrintf("%s/misc_de", volPath.c_str());
+
+ WaitUntilDirReady(externalCePath.c_str(), fail_fn);
+ MountAppDataTmpFs(externalCePath.c_str(), fail_fn);
+ WaitUntilDirReady(externalDePath.c_str(), fail_fn);
+ MountAppDataTmpFs(externalDePath.c_str(), fail_fn);
+ }
+ closedir(dir);
+
+ char mirrorCeSandboxPath[PATH_MAX];
+ char mirrorDeSandboxPath[PATH_MAX];
+ snprintf(mirrorCeSandboxPath, PATH_MAX, "/data_mirror/misc_ce/%s/%d/sdksandbox",
+ volUuid.c_str(), userId);
+ snprintf(mirrorDeSandboxPath, PATH_MAX, "/data_mirror/misc_de/%s/%d/sdksandbox",
+ volUuid.c_str(), userId);
+
+ if (bindMountCeSandboxDataDirs) {
+ PrepareDir(ceUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDir(ceSandboxPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ // TODO(b/231322885): Use inode numbers to find the correct app path when the device locked.
+ createAndMountAppData(packageName, packageName, mirrorCeSandboxPath, ceSandboxPath, fail_fn,
+ true /*call_fail_fn*/);
+
+ relabelDir(ceSandboxPath, sandboxContext, fail_fn);
+ relabelDir(ceUserPath, userContext, fail_fn);
+ }
+ if (bindMountDeSandboxDataDirs) {
+ PrepareDir(deUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDir(deSandboxPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ createAndMountAppData(packageName, packageName, mirrorDeSandboxPath, deSandboxPath, fail_fn,
+ true /*call_fail_fn*/);
+
+ relabelDir(deSandboxPath, sandboxContext, fail_fn);
+ relabelDir(deUserPath, userContext, fail_fn);
+ }
+
+ // We set the label AFTER everything is done, as we are applying
+ // the file operations on tmpfs. If we set the label when we mount
+ // tmpfs, SELinux will not happy as we are changing system_data_files.
+ relabelDir(internalCePath, context, fail_fn);
+ relabelDir(internalDePath, context, fail_fn);
+
+ // Relabel CE and DE dirs under /mnt/expand
+ dir = opendir(externalPrivateMountPath);
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath));
+ }
+ while ((ent = readdir(dir))) {
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
+ auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name);
+ auto externalCePath = StringPrintf("%s/misc_ce", volPath.c_str());
+ auto externalDePath = StringPrintf("%s/misc_de", volPath.c_str());
+ relabelDir(externalCePath.c_str(), context, fail_fn);
+ relabelDir(externalDePath.c_str(), context, fail_fn);
+ }
+ closedir(dir);
+
+ if (bindMountDeSandboxDataDirs) {
+ freecon(sandboxContext);
+ freecon(userContext);
+ }
+ freecon(context);
+}
+
static void insertPackagesToMergedList(JNIEnv* env,
std::vector<std::string>& merged_data_info_list,
jobjectArray data_info_list, const char* process_name,
@@ -1428,6 +1603,12 @@
MountAppDataTmpFs(kCurProfileDirPath, fail_fn);
MountAppDataTmpFs(kRefProfileDirPath, fail_fn);
+ // Sandbox processes do not have JIT profile, so no data needs to be bind mounted. However, it
+ // should still not have access to JIT profile, so tmpfs is mounted.
+ if (is_sdk_sandbox_uid(uid)) {
+ return;
+ }
+
// Create profile directory for this user.
std::string actualCurUserProfile = StringPrintf("%s/%d", kCurProfileDirPath, user_id);
PrepareDir(actualCurUserProfile, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
@@ -1580,9 +1761,15 @@
// Make sure app is running in its own mount namespace before isolating its data directories.
ensureInAppMountNamespace(fail_fn);
- // Sandbox data and jit profile directories by overlaying a tmpfs on those dirs and bind
- // mount all related packages separately.
+ // Isolate app data, jit profile and sandbox data directories by overlaying a tmpfs on those
+ // dirs and bind mount all related packages separately.
if (mount_data_dirs) {
+ // Sdk sandbox data isolation does not need to occur for app processes since sepolicy
+ // prevents access to sandbox data anyway.
+ if (is_sdk_sandbox_uid(uid)) {
+ isolateSdkSandboxData(env, pkg_data_info_list, uid, process_name, managed_nice_name,
+ fail_fn);
+ }
isolateAppData(env, pkg_data_info_list, allowlisted_data_info_list, uid, process_name,
managed_nice_name, fail_fn);
isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 285258a..556636dd 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -88,6 +88,7 @@
optional SettingProto odi_captions_volume_ui_enabled = 42 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Setting for accessibility magnification for following typing.
optional SettingProto accessibility_magnification_follow_typing_enabled = 43 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto contrast_level = 44 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index bd4f990..004d5d6 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -189,6 +189,8 @@
optional bool is_enhanced_discharge_prediction_personalized = 54;
optional bool is_low_power_standby_active = 55;
optional LowPowerStandbyControllerDumpProto low_power_standby_controller = 56;
+ // The battery level drained by the dream.
+ optional int32 battery_level_drained_while_dreaming = 57;
}
// A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index e597330..be509d1 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -235,7 +235,7 @@
<string name="shutdown_confirm" product="default" msgid="136816458966692315">"Telefon će se isključiti."</string>
<string name="shutdown_confirm_question" msgid="796151167261608447">"Želite li ugasiti telefon?"</string>
<string name="reboot_safemode_title" msgid="5853949122655346734">"Ponovo pokreni uređaj u sigurnom načinu rada"</string>
- <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Želite li pokrenuti uređaj u sigurnom načinu rada? To će onemogućiti sve aplikacije trećih strana koje ste instalirali. One će biti obnovljene kada ponovo pokrenete uređaj."</string>
+ <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Želite li ponovo pokrenuti uređaj u sigurnom načinu rada? To će onemogućiti sve aplikacije trećih strana koje ste instalirali. Obnovit će se kada još jednom ponovo pokrenete uređaj."</string>
<string name="recent_tasks_title" msgid="8183172372995396653">"Nedavni zadaci"</string>
<string name="no_recent_tasks" msgid="9063946524312275906">"Nema nedavno pokrenutih aplikacija."</string>
<string name="global_actions" product="tablet" msgid="4412132498517933867">"Opcije tableta"</string>
@@ -1015,7 +1015,7 @@
<string name="factorytest_failed" msgid="3190979160945298006">"Fabrički test nije uspio"</string>
<string name="factorytest_not_system" msgid="5658160199925519869">"Akcija FACTORY_TEST podržana je samo za pakete instalirane u facsikli /system/app."</string>
<string name="factorytest_no_action" msgid="339252838115675515">"Nije pronađen paket koji omogućava akciju FACTORY_TEST."</string>
- <string name="factorytest_reboot" msgid="2050147445567257365">"Ponovno pokretanje"</string>
+ <string name="factorytest_reboot" msgid="2050147445567257365">"Ponovo pokreni"</string>
<string name="js_dialog_title" msgid="7464775045615023241">"Stranica na \"<xliff:g id="TITLE">%s</xliff:g>\" kaže:"</string>
<string name="js_dialog_title_default" msgid="3769524569903332476">"JavaScript"</string>
<string name="js_dialog_before_unload_title" msgid="7012587995876771246">"Potvrdite navigaciju"</string>
@@ -1375,7 +1375,7 @@
<string name="console_running_notification_title" msgid="6087888939261635904">"Serijska konzola omogućena"</string>
<string name="console_running_notification_message" msgid="7892751888125174039">"Performanse su smanjene. Da onemogućite, provjerite program za učitavanje operativnog sistema."</string>
<string name="mte_override_notification_title" msgid="4731115381962792944">"Eksperimentalni MTE je omogućen"</string>
- <string name="mte_override_notification_message" msgid="2441170442725738942">"Moguće da će to uticati na performanse i stabilnost. Ponovo pokrenite da onemogućite. Ako je omogućeno pomoću arm64.memtag.bootctl, unaprijed ga postavite na \"Ništa\"."</string>
+ <string name="mte_override_notification_message" msgid="2441170442725738942">"To može uticati na performanse i stabilnost. Ponovo pokrenite da onemogućite. Ako je omogućeno pomoću arm64.memtag.bootctl, prvo postavite na \"Ništa\"."</string>
<string name="usb_contaminant_detected_title" msgid="4359048603069159678">"Tečnost ili nečistoće u USB priključku"</string>
<string name="usb_contaminant_detected_message" msgid="7346100585390795743">"USB priključak je automatski onemogućen. Dodirnite da saznate više."</string>
<string name="usb_contaminant_not_detected_title" msgid="2651167729563264053">"USB priključak je sada sigurno koristiti"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index cbed459..b11e540 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -33,18 +33,18 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Voicemail"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Connection problem or invalid MMI code."</string>
- <string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialling numbers only."</string>
- <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Cannot change call forwarding settings from your phone while you are roaming."</string>
+ <string name="mmiFdnError" msgid="3975490266767565852">"Operation is restricted to fixed dialing numbers only."</string>
+ <string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Can not change call forwarding settings from your phone while you are roaming."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Service was enabled."</string>
<string name="serviceEnabledFor" msgid="1463104778656711613">"Service was enabled for:"</string>
<string name="serviceDisabled" msgid="641878791205871379">"Service has been disabled."</string>
<string name="serviceRegistered" msgid="3856192211729577482">"Registration was successful."</string>
- <string name="serviceErased" msgid="997354043770513494">"Erase successful."</string>
+ <string name="serviceErased" msgid="997354043770513494">"Erasure was successful."</string>
<string name="passwordIncorrect" msgid="917087532676155877">"Incorrect password."</string>
<string name="mmiComplete" msgid="6341884570892520140">"MMI complete."</string>
- <string name="badPin" msgid="888372071306274355">"The old PIN that you typed is incorrect."</string>
- <string name="badPuk" msgid="4232069163733147376">"The PUK that you typed isn\'t correct."</string>
- <string name="mismatchPin" msgid="2929611853228707473">"The PINs that you typed don\'t match."</string>
+ <string name="badPin" msgid="888372071306274355">"The old PIN you typed isn\'t correct."</string>
+ <string name="badPuk" msgid="4232069163733147376">"The PUK you typed isn\'t correct."</string>
+ <string name="mismatchPin" msgid="2929611853228707473">"The PINs you typed don\'t match."</string>
<string name="invalidPin" msgid="7542498253319440408">"Type a PIN that is 4 to 8 numbers."</string>
<string name="invalidPuk" msgid="8831151490931907083">"Type a PUK that is 8 numbers or longer."</string>
<string name="needPuk" msgid="7321876090152422918">"Your SIM card is PUK-locked. Type the PUK code to unlock it."</string>
@@ -57,7 +57,7 @@
<string name="imei" msgid="2157082351232630390">"IMEI"</string>
<string name="meid" msgid="3291227361605924674">"MEID"</string>
<string name="ClipMmi" msgid="4110549342447630629">"Incoming Caller ID"</string>
- <string name="ClirMmi" msgid="6752346475055446417">"Hide outgoing caller ID"</string>
+ <string name="ClirMmi" msgid="6752346475055446417">"Hide Outgoing Caller ID"</string>
<string name="ColpMmi" msgid="4736462893284419302">"Connected Line ID"</string>
<string name="ColrMmi" msgid="5889782479745764278">"Connected Line ID Restriction"</string>
<string name="CfMmi" msgid="8390012691099787178">"Call forwarding"</string>
@@ -67,7 +67,7 @@
<string name="PinMmi" msgid="7133542099618330959">"PIN change"</string>
<string name="CnipMmi" msgid="4897531155968151160">"Calling number present"</string>
<string name="CnirMmi" msgid="885292039284503036">"Calling number restricted"</string>
- <string name="ThreeWCMmi" msgid="2436550866139999411">"Three-way calling"</string>
+ <string name="ThreeWCMmi" msgid="2436550866139999411">"Three way calling"</string>
<string name="RuacMmi" msgid="1876047385848991110">"Rejection of undesired annoying calls"</string>
<string name="CndMmi" msgid="185136449405618437">"Calling number delivery"</string>
<string name="DndMmi" msgid="8797375819689129800">"Do not disturb"</string>
@@ -93,7 +93,7 @@
<string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Mobile data status"</string>
<string name="notification_channel_sms" msgid="1243384981025535724">"SMS messages"</string>
<string name="notification_channel_voice_mail" msgid="8457433203106654172">"Voicemail messages"</string>
- <string name="notification_channel_wfc" msgid="9048240466765169038">"Wi-Fi Calling"</string>
+ <string name="notification_channel_wfc" msgid="9048240466765169038">"Wi-Fi calling"</string>
<string name="notification_channel_sim" msgid="5098802350325677490">"SIM status"</string>
<string name="notification_channel_sim_high_prio" msgid="642361929452850928">"High priority SIM status"</string>
<string name="peerTtyModeFull" msgid="337553730440832160">"Peer requested TTY Mode FULL"</string>
@@ -111,7 +111,7 @@
<string name="roamingText0" msgid="7793257871609854208">"Roaming Indicator On"</string>
<string name="roamingText1" msgid="5073028598334616445">"Roaming Indicator Off"</string>
<string name="roamingText2" msgid="2834048284153110598">"Roaming Indicator Flashing"</string>
- <string name="roamingText3" msgid="831690234035748988">"Out of local area"</string>
+ <string name="roamingText3" msgid="831690234035748988">"Out of Neighborhood"</string>
<string name="roamingText4" msgid="2171252529065590728">"Out of Building"</string>
<string name="roamingText5" msgid="4294671587635796641">"Roaming - Preferred System"</string>
<string name="roamingText6" msgid="5536156746637992029">"Roaming - Available System"</string>
@@ -132,15 +132,15 @@
<!-- no translation found for wfcSpnFormat_spn (2982505428519096311) -->
<skip />
<string name="wfcSpnFormat_spn_wifi_calling" msgid="3165949348000906194">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi Calling"</string>
- <string name="wfcSpnFormat_spn_wifi_calling_vo_hyphen" msgid="3836827895369365298">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi calling"</string>
- <string name="wfcSpnFormat_wlan_call" msgid="4895315549916165700">"WLAN call"</string>
+ <string name="wfcSpnFormat_spn_wifi_calling_vo_hyphen" msgid="3836827895369365298">"<xliff:g id="SPN">%s</xliff:g> WiFi Calling"</string>
+ <string name="wfcSpnFormat_wlan_call" msgid="4895315549916165700">"WLAN Call"</string>
<string name="wfcSpnFormat_spn_wlan_call" msgid="255919245825481510">"<xliff:g id="SPN">%s</xliff:g> WLAN Call"</string>
<string name="wfcSpnFormat_spn_wifi" msgid="7232899594327126970">"<xliff:g id="SPN">%s</xliff:g> Wi-Fi"</string>
- <string name="wfcSpnFormat_wifi_calling_bar_spn" msgid="8383917598312067365">"Wi-Fi Calling | <xliff:g id="SPN">%s</xliff:g>"</string>
+ <string name="wfcSpnFormat_wifi_calling_bar_spn" msgid="8383917598312067365">"WiFi Calling | <xliff:g id="SPN">%s</xliff:g>"</string>
<string name="wfcSpnFormat_spn_vowifi" msgid="6865214948822061486">"<xliff:g id="SPN">%s</xliff:g> VoWifi"</string>
<string name="wfcSpnFormat_wifi_calling" msgid="6178935388378661755">"Wi-Fi Calling"</string>
<string name="wfcSpnFormat_wifi" msgid="1376356951297043426">"Wi-Fi"</string>
- <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"Wi-Fi Calling"</string>
+ <string name="wfcSpnFormat_wifi_calling_wo_hyphen" msgid="7178561009225028264">"WiFi Calling"</string>
<string name="wfcSpnFormat_vowifi" msgid="8371335230890725606">"VoWifi"</string>
<string name="wifi_calling_off_summary" msgid="5626710010766902560">"Off"</string>
<string name="wfc_mode_wifi_preferred_summary" msgid="1035175836270943089">"Call over Wi-Fi"</string>
@@ -148,7 +148,7 @@
<string name="wfc_mode_wifi_only_summary" msgid="104951993894678665">"Wi-Fi only"</string>
<!-- no translation found for crossSimFormat_spn (9125246077491634262) -->
<skip />
- <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup calling"</string>
+ <string name="crossSimFormat_spn_cross_sim_calling" msgid="5620807020002879057">"<xliff:g id="SPN">%s</xliff:g> Backup Calling"</string>
<string name="cfTemplateNotForwarded" msgid="862202427794270501">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string>
<string name="cfTemplateForwarded" msgid="9132506315842157860">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
<string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> after <xliff:g id="TIME_DELAY">{2}</xliff:g> seconds"</string>
@@ -172,13 +172,13 @@
<string name="httpErrorFile" msgid="3400658466057744084">"Couldn\'t access the file."</string>
<string name="httpErrorFileNotFound" msgid="5191433324871147386">"Couldn\'t find the requested file."</string>
<string name="httpErrorTooManyRequests" msgid="2149677715552037198">"Too many requests are being processed. Try again later."</string>
- <string name="notification_title" msgid="5783748077084481121">"Sign-in error for <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
+ <string name="notification_title" msgid="5783748077084481121">"Signin error for <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
<string name="contentServiceSync" msgid="2341041749565687871">"Sync"</string>
<string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Can\'t sync"</string>
<string name="contentServiceTooManyDeletesNotificationDesc" msgid="4562226280528716090">"Attempted to delete too many <xliff:g id="CONTENT_TYPE">%s</xliff:g>."</string>
<string name="low_memory" product="tablet" msgid="5557552311566179924">"Tablet storage is full. Delete some files to free space."</string>
- <string name="low_memory" product="watch" msgid="3479447988234030194">"Watch storage is full. Delete some files to free up space."</string>
- <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV device storage is full. Delete some files to free up space."</string>
+ <string name="low_memory" product="watch" msgid="3479447988234030194">"Watch storage is full. Delete some files to free space."</string>
+ <string name="low_memory" product="tv" msgid="6663680413790323318">"Android TV device storage is full. Delete some files to free space."</string>
<string name="low_memory" product="default" msgid="2539532364144025569">"Phone storage is full. Delete some files to free space."</string>
<string name="ssl_ca_cert_warning" msgid="7233573909730048571">"{count,plural, =1{Certificate authority installed}other{Certificate authorities installed}}"</string>
<string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"By an unknown third party"</string>
@@ -192,16 +192,16 @@
<string name="network_logging_notification_title" msgid="554983187553845004">"Device is managed"</string>
<string name="network_logging_notification_text" msgid="1327373071132562512">"Your organization manages this device and may monitor network traffic. Tap for details."</string>
<string name="location_changed_notification_title" msgid="3620158742816699316">"Apps can access your location"</string>
- <string name="location_changed_notification_text" msgid="7158423339982706912">"Contact your IT admin to find out more"</string>
- <string name="geofencing_service" msgid="3826902410740315456">"Geofencing service"</string>
+ <string name="location_changed_notification_text" msgid="7158423339982706912">"Contact your IT admin to learn more"</string>
+ <string name="geofencing_service" msgid="3826902410740315456">"Geofencing Service"</string>
<string name="country_detector" msgid="7023275114706088854">"Country Detector"</string>
<string name="location_service" msgid="2439187616018455546">"Location Service"</string>
- <string name="gnss_service" msgid="8907781262179951385">"GNSS service"</string>
+ <string name="gnss_service" msgid="8907781262179951385">"GNSS Service"</string>
<string name="sensor_notification_service" msgid="7474531979178682676">"Sensor Notification Service"</string>
<string name="twilight_service" msgid="8964898045693187224">"Twilight Service"</string>
- <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS time update service"</string>
- <string name="device_policy_manager_service" msgid="5085762851388850332">"Device Policy manager service"</string>
- <string name="music_recognition_manager_service" msgid="7481956037950276359">"Music recognition manager service"</string>
+ <string name="gnss_time_update_service" msgid="9039489496037616095">"GNSS Time Update Service"</string>
+ <string name="device_policy_manager_service" msgid="5085762851388850332">"Device Policy Manager Service"</string>
+ <string name="music_recognition_manager_service" msgid="7481956037950276359">"Music Recognition Manager Service"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"Your device will be erased"</string>
<string name="factory_reset_message" msgid="2657049595153992213">"The admin app can\'t be used. Your device will now be erased.\n\nIf you have questions, contact your organization\'s admin."</string>
<string name="printing_disabled_by" msgid="3517499806528864633">"Printing disabled by <xliff:g id="OWNER_APP">%s</xliff:g>."</string>
@@ -234,9 +234,9 @@
<string name="shutdown_confirm" product="default" msgid="136816458966692315">"Your phone will shut down."</string>
<string name="shutdown_confirm_question" msgid="796151167261608447">"Do you want to shut down?"</string>
<string name="reboot_safemode_title" msgid="5853949122655346734">"Reboot to safe mode"</string>
- <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Do you want to reboot into safe mode? This will disable all third-party applications that you have installed. They will be restored when you reboot again."</string>
+ <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Do you want to reboot into safe mode? This will disable all third party applications you have installed. They will be restored when you reboot again."</string>
<string name="recent_tasks_title" msgid="8183172372995396653">"Recent"</string>
- <string name="no_recent_tasks" msgid="9063946524312275906">"No recent apps"</string>
+ <string name="no_recent_tasks" msgid="9063946524312275906">"No recent apps."</string>
<string name="global_actions" product="tablet" msgid="4412132498517933867">"Tablet options"</string>
<string name="global_actions" product="tv" msgid="3871763739487450369">"Android TV options"</string>
<string name="global_actions" product="default" msgid="6410072189971495460">"Phone options"</string>
@@ -249,9 +249,9 @@
<string name="global_action_logout" msgid="6093581310002476511">"End session"</string>
<string name="global_action_screenshot" msgid="2610053466156478564">"Screenshot"</string>
<string name="bugreport_title" msgid="8549990811777373050">"Bug report"</string>
- <string name="bugreport_message" msgid="5212529146119624326">"This will collect information about your current device state, to send as an email message. It will take a little time from starting the bug report until it is ready to be sent. Please be patient."</string>
+ <string name="bugreport_message" msgid="5212529146119624326">"This will collect information about your current device state, to send as an e-mail message. It will take a little time from starting the bug report until it is ready to be sent; please be patient."</string>
<string name="bugreport_option_interactive_title" msgid="7968287837902871289">"Interactive report"</string>
- <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Use this under most circumstances. It allows you to track progress of the report, enter more details about the problem and take screenshots. It might omit some less-used sections that take a long time to report."</string>
+ <string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Use this under most circumstances. It allows you to track progress of the report, enter more details about the problem, and take screenshots. It might omit some less-used sections that take a long time to report."</string>
<string name="bugreport_option_full_title" msgid="7681035745950045690">"Full report"</string>
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"Use this option for minimal system interference when your device is unresponsive or too slow, or when you need all report sections. Does not allow you to enter more details or take additional screenshots."</string>
<string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{Taking screenshot for bug report in # second.}other{Taking screenshot for bug report in # seconds.}}"</string>
@@ -320,7 +320,7 @@
<string name="permgrouplab_nearby_devices" msgid="5529147543651181991">"Nearby devices"</string>
<string name="permgroupdesc_nearby_devices" msgid="3213561597116913508">"discover and connect to nearby devices"</string>
<string name="permgrouplab_calllog" msgid="7926834372073550288">"Call logs"</string>
- <string name="permgroupdesc_calllog" msgid="2026996642917801803">"read and write phone call logs"</string>
+ <string name="permgroupdesc_calllog" msgid="2026996642917801803">"read and write phone call log"</string>
<string name="permgrouplab_phone" msgid="570318944091926620">"Phone"</string>
<string name="permgroupdesc_phone" msgid="270048070781478204">"make and manage phone calls"</string>
<string name="permgrouplab_sensors" msgid="9134046949784064495">"Body sensors"</string>
@@ -336,7 +336,7 @@
<string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Control display magnification"</string>
<string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Control the display\'s zoom level and positioning."</string>
<string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Perform gestures"</string>
- <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Can tap, swipe, pinch and perform other gestures."</string>
+ <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Can tap, swipe, pinch, and perform other gestures."</string>
<string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Fingerprint gestures"</string>
<string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Can capture gestures performed on the device\'s fingerprint sensor."</string>
<string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Take screenshot"</string>
@@ -349,24 +349,24 @@
<string name="permdesc_expandStatusBar" msgid="7180756900448498536">"Allows the app to expand or collapse the status bar."</string>
<string name="permlab_fullScreenIntent" msgid="4310888199502509104">"display notifications as full screen activities on a locked device"</string>
<string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Allows the app to display notifications as full screen activities on a locked device"</string>
- <string name="permlab_install_shortcut" msgid="7451554307502256221">"Install shortcuts"</string>
- <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Allows an application to add Home screen shortcuts without user intervention."</string>
+ <string name="permlab_install_shortcut" msgid="7451554307502256221">"install shortcuts"</string>
+ <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Allows an application to add Homescreen shortcuts without user intervention."</string>
<string name="permlab_uninstall_shortcut" msgid="295263654781900390">"uninstall shortcuts"</string>
- <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Allows the application to remove Home screen shortcuts without user intervention."</string>
+ <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Allows the application to remove Homescreen shortcuts without user intervention."</string>
<string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"reroute outgoing calls"</string>
- <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Allows the app to see the number being dialled during an outgoing call with the option to redirect the call to a different number or abort the call altogether."</string>
+ <string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"Allows the app to see the number being dialed during an outgoing call with the option to redirect the call to a different number or abort the call altogether."</string>
<string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"answer phone calls"</string>
<string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"Allows the app to answer an incoming phone call."</string>
<string name="permlab_receiveSms" msgid="505961632050451881">"receive text messages (SMS)"</string>
- <string name="permdesc_receiveSms" msgid="1797345626687832285">"Allows the app to receive and process SMS messages. This means that the app could monitor or delete messages sent to your device without showing them to you."</string>
+ <string name="permdesc_receiveSms" msgid="1797345626687832285">"Allows the app to receive and process SMS messages. This means the app could monitor or delete messages sent to your device without showing them to you."</string>
<string name="permlab_receiveMms" msgid="4000650116674380275">"receive text messages (MMS)"</string>
- <string name="permdesc_receiveMms" msgid="958102423732219710">"Allows the app to receive and process MMS messages. This means that the app could monitor or delete messages sent to your device without showing them to you."</string>
+ <string name="permdesc_receiveMms" msgid="958102423732219710">"Allows the app to receive and process MMS messages. This means the app could monitor or delete messages sent to your device without showing them to you."</string>
<string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"Forward cell broadcast messages"</string>
<string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"Allows the app to bind to the cell broadcast module in order to forward cell broadcast messages as they are received. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received."</string>
<string name="permlab_manageOngoingCalls" msgid="281244770664231782">"Manage ongoing calls"</string>
<string name="permdesc_manageOngoingCalls" msgid="7003138133829915265">"Allows an app to see details about ongoing calls on your device and to control these calls."</string>
- <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"read mobile broadcast messages"</string>
- <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Allows the app to read mobile broadcast messages received by your device. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency mobile broadcast is received."</string>
+ <string name="permlab_readCellBroadcasts" msgid="5869884450872137693">"read cell broadcast messages"</string>
+ <string name="permdesc_readCellBroadcasts" msgid="672513437331980168">"Allows the app to read cell broadcast messages received by your device. Cell broadcast alerts are delivered in some locations to warn you of emergency situations. Malicious apps may interfere with the performance or operation of your device when an emergency cell broadcast is received."</string>
<string name="permlab_subscribedFeedsRead" msgid="217624769238425461">"read subscribed feeds"</string>
<string name="permdesc_subscribedFeedsRead" msgid="6911349196661811865">"Allows the app to get details about the currently synced feeds."</string>
<string name="permlab_sendSms" msgid="7757368721742014252">"send and view SMS messages"</string>
@@ -381,7 +381,7 @@
<string name="permdesc_getTasks" msgid="7388138607018233726">"Allows the app to retrieve information about currently and recently running tasks. This may allow the app to discover information about which applications are used on the device."</string>
<string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"manage profile and device owners"</string>
<string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"Allows apps to set the profile owners and the device owner."</string>
- <string name="permlab_reorderTasks" msgid="7598562301992923804">"re-order running apps"</string>
+ <string name="permlab_reorderTasks" msgid="7598562301992923804">"reorder running apps"</string>
<string name="permdesc_reorderTasks" msgid="8796089937352344183">"Allows the app to move tasks to the foreground and background. The app may do this without your input."</string>
<string name="permlab_enableCarMode" msgid="893019409519325311">"enable car mode"</string>
<string name="permdesc_enableCarMode" msgid="56419168820473508">"Allows the app to enable the car mode."</string>
@@ -394,13 +394,13 @@
<string name="permlab_useDataInBackground" msgid="783415807623038947">"use data in the background"</string>
<string name="permdesc_useDataInBackground" msgid="1230753883865891987">"This app can use data in the background. This may increase data usage."</string>
<string name="permlab_persistentActivity" msgid="464970041740567970">"make app always run"</string>
- <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the tablet."</string>
- <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps slowing down your Android TV device."</string>
- <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit the memory available to other apps, slowing down the phone."</string>
+ <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the tablet."</string>
+ <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down your Android TV device."</string>
+ <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Allows the app to make parts of itself persistent in memory. This can limit memory available to other apps slowing down the phone."</string>
<string name="permlab_foregroundService" msgid="1768855976818467491">"run foreground service"</string>
<string name="permdesc_foregroundService" msgid="8720071450020922795">"Allows the app to make use of foreground services."</string>
<string name="permlab_getPackageSize" msgid="375391550792886641">"measure app storage space"</string>
- <string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data and cache sizes"</string>
+ <string name="permdesc_getPackageSize" msgid="742743530909966782">"Allows the app to retrieve its code, data, and cache sizes"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"modify system settings"</string>
<string name="permdesc_writeSettings" msgid="8293047411196067188">"Allows the app to modify the system\'s settings data. Malicious apps may corrupt your system\'s configuration."</string>
<string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"run at startup"</string>
@@ -412,9 +412,9 @@
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make your Android TV device slow or unstable by causing it to use too much memory."</string>
<string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Allows the app to send sticky broadcasts, which remain after the broadcast ends. Excessive use may make the phone slow or unstable by causing it to use too much memory."</string>
<string name="permlab_readContacts" msgid="8776395111787429099">"read your contacts"</string>
- <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Allows the app to read data about your contacts stored on your tablet. Apps will also have access to the accounts on your tablet that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Allows the app to read data about your contacts stored on your Android TV device. Apps will also have access to the accounts on your Android TV device that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
- <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Allows the app to read data about your contacts stored on your phone. Apps will also have access to the accounts on your phone that have created contacts. This may include any accounts created by apps that you have installed. This permission allows any apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Allows the app to read data about your contacts stored on your tablet. Apps will also have access to the accounts on your tablet that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Allows the app to read data about your contacts stored on your Android TV device. Apps will also have access to the accounts on your Android TV device that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
+ <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Allows the app to read data about your contacts stored on your phone. Apps will also have access to the accounts on your phone that have created contacts. This may include accounts created by apps you have installed. This permission allows apps to save your contact data, and malicious apps may share contact data without your knowledge."</string>
<string name="permlab_writeContacts" msgid="8919430536404830430">"modify your contacts"</string>
<string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Allows the app to modify the data about your contacts stored on your tablet. This permission allows apps to delete contact data."</string>
<string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Allows the app to modify the data about your contacts stored on your Android TV device. This permission allows apps to delete contact data."</string>
@@ -423,26 +423,26 @@
<string name="permdesc_readCallLog" msgid="8964770895425873433">"This app can read your call history."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"write call log"</string>
<string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"Allows the app to modify your tablet\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to erase or modify your call log."</string>
- <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Allows the app to modify your Android TV device\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to delete or modify your call log."</string>
+ <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"Allows the app to modify your Android TV device\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to erase or modify your call log."</string>
<string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"Allows the app to modify your phone\'s call log, including data about incoming and outgoing calls. Malicious apps may use this to erase or modify your call log."</string>
<string name="permlab_bodySensors" msgid="662918578601619569">"Access body sensor data, like heart rate, while in use"</string>
- <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Allows the app to access body sensor data, such as heart rate, temperature and blood oxygen percentage, while the app is in use."</string>
+ <string name="permdesc_bodySensors" product="default" msgid="7652650410295512140">"Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in use."</string>
<string name="permlab_bodySensors_background" msgid="4912560779957760446">"Access body sensor data, like heart rate, while in the background"</string>
- <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Allows the app to access body sensor data, such as heart rate, temperature and blood oxygen percentage, while the app is in the background."</string>
+ <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background."</string>
<string name="permlab_readCalendar" msgid="6408654259475396200">"Read calendar events and details"</string>
<string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"This app can read all calendar events stored on your tablet and share or save your calendar data."</string>
<string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"This app can read all calendar events stored on your Android TV device and share or save your calendar data."</string>
<string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"This app can read all calendar events stored on your phone and share or save your calendar data."</string>
- <string name="permlab_writeCalendar" msgid="6422137308329578076">"add or modify calendar events and send emails to guests without owners\' knowledge"</string>
- <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"This app can add, remove or change calendar events on your tablet. This app can send messages that may appear to come from calendar owners or change events without notifying their owners."</string>
- <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"This app can add, remove or change calendar events on your Android TV device. This app can send messages that may appear to come from calendar owners or change events without notifying their owners."</string>
- <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"This app can add, remove or change calendar events on your phone. This app can send messages that may appear to come from calendar owners or change events without notifying their owners."</string>
+ <string name="permlab_writeCalendar" msgid="6422137308329578076">"add or modify calendar events and send email to guests without owners\' knowledge"</string>
+ <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"This app can add, remove, or change calendar events on your tablet. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners."</string>
+ <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"This app can add, remove, or change calendar events on your Android TV device. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners."</string>
+ <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"This app can add, remove, or change calendar events on your phone. This app can send messages that may appear to come from calendar owners, or change events without notifying their owners."</string>
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"access extra location provider commands"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"Allows the app to access extra location provider commands. This may allow the app to interfere with the operation of the GPS or other location sources."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"access precise location only in the foreground"</string>
- <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"This app can get your precise location from Location Services while the app is in use. Location Services for your device must be turned on for the app to get location. This may increase battery usage."</string>
+ <string name="permdesc_accessFineLocation" msgid="6732174080240016335">"This app can get your precise location from location services while the app is in use. Location services for your device must be turned on for the app to get location. This may increase battery usage."</string>
<string name="permlab_accessCoarseLocation" msgid="1561042925407799741">"access approximate location only in the foreground"</string>
- <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"This app can get your approximate location from Location Services while the app is in use. Location Services for your device must be turned on for the app to get location."</string>
+ <string name="permdesc_accessCoarseLocation" msgid="778521847873199160">"This app can get your approximate location from location services while the app is in use. Location services for your device must be turned on for the app to get location."</string>
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"access location in the background"</string>
<string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"This app can access location at any time, even while the app is not in use."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"change your audio settings"</string>
@@ -453,13 +453,13 @@
<string name="permdesc_recordBackgroundAudio" msgid="1992623135737407516">"This app can record audio using the microphone at any time."</string>
<string name="permlab_sim_communication" msgid="176788115994050692">"send commands to the SIM"</string>
<string name="permdesc_sim_communication" msgid="4179799296415957960">"Allows the app to send commands to the SIM. This is very dangerous."</string>
- <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognise physical activity"</string>
- <string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognise your physical activity."</string>
+ <string name="permlab_activityRecognition" msgid="1782303296053990884">"recognize physical activity"</string>
+ <string name="permdesc_activityRecognition" msgid="8667484762991357519">"This app can recognize your physical activity."</string>
<string name="permlab_camera" msgid="6320282492904119413">"take pictures and videos"</string>
<string name="permdesc_camera" msgid="5240801376168647151">"This app can take pictures and record videos using the camera while the app is in use."</string>
<string name="permlab_backgroundCamera" msgid="7549917926079731681">"take pictures and videos in the background"</string>
<string name="permdesc_backgroundCamera" msgid="1615291686191138250">"This app can take pictures and record videos using the camera at any time."</string>
- <string name="permlab_systemCamera" msgid="3642917457796210580">"Grant an application or service access to system cameras to take pictures and videos"</string>
+ <string name="permlab_systemCamera" msgid="3642917457796210580">"Allow an application or service access to system cameras to take pictures and videos"</string>
<string name="permdesc_systemCamera" msgid="5938360914419175986">"This privileged or system app can take pictures and record videos using a system camera at any time. Requires the android.permission.CAMERA permission to be held by the app as well"</string>
<string name="permlab_cameraOpenCloseListener" msgid="5548732769068109315">"Allow an application or service to receive callbacks about camera devices being opened or closed."</string>
<string name="permdesc_cameraOpenCloseListener" msgid="2002636131008772908">"This app can receive callbacks when any camera device is being opened (by what application) or closed."</string>
@@ -471,14 +471,14 @@
<string name="permlab_accessImsCallService" msgid="442192920714863782">"access IMS call service"</string>
<string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Allows the app to use the IMS service to make calls without your intervention."</string>
<string name="permlab_readPhoneState" msgid="8138526903259297969">"read phone status and identity"</string>
- <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Allows the app to access the phone features of the device. This permission allows the app to determine the phone number and device IDs, whether a call is active and the remote number connected by a call."</string>
+ <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Allows the app to access the phone features of the device. This permission allows the app to determine the phone number and device IDs, whether a call is active, and the remote number connected by a call."</string>
<string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"read basic telephony status and identity"</string>
<string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Allows the app to access the basic telephony features of the device."</string>
<string name="permlab_manageOwnCalls" msgid="9033349060307561370">"route calls through the system"</string>
<string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Allows the app to route its calls through the system in order to improve the calling experience."</string>
<string name="permlab_callCompanionApp" msgid="3654373653014126884">"see and control calls through the system."</string>
<string name="permdesc_callCompanionApp" msgid="8474168926184156261">"Allows the app to see and control ongoing calls on the device. This includes information such as call numbers for calls and the state of the calls."</string>
- <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"exempt from audio recording restrictions"</string>
+ <string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"exempt from audio record restrictions"</string>
<string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"Exempt the app from restrictions to record audio."</string>
<string name="permlab_acceptHandover" msgid="2925523073573116523">"continue a call from another app"</string>
<string name="permdesc_acceptHandovers" msgid="7129026180128626870">"Allows the app to continue a call which was started in another app."</string>
@@ -505,64 +505,64 @@
<string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"Allows the app to change your Android TV device\'s time zone."</string>
<string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"Allows the app to change the phone\'s time zone."</string>
<string name="permlab_getAccounts" msgid="5304317160463582791">"find accounts on the device"</string>
- <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Allows the app to get the list of accounts known by the tablet. This may include any accounts created by applications that you have installed."</string>
- <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Allows the app to get the list of accounts known by your Android TV device. This may include any accounts created by applications that you have installed."</string>
- <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Allows the app to get the list of accounts known by the phone. This may include any accounts created by applications that you have installed."</string>
+ <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"Allows the app to get the list of accounts known by the tablet. This may include any accounts created by applications you have installed."</string>
+ <string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"Allows the app to get the list of accounts known by your Android TV device. This may include any accounts created by applications you have installed."</string>
+ <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"Allows the app to get the list of accounts known by the phone. This may include any accounts created by applications you have installed."</string>
<string name="permlab_accessNetworkState" msgid="2349126720783633918">"view network connections"</string>
<string name="permdesc_accessNetworkState" msgid="4394564702881662849">"Allows the app to view information about network connections such as which networks exist and are connected."</string>
<string name="permlab_createNetworkSockets" msgid="3224420491603590541">"have full network access"</string>
- <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Allows the app to create network sockets and use customised network protocols. The browser and other applications provide means to send data to the Internet, so this permission is not required to send data to the Internet."</string>
+ <string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"Allows the app to create network sockets and use custom network protocols. The browser and other applications provide means to send data to the internet, so this permission is not required to send data to the internet."</string>
<string name="permlab_changeNetworkState" msgid="8945711637530425586">"change network connectivity"</string>
<string name="permdesc_changeNetworkState" msgid="649341947816898736">"Allows the app to change the state of network connectivity."</string>
<string name="permlab_changeTetherState" msgid="9079611809931863861">"change tethered connectivity"</string>
<string name="permdesc_changeTetherState" msgid="3025129606422533085">"Allows the app to change the state of tethered network connectivity."</string>
<string name="permlab_accessWifiState" msgid="5552488500317911052">"view Wi-Fi connections"</string>
<string name="permdesc_accessWifiState" msgid="6913641669259483363">"Allows the app to view information about Wi-Fi networking, such as whether Wi-Fi is enabled and name of connected Wi-Fi devices."</string>
- <string name="permlab_changeWifiState" msgid="7947824109713181554">"Connect and disconnect from Wi-Fi"</string>
+ <string name="permlab_changeWifiState" msgid="7947824109713181554">"connect and disconnect from Wi-Fi"</string>
<string name="permdesc_changeWifiState" msgid="7170350070554505384">"Allows the app to connect to and disconnect from Wi-Fi access points and to make changes to device configuration for Wi-Fi networks."</string>
<string name="permlab_changeWifiMulticastState" msgid="285626875870754696">"allow Wi-Fi Multicast reception"</string>
<string name="permdesc_changeWifiMulticastState" product="tablet" msgid="191079868596433554">"Allows the app to receive packets sent to all devices on a Wi-Fi network using multicast addresses, not just your tablet. It uses more power than the non-multicast mode."</string>
<string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"Allows the app to receive packets sent to all devices on a Wi-Fi network using multicast addresses, not just your Android TV device. It uses more power than the non-multicast mode."</string>
<string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"Allows the app to receive packets sent to all devices on a Wi-Fi network using multicast addresses, not just your phone. It uses more power than the non-multicast mode."</string>
- <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"Access Bluetooth settings"</string>
- <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Allows the app to configure the local Bluetooth tablet and to discover and pair with remote devices."</string>
- <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Allows the app to configure Bluetooth on your Android TV device and to discover and pair with remote devices."</string>
- <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Allows the app to configure the local Bluetooth phone and to discover and pair with remote devices."</string>
+ <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"access Bluetooth settings"</string>
+ <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"Allows the app to configure the local Bluetooth tablet, and to discover and pair with remote devices."</string>
+ <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"Allows the app to configure Bluetooth on your Android TV device, and to discover and pair with remote devices."</string>
+ <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"Allows the app to configure the local Bluetooth phone, and to discover and pair with remote devices."</string>
<string name="permlab_accessWimaxState" msgid="7029563339012437434">"connect and disconnect from WiMAX"</string>
<string name="permdesc_accessWimaxState" msgid="5372734776802067708">"Allows the app to determine whether WiMAX is enabled and information about any WiMAX networks that are connected."</string>
<string name="permlab_changeWimaxState" msgid="6223305780806267462">"change WiMAX state"</string>
<string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Allows the app to connect the tablet to and disconnect the tablet from WiMAX networks."</string>
<string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Allows the app to connect your Android TV device to and disconnect your Android TV device from WiMAX networks."</string>
<string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Allows the app to connect the phone to and disconnect the phone from WiMAX networks."</string>
- <string name="permlab_bluetooth" msgid="586333280736937209">"Pair with Bluetooth devices"</string>
- <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Allows the app to view the configuration of Bluetooth on the tablet and to make and accept connections with paired devices."</string>
- <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Allows the app to view the configuration of Bluetooth on your Android TV device and to make and accept connections with paired devices."</string>
- <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Allows the app to view the configuration of the Bluetooth on the phone and to make and accept connections with paired devices."</string>
+ <string name="permlab_bluetooth" msgid="586333280736937209">"pair with Bluetooth devices"</string>
+ <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Allows the app to view the configuration of Bluetooth on the tablet, and to make and accept connections with paired devices."</string>
+ <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Allows the app to view the configuration of Bluetooth on your Android TV device, and to make and accept connections with paired devices."</string>
+ <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Allows the app to view the configuration of the Bluetooth on the phone, and to make and accept connections with paired devices."</string>
<string name="permlab_bluetooth_scan" msgid="5402587142833124594">"discover and pair nearby Bluetooth devices"</string>
<string name="permdesc_bluetooth_scan" product="default" msgid="6540723536925289276">"Allows the app to discover and pair nearby Bluetooth devices"</string>
<string name="permlab_bluetooth_connect" msgid="6657463246355003528">"connect to paired Bluetooth devices"</string>
<string name="permdesc_bluetooth_connect" product="default" msgid="4546016548795544617">"Allows the app to connect to paired Bluetooth devices"</string>
<string name="permlab_bluetooth_advertise" msgid="2781147747928853177">"advertise to nearby Bluetooth devices"</string>
<string name="permdesc_bluetooth_advertise" product="default" msgid="6085174451034210183">"Allows the app to advertise to nearby Bluetooth devices"</string>
- <string name="permlab_uwb_ranging" msgid="8141915781475770665">"determine relative position between nearby ultra-wideband devices"</string>
- <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Allow the app to determine relative position between nearby ultra-wideband devices"</string>
+ <string name="permlab_uwb_ranging" msgid="8141915781475770665">"determine relative position between nearby Ultra-Wideband devices"</string>
+ <string name="permdesc_uwb_ranging" msgid="2519723069604307055">"Allow the app to determine relative position between nearby Ultra-Wideband devices"</string>
<string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"interact with nearby Wi‑Fi devices"</string>
- <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Allows the app to advertise, connect and determine the relative position of nearby Wi‑Fi devices"</string>
- <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Preferred NFC payment service information"</string>
- <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Allows the app to get preferred NFC payment service information, such as registered aids and route destination."</string>
- <string name="permlab_nfc" msgid="1904455246837674977">"control Near-Field Communication"</string>
- <string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards and readers."</string>
+ <string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"Allows the app to advertise, connect, and determine the relative position of nearby Wi‑Fi devices"</string>
+ <string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"Preferred NFC Payment Service Information"</string>
+ <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"Allows the app to get preferred nfc payment service information like registered aids and route destination."</string>
+ <string name="permlab_nfc" msgid="1904455246837674977">"control Near Field Communication"</string>
+ <string name="permdesc_nfc" msgid="8352737680695296741">"Allows the app to communicate with Near Field Communication (NFC) tags, cards, and readers."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"disable your screen lock"</string>
<string name="permdesc_disableKeyguard" msgid="3223710003098573038">"Allows the app to disable the keylock and any associated password security. For example, the phone disables the keylock when receiving an incoming phone call, then re-enables the keylock when the call is finished."</string>
<string name="permlab_requestPasswordComplexity" msgid="1808977190557794109">"request screen lock complexity"</string>
- <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Allows the app to learn the screen lock complexity level (high, medium, low or none), which indicates the possible range of length and type of the screen lock. The app can also suggest to users that they update the screen lock to a certain level but users can freely ignore and navigate away. Note that the screen lock is not stored in plain text so the app does not know the exact password."</string>
+ <string name="permdesc_requestPasswordComplexity" msgid="1130556896836258567">"Allows the app to learn the screen lock complexity level (high, medium, low or none), which indicates the possible range of length and type of the screen lock. The app can also suggest to users that they update the screen lock to a certain level but users can freely ignore and navigate away. Note that the screen lock is not stored in plaintext so the app does not know the exact password."</string>
<string name="permlab_postNotification" msgid="4875401198597803658">"show notifications"</string>
<string name="permdesc_postNotification" msgid="5974977162462877075">"Allows the app to show notifications"</string>
<string name="permlab_useBiometric" msgid="6314741124749633786">"use biometric hardware"</string>
<string name="permdesc_useBiometric" msgid="7502858732677143410">"Allows the app to use biometric hardware for authentication"</string>
<string name="permlab_manageFingerprint" msgid="7432667156322821178">"manage fingerprint hardware"</string>
<string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Allows the app to invoke methods to add and delete fingerprint templates for use."</string>
- <string name="permlab_useFingerprint" msgid="1001421069766751922">"Use fingerprint hardware"</string>
+ <string name="permlab_useFingerprint" msgid="1001421069766751922">"use fingerprint hardware"</string>
<string name="permdesc_useFingerprint" msgid="412463055059323742">"Allows the app to use fingerprint hardware for authentication"</string>
<string name="permlab_audioWrite" msgid="8501705294265669405">"modify your music collection"</string>
<string name="permdesc_audioWrite" msgid="8057399517013412431">"Allows the app to modify your music collection."</string>
@@ -574,19 +574,19 @@
<string name="permdesc_mediaLocation" msgid="597912899423578138">"Allows the app to read locations from your media collection."</string>
<string name="biometric_app_setting_name" msgid="3339209978734534457">"Use biometrics"</string>
<string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Use biometrics or screen lock"</string>
- <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verify that it’s you"</string>
+ <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verify it’s you"</string>
<string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Use your biometric to continue"</string>
<string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Use your biometric or screen lock to continue"</string>
<string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometric hardware unavailable"</string>
- <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication cancelled"</string>
- <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognised"</string>
- <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication cancelled"</string>
- <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern or password set"</string>
- <string name="biometric_error_generic" msgid="6784371929985434439">"Error while authenticating"</string>
+ <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Authentication canceled"</string>
+ <string name="biometric_not_recognized" msgid="5106687642694635888">"Not recognized"</string>
+ <string name="biometric_error_canceled" msgid="8266582404844179778">"Authentication canceled"</string>
+ <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"No pin, pattern, or password set"</string>
+ <string name="biometric_error_generic" msgid="6784371929985434439">"Error authenticating"</string>
<string name="screen_lock_app_setting_name" msgid="6054944352976789228">"Use screen lock"</string>
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Enter your screen lock to continue"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Press firmly on the sensor"</string>
- <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognise fingerprint. Try again."</string>
+ <string name="fingerprint_acquired_insufficient" msgid="623888149088216458">"Can’t recognize fingerprint. Try again."</string>
<string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Clean fingerprint sensor and try again"</string>
<string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Clean sensor and try again"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Press firmly on the sensor"</string>
@@ -598,16 +598,16 @@
<string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"Change the position of your finger slightly each time"</string>
<string-array name="fingerprint_acquired_vendor">
</string-array>
- <string name="fingerprint_error_not_match" msgid="4599441812893438961">"Fingerprint not recognised"</string>
- <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"Fingerprint not recognised"</string>
+ <string name="fingerprint_error_not_match" msgid="4599441812893438961">"Fingerprint not recognized"</string>
+ <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"Fingerprint not recognized"</string>
<string name="fingerprint_authenticated" msgid="2024862866860283100">"Fingerprint authenticated"</string>
<string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"Face authenticated"</string>
- <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Face authenticated. Please press confirm"</string>
+ <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Face authenticated, please press confirm"</string>
<string name="fingerprint_error_hw_not_available" msgid="4571700896929561202">"Fingerprint hardware not available."</string>
<string name="fingerprint_error_no_space" msgid="7285481581905967580">"Can’t set up fingerprint"</string>
<string name="fingerprint_error_timeout" msgid="7361192266621252164">"Fingerprint setup timed out. Try again."</string>
- <string name="fingerprint_error_canceled" msgid="540026881380070750">"Fingerprint operation cancelled."</string>
- <string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Fingerprint operation cancelled by user."</string>
+ <string name="fingerprint_error_canceled" msgid="540026881380070750">"Fingerprint operation canceled."</string>
+ <string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Fingerprint operation canceled by user."</string>
<string name="fingerprint_error_lockout" msgid="6626753679019351368">"Too many attempts. Use screen lock instead."</string>
<string name="fingerprint_error_lockout_permanent" msgid="9060651300306264843">"Too many attempts. Use screen lock instead."</string>
<string name="fingerprint_error_unable_to_process" msgid="2446280592818621224">"Can’t process fingerprint. Try again."</string>
@@ -639,7 +639,7 @@
<string name="face_acquired_insufficient" msgid="6889245852748492218">"Can’t create your face model. Try again."</string>
<string name="face_acquired_too_bright" msgid="8070756048978079164">"Too bright. Try gentler lighting."</string>
<string name="face_acquired_too_dark" msgid="8539853432479385326">"Not enough light"</string>
- <string name="face_acquired_too_close" msgid="4453646176196302462">"Move phone further away"</string>
+ <string name="face_acquired_too_close" msgid="4453646176196302462">"Move phone farther away"</string>
<string name="face_acquired_too_far" msgid="2922278214231064859">"Move phone closer"</string>
<string name="face_acquired_too_high" msgid="8278815780046368576">"Move phone higher"</string>
<string name="face_acquired_too_low" msgid="4075391872960840081">"Move phone lower"</string>
@@ -649,7 +649,7 @@
<string name="face_acquired_not_detected" msgid="1057966913397548150">"Can’t see your face. Hold your phone at eye level."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"Too much motion. Hold phone steady."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Please re-enroll your face."</string>
- <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognise face. Try again."</string>
+ <string name="face_acquired_too_different" msgid="2520389515612972889">"Can’t recognize face. Try again."</string>
<string name="face_acquired_too_similar" msgid="8882920552674125694">"Change the position of your head slightly"</string>
<string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"Look more directly at your phone"</string>
<string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"Look more directly at your phone"</string>
@@ -668,8 +668,8 @@
<string name="face_error_hw_not_available" msgid="5085202213036026288">"Can’t verify face. Hardware not available."</string>
<string name="face_error_timeout" msgid="2598544068593889762">"Try Face Unlock again"</string>
<string name="face_error_no_space" msgid="5649264057026021723">"Can’t store new face data. Delete an old one first."</string>
- <string name="face_error_canceled" msgid="2164434737103802131">"Face operation cancelled."</string>
- <string name="face_error_user_canceled" msgid="5766472033202928373">"Face Unlock cancelled by user"</string>
+ <string name="face_error_canceled" msgid="2164434737103802131">"Face operation canceled."</string>
+ <string name="face_error_user_canceled" msgid="5766472033202928373">"Face Unlock canceled by user"</string>
<string name="face_error_lockout" msgid="7864408714994529437">"Too many attempts. Try again later."</string>
<string name="face_error_lockout_permanent" msgid="3277134834042995260">"Too many attempts. Face Unlock disabled."</string>
<string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"Too many attempts. Enter screen lock instead."</string>
@@ -689,7 +689,7 @@
<string name="permlab_readSyncSettings" msgid="6250532864893156277">"read sync settings"</string>
<string name="permdesc_readSyncSettings" msgid="1325658466358779298">"Allows the app to read the sync settings for an account. For example, this can determine whether the People app is synced with an account."</string>
<string name="permlab_writeSyncSettings" msgid="6583154300780427399">"toggle sync on and off"</string>
- <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Allows an app to modify the sync settings for an account. For example, this can be used to enable syncing of the People app with an account."</string>
+ <string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"Allows an app to modify the sync settings for an account. For example, this can be used to enable sync of the People app with an account."</string>
<string name="permlab_readSyncStats" msgid="3747407238320105332">"read sync statistics"</string>
<string name="permdesc_readSyncStats" msgid="3867809926567379434">"Allows an app to read the sync stats for an account, including the history of sync events and how much data is synced."</string>
<string name="permlab_sdcardRead" msgid="5791467020950064920">"read the contents of your shared storage"</string>
@@ -759,12 +759,12 @@
<string name="policylab_limitPassword" msgid="4851829918814422199">"Set password rules"</string>
<string name="policydesc_limitPassword" msgid="4105491021115793793">"Control the length and the characters allowed in screen lock passwords and PINs."</string>
<string name="policylab_watchLogin" msgid="7599669460083719504">"Monitor screen unlock attempts"</string>
- <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Monitor the number of incorrect passwords typed when unlocking the screen and lock the tablet or erase all the tablet\'s data if too many incorrect passwords are typed."</string>
- <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or delete all of your Android TV device\'s data if too many incorrect passwords are typed."</string>
- <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the infotainment system or erase all the infotainment system\'s data if too many incorrect passwords are typed."</string>
- <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Monitor the number of incorrect passwords typed when unlocking the screen and lock the phone or erase all the phone\'s data if too many incorrect passwords are typed."</string>
+ <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the tablet or erase all the tablet\'s data if too many incorrect passwords are typed."</string>
+ <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or erase all your Android TV device\'s data if too many incorrect passwords are typed."</string>
+ <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Monitor the number of incorrect passwords typed. when unlocking the screen, and lock the infotainment system or erase all the infotainment system\'s data if too many incorrect passwords are typed."</string>
+ <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Monitor the number of incorrect passwords typed. when unlocking the screen, and lock the phone or erase all the phone\'s data if too many incorrect passwords are typed."</string>
<string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the tablet or erase all this user\'s data if too many incorrect passwords are typed."</string>
- <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or delete all of this user\'s data if too many incorrect passwords are typed."</string>
+ <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock your Android TV device or erase all this user\'s data if too many incorrect passwords are typed."</string>
<string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the infotainment system or erase all this profile\'s data if too many incorrect passwords are typed."</string>
<string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Monitor the number of incorrect passwords typed when unlocking the screen, and lock the phone or erase all this user\'s data if too many incorrect passwords are typed."</string>
<string name="policylab_resetPassword" msgid="214556238645096520">"Change the screen lock"</string>
@@ -773,19 +773,19 @@
<string name="policydesc_forceLock" msgid="1008844760853899693">"Control how and when the screen locks."</string>
<string name="policylab_wipeData" msgid="1359485247727537311">"Erase all data"</string>
<string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"Erase the tablet\'s data without warning by performing a factory data reset."</string>
- <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"Delete your Android TV device\'s data without warning by performing a factory data reset."</string>
+ <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"Erase your Android TV device\'s data without warning by performing a factory data reset."</string>
<string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Erase the infotainment system\'s data without warning by performing a factory data reset."</string>
<string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Erase the phone\'s data without warning by performing a factory data reset."</string>
<string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Erase profile data"</string>
<string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Erase user data"</string>
<string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Erase this user\'s data on this tablet without warning."</string>
- <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Delete this user\'s data on this Android TV device without warning."</string>
+ <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Erase this user\'s data on this Android TV device without warning."</string>
<string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Erase this profile\'s data on this infotainment system without warning."</string>
<string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"Erase this user\'s data on this phone without warning."</string>
<string name="policylab_setGlobalProxy" msgid="215332221188670221">"Set the device global proxy"</string>
<string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"Set the device global proxy to be used while policy is enabled. Only the device owner can set the global proxy."</string>
- <string name="policylab_expirePassword" msgid="6015404400532459169">"Set screen lock password expiry"</string>
- <string name="policydesc_expirePassword" msgid="9136524319325960675">"Change how frequently the screen lock password, PIN or pattern must be changed."</string>
+ <string name="policylab_expirePassword" msgid="6015404400532459169">"Set screen lock password expiration"</string>
+ <string name="policydesc_expirePassword" msgid="9136524319325960675">"Change how frequently the screen lock password, PIN, or pattern must be changed."</string>
<string name="policylab_encryptedStorage" msgid="9012936958126670110">"Set storage encryption"</string>
<string name="policydesc_encryptedStorage" msgid="1102516950740375617">"Require that stored app data be encrypted."</string>
<string name="policylab_disableCamera" msgid="5749486347810162018">"Disable cameras"</string>
@@ -796,8 +796,8 @@
<item msgid="8996339953292723951">"Home"</item>
<item msgid="7740243458912727194">"Mobile"</item>
<item msgid="8526146065496663766">"Work"</item>
- <item msgid="8150904584178569699">"Work fax"</item>
- <item msgid="4537253139152229577">"Home fax"</item>
+ <item msgid="8150904584178569699">"Work Fax"</item>
+ <item msgid="4537253139152229577">"Home Fax"</item>
<item msgid="6751245029698664340">"Pager"</item>
<item msgid="1692790665884224905">"Other"</item>
<item msgid="6216981255272016212">"Custom"</item>
@@ -839,8 +839,8 @@
<string name="phoneTypeHome" msgid="3880132427643623588">"Home"</string>
<string name="phoneTypeMobile" msgid="1178852541462086735">"Mobile"</string>
<string name="phoneTypeWork" msgid="6604967163358864607">"Work"</string>
- <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Work fax"</string>
- <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Home fax"</string>
+ <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Work Fax"</string>
+ <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Home Fax"</string>
<string name="phoneTypePager" msgid="576402072263522767">"Pager"</string>
<string name="phoneTypeOther" msgid="6918196243648754715">"Other"</string>
<string name="phoneTypeCallback" msgid="3455781500844157767">"Callback"</string>
@@ -851,7 +851,7 @@
<string name="phoneTypeOtherFax" msgid="3037145630364770357">"Other Fax"</string>
<string name="phoneTypeRadio" msgid="2637819130239264771">"Radio"</string>
<string name="phoneTypeTelex" msgid="2558783611711876562">"Telex"</string>
- <string name="phoneTypeTtyTdd" msgid="532038552105328779">"TTY/TDD"</string>
+ <string name="phoneTypeTtyTdd" msgid="532038552105328779">"TTY TDD"</string>
<string name="phoneTypeWorkMobile" msgid="7522314392003565121">"Work Mobile"</string>
<string name="phoneTypeWorkPager" msgid="3748332310638505234">"Work Pager"</string>
<string name="phoneTypeAssistant" msgid="757550783842231039">"Assistant"</string>
@@ -882,7 +882,7 @@
<string name="imProtocolGoogleTalk" msgid="9194016024343166782">"Hangouts"</string>
<string name="imProtocolIcq" msgid="2410325380427389521">"ICQ"</string>
<string name="imProtocolJabber" msgid="7919269388889582015">"Jabber"</string>
- <string name="imProtocolNetMeeting" msgid="4985002408136148256">"Net Meeting"</string>
+ <string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string>
<string name="orgTypeWork" msgid="8684458700669564172">"Work"</string>
<string name="orgTypeOther" msgid="5450675258408005553">"Other"</string>
<string name="orgTypeCustom" msgid="1126322047677329218">"Custom"</string>
@@ -909,12 +909,12 @@
<string name="keyguard_password_enter_pin_code" msgid="6401406801060956153">"Type PIN code"</string>
<string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"Type PUK and new PIN code"</string>
<string name="keyguard_password_enter_puk_prompt" msgid="2825313071899938305">"PUK code"</string>
- <string name="keyguard_password_enter_pin_prompt" msgid="5505434724229581207">"New PIN Code"</string>
+ <string name="keyguard_password_enter_pin_prompt" msgid="5505434724229581207">"New PIN code"</string>
<string name="keyguard_password_entry_touch_hint" msgid="4032288032993261520"><font size="17">"Tap to type password"</font></string>
<string name="keyguard_password_enter_password_code" msgid="2751130557661643482">"Type password to unlock"</string>
<string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"Type PIN to unlock"</string>
<string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"Incorrect PIN code."</string>
- <string name="keyguard_label_text" msgid="3841953694564168384">"To unlock, press Menu, then 0."</string>
+ <string name="keyguard_label_text" msgid="3841953694564168384">"To unlock, press Menu then 0."</string>
<string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Emergency number"</string>
<string name="lockscreen_carrier_default" msgid="6192313772955399160">"No service"</string>
<string name="lockscreen_screen_locked" msgid="7364905540516041817">"Screen locked."</string>
@@ -942,7 +942,7 @@
<string name="lockscreen_transport_play_description" msgid="106868788691652733">"Play"</string>
<string name="lockscreen_transport_stop_description" msgid="1449552232598355348">"Stop"</string>
<string name="lockscreen_transport_rew_description" msgid="7680106856221622779">"Rewind"</string>
- <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Fast-forward"</string>
+ <string name="lockscreen_transport_ffw_description" msgid="4763794746640196772">"Fast forward"</string>
<string name="emergency_calls_only" msgid="3057351206678279851">"Emergency calls only"</string>
<string name="lockscreen_network_locked_message" msgid="2814046965899249635">"Network locked"</string>
<string name="lockscreen_sim_puk_locked_message" msgid="6618356415831082174">"SIM card is PUK-locked."</string>
@@ -952,9 +952,9 @@
<string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6458790975898594240">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="3118353451602377380">"You have incorrectly typed your password <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="2874278239714821984">"You have incorrectly typed your PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your tablet using your Google sign-in.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"You have drawn your unlock pattern incorrectly <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using your Google sign-in.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
- <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"You have drawn your unlock pattern incorrectly <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using your Google sign-in.\n\n Please try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tablet" msgid="3069635524964070596">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your tablet using your Google signin.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="tv" msgid="6399092175942158529">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using your Google signin.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+ <string name="lockscreen_failed_attempts_almost_glogin" product="default" msgid="5691623136957148335">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using your Google signin.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tablet" msgid="7914445759242151426">"You have incorrectly attempted to unlock the tablet <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, the tablet will be reset to factory default and all user data will be lost."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="tv" msgid="4275591249631864248">"You have incorrectly attempted to unlock your Android TV device <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, your Android TV device will be reset to factory default and all user data will be lost."</string>
<string name="lockscreen_failed_attempts_almost_at_wipe" product="default" msgid="1166532464798446579">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, the phone will be reset to factory default and all user data will be lost."</string>
@@ -962,15 +962,15 @@
<string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"You have incorrectly attempted to unlock your Android TV device <xliff:g id="NUMBER">%d</xliff:g> times. Your Android TV device will now be reset to factory default."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER">%d</xliff:g> times. The phone will now be reset to factory default."</string>
<string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Try again in <xliff:g id="NUMBER">%d</xliff:g> seconds."</string>
- <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Forgotten pattern?"</string>
+ <string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Forgot pattern?"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Account unlock"</string>
<string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"Too many pattern attempts"</string>
- <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"To unlock, sign in with your Google Account."</string>
+ <string name="lockscreen_glogin_instructions" msgid="4695162942525531700">"To unlock, sign in with your Google account."</string>
<string name="lockscreen_glogin_username_hint" msgid="6916101478673157045">"Username (email)"</string>
<string name="lockscreen_glogin_password_hint" msgid="3031027901286812848">"Password"</string>
<string name="lockscreen_glogin_submit_button" msgid="3590556636347843733">"Sign in"</string>
<string name="lockscreen_glogin_invalid_input" msgid="4369219936865697679">"Invalid username or password."</string>
- <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Forgot your username or password?\nVisit "<b>"google.co.uk/accounts/recovery"</b>"."</string>
+ <string name="lockscreen_glogin_account_recovery_hint" msgid="1683405808525090649">"Forgot your username or password?\nVisit "<b>"google.com/accounts/recovery"</b>"."</string>
<string name="lockscreen_glogin_checking_password" msgid="2607271802803381645">"Checking…"</string>
<string name="lockscreen_unlock_label" msgid="4648257878373307582">"Unlock"</string>
<string name="lockscreen_sound_on_label" msgid="1660281470535492430">"Sound on"</string>
@@ -982,7 +982,7 @@
<string name="lockscreen_access_pattern_detected" msgid="3931150554035194012">"Pattern completed"</string>
<string name="lockscreen_access_pattern_area" msgid="1288780416685002841">"Pattern area."</string>
<string name="keyguard_accessibility_widget_changed" msgid="7298011259508200234">"%1$s. Widget %2$d of %3$d."</string>
- <string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"Add widget"</string>
+ <string name="keyguard_accessibility_add_widget" msgid="8245795023551343672">"Add widget."</string>
<string name="keyguard_accessibility_widget_empty_slot" msgid="544239307077644480">"Empty"</string>
<string name="keyguard_accessibility_unlock_area_expanded" msgid="7768634718706488951">"Unlock area expanded."</string>
<string name="keyguard_accessibility_unlock_area_collapsed" msgid="4729922043778400434">"Unlock area collapsed."</string>
@@ -999,8 +999,8 @@
<string name="keyguard_accessibility_pattern_unlock" msgid="8669128146589233293">"Pattern unlock."</string>
<string name="keyguard_accessibility_face_unlock" msgid="4533832120787386728">"Face Unlock."</string>
<string name="keyguard_accessibility_pin_unlock" msgid="4020864007967340068">"Pin unlock."</string>
- <string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"SIM PIN unlock."</string>
- <string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"SIM PUK unlock."</string>
+ <string name="keyguard_accessibility_sim_pin_unlock" msgid="4895939120871890557">"Sim Pin unlock."</string>
+ <string name="keyguard_accessibility_sim_puk_unlock" msgid="3459003464041899101">"Sim Puk unlock."</string>
<string name="keyguard_accessibility_password_unlock" msgid="6130186108581153265">"Password unlock."</string>
<string name="keyguard_accessibility_pattern_area" msgid="1419570880512350689">"Pattern area."</string>
<string name="keyguard_accessibility_slide_area" msgid="4331399051142520176">"Slide area."</string>
@@ -1022,18 +1022,18 @@
<string name="js_dialog_before_unload_negative_button" msgid="3873765747622415310">"Stay on this Page"</string>
<string name="js_dialog_before_unload" msgid="7213364985774778744">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nAre you sure you want to navigate away from this page?"</string>
<string name="save_password_label" msgid="9161712335355510035">"Confirm"</string>
- <string name="double_tap_toast" msgid="7065519579174882778">"Tip: double-tap to zoom in and out."</string>
- <string name="autofill_this_form" msgid="3187132440451621492">"Auto-fill"</string>
- <string name="setup_autofill" msgid="5431369130866618567">"Set up Auto-fill"</string>
+ <string name="double_tap_toast" msgid="7065519579174882778">"Tip: Double-tap to zoom in and out."</string>
+ <string name="autofill_this_form" msgid="3187132440451621492">"Autofill"</string>
+ <string name="setup_autofill" msgid="5431369130866618567">"Set up Autofill"</string>
<string name="autofill_window_title" msgid="4379134104008111961">"Autofill with <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
<string name="autofill_address_name_separator" msgid="8190155636149596125">" "</string>
<string name="autofill_address_summary_name_format" msgid="3402882515222673691">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="760522655085707045">", "</string>
<string name="autofill_address_summary_format" msgid="8417010069362125194">"$1$2$3"</string>
<string name="autofill_province" msgid="3676846437741893159">"Province"</string>
- <string name="autofill_postal_code" msgid="7034789388968295591">"Postcode"</string>
+ <string name="autofill_postal_code" msgid="7034789388968295591">"Postal code"</string>
<string name="autofill_state" msgid="3341725337190434069">"State"</string>
- <string name="autofill_zip_code" msgid="1315503730274962450">"Zip code"</string>
+ <string name="autofill_zip_code" msgid="1315503730274962450">"ZIP code"</string>
<string name="autofill_county" msgid="7781382735643492173">"County"</string>
<string name="autofill_island" msgid="5367139008536593734">"Island"</string>
<string name="autofill_district" msgid="6428712062213557327">"District"</string>
@@ -1045,15 +1045,15 @@
<string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"read your Web bookmarks and history"</string>
<string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Allows the app to read the history of all URLs that the Browser has visited, and all of the Browser\'s bookmarks. Note: this permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
<string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"write web bookmarks and history"</string>
- <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Allows the app to modify the Browser\'s history or bookmarks stored on your tablet. This may allow the app to delete or modify Browser data. Note: this permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
- <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Allows the app to modify the browser\'s history or bookmarks stored on your Android TV device. This may allow the app to delete or modify browser data. Note: This permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
- <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Allows the app to modify the Browser\'s history or bookmarks stored on your phone. This may allow the app to delete or modify Browser data. Note: this permission may not be enforced by third-party browsers or other applications with web browsing capabilities."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Allows the app to modify the Browser\'s history or bookmarks stored on your tablet. This may allow the app to erase or modify Browser data. Note: this permission may note be enforced by third-party browsers or other applications with web browsing capabilities."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Allows the app to modify the Browser\'s history or bookmarks stored on your Android TV device. This may allow the app to erase or modify Browser data. Note: this permission may note be enforced by third-party browsers or other applications with web browsing capabilities."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Allows the app to modify the Browser\'s history or bookmarks stored on your phone. This may allow the app to erase or modify Browser data. Note: this permission may note be enforced by third-party browsers or other applications with web browsing capabilities."</string>
<string name="permlab_setAlarm" msgid="1158001610254173567">"set an alarm"</string>
<string name="permdesc_setAlarm" msgid="2185033720060109640">"Allows the app to set an alarm in an installed alarm clock app. Some alarm clock apps may not implement this feature."</string>
<string name="permlab_addVoicemail" msgid="4770245808840814471">"add voicemail"</string>
<string name="permdesc_addVoicemail" msgid="5470312139820074324">"Allows the app to add messages to your voicemail inbox."</string>
- <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"Modify Browser geo-location permissions"</string>
- <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Allows the app to modify the Browser\'s geo-location permissions. Malicious apps may use this to allow sending location information to arbitrary websites."</string>
+ <string name="permlab_writeGeolocationPermissions" msgid="8605631647492879449">"modify Browser geolocation permissions"</string>
+ <string name="permdesc_writeGeolocationPermissions" msgid="5817346421222227772">"Allows the app to modify the Browser\'s geolocation permissions. Malicious apps may use this to allow sending location information to arbitrary web sites."</string>
<string name="save_password_message" msgid="2146409467245462965">"Do you want the browser to remember this password?"</string>
<string name="save_password_notnow" msgid="2878327088951240061">"Not now"</string>
<string name="save_password_remember" msgid="6490888932657708341">"Remember"</string>
@@ -1062,9 +1062,9 @@
<string name="text_copied" msgid="2531420577879738860">"Text copied to clipboard."</string>
<string name="pasted_from_app" msgid="5627698450808256545">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted from <xliff:g id="SOURCE_APP_NAME">%2$s</xliff:g>"</string>
<string name="pasted_from_clipboard" msgid="7355790625710831847">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted from your clipboard"</string>
- <string name="pasted_text" msgid="4298871641549173733">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted text that you copied"</string>
- <string name="pasted_image" msgid="4729097394781491022">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted an image that you copied"</string>
- <string name="pasted_content" msgid="646276353060777131">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted content that you copied"</string>
+ <string name="pasted_text" msgid="4298871641549173733">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted text you copied"</string>
+ <string name="pasted_image" msgid="4729097394781491022">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted an image you copied"</string>
+ <string name="pasted_content" msgid="646276353060777131">"<xliff:g id="PASTING_APP_NAME">%1$s</xliff:g> pasted content you copied"</string>
<string name="more_item_label" msgid="7419249600215749115">"More"</string>
<string name="prepend_shortcut_label" msgid="1743716737502867951">"Menu+"</string>
<string name="menu_meta_shortcut_label" msgid="1623390163674762478">"Meta+"</string>
@@ -1093,7 +1093,7 @@
<string name="older" msgid="1645159827884647400">"Older"</string>
<string name="preposition_for_date" msgid="2780767868832729599">"on <xliff:g id="DATE">%s</xliff:g>"</string>
<string name="preposition_for_time" msgid="4336835286453822053">"at <xliff:g id="TIME">%s</xliff:g>"</string>
- <string name="preposition_for_year" msgid="3149809685340130039">"in<xliff:g id="YEAR">%s</xliff:g>"</string>
+ <string name="preposition_for_year" msgid="3149809685340130039">"in <xliff:g id="YEAR">%s</xliff:g>"</string>
<string name="day" msgid="8394717255950176156">"day"</string>
<string name="days" msgid="4570879797423034973">"days"</string>
<string name="hour" msgid="7796325297097314653">"hour"</string>
@@ -1114,7 +1114,7 @@
<string name="duration_minutes_shortest_future" msgid="5260857299282734759">"in <xliff:g id="COUNT">%d</xliff:g>m"</string>
<string name="duration_hours_shortest_future" msgid="2979276794547981674">"in <xliff:g id="COUNT">%d</xliff:g>h"</string>
<string name="duration_days_shortest_future" msgid="3392722163935571543">"in <xliff:g id="COUNT">%d</xliff:g>d"</string>
- <string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g> y"</string>
+ <string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g>y"</string>
<string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{# minute ago}other{# minutes ago}}"</string>
<string name="duration_hours_relative" msgid="4836449961693180253">"{count,plural, =1{# hour ago}other{# hours ago}}"</string>
<string name="duration_days_relative" msgid="621965767567258302">"{count,plural, =1{# day ago}other{# days ago}}"</string>
@@ -1140,13 +1140,13 @@
<string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"Failed to copy to clipboard"</string>
<string name="paste" msgid="461843306215520225">"Paste"</string>
<string name="paste_as_plain_text" msgid="7664800665823182587">"Paste as plain text"</string>
- <string name="replace" msgid="7842675434546657444">"Replace..."</string>
+ <string name="replace" msgid="7842675434546657444">"Replace…"</string>
<string name="delete" msgid="1514113991712129054">"Delete"</string>
<string name="copyUrl" msgid="6229645005987260230">"Copy URL"</string>
<string name="selectTextMode" msgid="3225108910999318778">"Select text"</string>
<string name="undo" msgid="3175318090002654673">"Undo"</string>
<string name="redo" msgid="7231448494008532233">"Redo"</string>
- <string name="autofill" msgid="511224882647795296">"Auto-fill"</string>
+ <string name="autofill" msgid="511224882647795296">"Autofill"</string>
<string name="textSelectionCABTitle" msgid="5151441579532476940">"Text selection"</string>
<string name="addToDictionary" msgid="8041821113480950096">"Add to dictionary"</string>
<string name="deleteText" msgid="4200807474529938112">"Delete"</string>
@@ -1156,7 +1156,7 @@
<string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Switch input method"</string>
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Storage space running out"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Some system functions may not work"</string>
- <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
+ <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Not enough storage for the system. Make sure you have 250MB of free space and restart."</string>
<string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> is running"</string>
<string name="app_running_notification_text" msgid="5120815883400228566">"Tap for more information or to stop the app."</string>
<string name="ok" msgid="2646370155170753815">"OK"</string>
@@ -1171,7 +1171,7 @@
<string name="not_checked" msgid="7972320087569023342">"not checked"</string>
<string name="selected" msgid="6614607926197755875">"selected"</string>
<string name="not_selected" msgid="410652016565864475">"not selected"</string>
- <string name="in_progress" msgid="2149208189184319441">"In progress"</string>
+ <string name="in_progress" msgid="2149208189184319441">"in progress"</string>
<string name="whichApplication" msgid="5432266899591255759">"Complete action using"</string>
<string name="whichApplicationNamed" msgid="6969946041713975681">"Complete action using %1$s"</string>
<string name="whichApplicationLabel" msgid="7852182961472531728">"Complete action"</string>
@@ -1234,8 +1234,8 @@
<string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"<xliff:g id="APP_NAME">%1$s</xliff:g> was built for an incompatible version of the Android OS and may behave unexpectedly. An updated version of the app may be available."</string>
<string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"Always show"</string>
<string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"Check for update"</string>
- <string name="smv_application" msgid="3775183542777792638">"The app <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has violated its self-enforced Strict Mode policy."</string>
- <string name="smv_process" msgid="1398801497130695446">"The process <xliff:g id="PROCESS">%1$s</xliff:g> has violated its self-enforced StrictMode policy."</string>
+ <string name="smv_application" msgid="3775183542777792638">"The app <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has violated its self-enforced StrictMode policy."</string>
+ <string name="smv_process" msgid="1398801497130695446">"The process <xliff:g id="PROCESS">%1$s</xliff:g> has has violated its self-enforced StrictMode policy."</string>
<string name="android_upgrading_title" product="default" msgid="7279077384220829683">"Phone is updating…"</string>
<string name="android_upgrading_title" product="tablet" msgid="4268417249079938805">"Tablet is updating…"</string>
<string name="android_upgrading_title" product="device" msgid="6774767702998149762">"Device is updating…"</string>
@@ -1243,18 +1243,18 @@
<string name="android_start_title" product="automotive" msgid="7917984412828168079">"Android is starting…"</string>
<string name="android_start_title" product="tablet" msgid="4429767260263190344">"Tablet is starting…"</string>
<string name="android_start_title" product="device" msgid="6967413819673299309">"Device is starting…"</string>
- <string name="android_upgrading_fstrim" msgid="3259087575528515329">"Optimising storage."</string>
+ <string name="android_upgrading_fstrim" msgid="3259087575528515329">"Optimizing storage."</string>
<string name="android_upgrading_notification_title" product="default" msgid="3509927005342279257">"Finishing system update…"</string>
<string name="app_upgrading_toast" msgid="1016267296049455585">"<xliff:g id="APPLICATION">%1$s</xliff:g> is upgrading…"</string>
- <string name="android_upgrading_apk" msgid="1339564803894466737">"Optimising app <xliff:g id="NUMBER_0">%1$d</xliff:g> of <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
+ <string name="android_upgrading_apk" msgid="1339564803894466737">"Optimizing app <xliff:g id="NUMBER_0">%1$d</xliff:g> of <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
<string name="android_preparing_apk" msgid="589736917792300956">"Preparing <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
<string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Starting apps."</string>
<string name="android_upgrading_complete" msgid="409800058018374746">"Finishing boot."</string>
- <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"You pressed the power button – this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint."</string>
+ <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly while setting up your fingerprint."</string>
<string name="fp_power_button_enrollment_title" msgid="6976841690455338563">"To end setup, turn off screen"</string>
<string name="fp_power_button_enrollment_button_text" msgid="3199783266386029200">"Turn off"</string>
<string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continue verifying your fingerprint?"</string>
- <string name="fp_power_button_bp_message" msgid="2983163038168903393">"You pressed the power button – this usually turns off the screen.\n\nTry tapping lightly to verify your fingerprint."</string>
+ <string name="fp_power_button_bp_message" msgid="2983163038168903393">"You pressed the power button — this usually turns off the screen.\n\nTry tapping lightly to verify your fingerprint."</string>
<string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Turn off screen"</string>
<string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continue"</string>
<string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
@@ -1269,8 +1269,8 @@
<string name="dump_heap_notification_detail" msgid="8431586843001054050">"Heap dump collected. Tap to share."</string>
<string name="dump_heap_title" msgid="4367128917229233901">"Share heap dump?"</string>
<string name="dump_heap_text" msgid="1692649033835719336">"The <xliff:g id="PROC">%1$s</xliff:g> process has exceeded its memory limit of <xliff:g id="SIZE">%2$s</xliff:g>. A heap dump is available for you to share with its developer. Be careful: this heap dump can contain any of your personal information that the application has access to."</string>
- <string name="dump_heap_system_text" msgid="6805155514925350849">"The <xliff:g id="PROC">%1$s</xliff:g> process has exceeded its memory limit of <xliff:g id="SIZE">%2$s</xliff:g>. A heap dump is available for you to share. Be careful: this heap dump can contain any sensitive personal information that the process has access to, which may include things that you’ve typed."</string>
- <string name="dump_heap_ready_text" msgid="5849618132123045516">"A heap dump of <xliff:g id="PROC">%1$s</xliff:g>’s process is available for you to share. Be careful: this heap dump may contain any sensitive personal information that the process has access to, which may include things that you’ve typed."</string>
+ <string name="dump_heap_system_text" msgid="6805155514925350849">"The <xliff:g id="PROC">%1$s</xliff:g> process has exceeded its memory limit of <xliff:g id="SIZE">%2$s</xliff:g>. A heap dump is available for you to share. Be careful: this heap dump can contain any sensitive personal information that the process has access to, which may include things you’ve typed."</string>
+ <string name="dump_heap_ready_text" msgid="5849618132123045516">"A heap dump of <xliff:g id="PROC">%1$s</xliff:g>’s process is available for you to share. Be careful: this heap dump may contain any sensitive personal information that the process has access to, which may include things you’ve typed."</string>
<string name="sendText" msgid="493003724401350724">"Choose an action for text"</string>
<string name="volume_ringtone" msgid="134784084629229029">"Ringer volume"</string>
<string name="volume_music" msgid="7727274216734955095">"Media volume"</string>
@@ -1290,22 +1290,22 @@
<string name="ringtone_default_with_actual" msgid="2709686194556159773">"Default (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
<string name="ringtone_silent" msgid="397111123930141876">"None"</string>
<string name="ringtone_picker_title" msgid="667342618626068253">"Ringtones"</string>
- <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Alarm Sounds"</string>
- <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Notification Sounds"</string>
+ <string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"Alarm sounds"</string>
+ <string name="ringtone_picker_title_notification" msgid="6387191794719608122">"Notification sounds"</string>
<string name="ringtone_unknown" msgid="5059495249862816475">"Unknown"</string>
- <string name="wifi_available_sign_in" msgid="381054692557675237">"Sign in to a Wi-Fi network"</string>
+ <string name="wifi_available_sign_in" msgid="381054692557675237">"Sign in to Wi-Fi network"</string>
<string name="network_available_sign_in" msgid="1520342291829283114">"Sign in to network"</string>
<!-- no translation found for network_available_sign_in_detailed (7520423801613396556) -->
<skip />
- <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no Internet access"</string>
+ <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has no internet access"</string>
<string name="wifi_no_internet_detailed" msgid="634938444133558942">"Tap for options"</string>
- <string name="mobile_no_internet" msgid="4014455157529909781">"Mobile network has no Internet access"</string>
- <string name="other_networks_no_internet" msgid="6698711684200067033">"Network has no Internet access"</string>
+ <string name="mobile_no_internet" msgid="4014455157529909781">"Mobile network has no internet access"</string>
+ <string name="other_networks_no_internet" msgid="6698711684200067033">"Network has no internet access"</string>
<string name="private_dns_broken_detailed" msgid="3709388271074611847">"Private DNS server cannot be accessed"</string>
<string name="network_partial_connectivity" msgid="4791024923851432291">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> has limited connectivity"</string>
<string name="network_partial_connectivity_detailed" msgid="5741329444564575840">"Tap to connect anyway"</string>
<string name="network_switch_metered" msgid="1531869544142283384">"Switched to <xliff:g id="NETWORK_TYPE">%1$s</xliff:g>"</string>
- <string name="network_switch_metered_detail" msgid="1358296010128405906">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no Internet access. Charges may apply."</string>
+ <string name="network_switch_metered_detail" msgid="1358296010128405906">"Device uses <xliff:g id="NEW_NETWORK">%1$s</xliff:g> when <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> has no internet access. Charges may apply."</string>
<string name="network_switch_metered_toast" msgid="501662047275723743">"Switched from <xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> to <xliff:g id="NEW_NETWORK">%2$s</xliff:g>"</string>
<string-array name="network_switch_type_name">
<item msgid="2255670471736226365">"mobile data"</item>
@@ -1349,7 +1349,7 @@
<string name="date_time_done" msgid="8363155889402873463">"Done"</string>
<string name="perms_new_perm_prefix" msgid="6984556020395757087"><font size="12" fgcolor="#ff33b5e5">"NEW: "</font></string>
<string name="perms_description_app" msgid="2747752389870161996">"Provided by <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
- <string name="no_permissions" msgid="5729199278862516390">"No permission required"</string>
+ <string name="no_permissions" msgid="5729199278862516390">"No permissions required"</string>
<string name="perm_costs_money" msgid="749054595022779685">"this may cost you money"</string>
<string name="dlg_ok" msgid="5103447663504839312">"OK"</string>
<string name="usb_charging_notification_title" msgid="1674124518282666955">"Charging this device via USB"</string>
@@ -1361,7 +1361,7 @@
<string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB accessory connected"</string>
<string name="usb_notification_message" msgid="4715163067192110676">"Tap for more options."</string>
<string name="usb_power_notification_message" msgid="7284765627437897702">"Charging connected device. Tap for more options."</string>
- <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Analogue audio accessory detected"</string>
+ <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Analog audio accessory detected"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"The attached device is not compatible with this phone. Tap to learn more."</string>
<string name="adb_active_notification_title" msgid="408390247354560331">"USB debugging connected"</string>
<string name="adb_active_notification_message" msgid="5617264033476778211">"Tap to turn off USB debugging"</string>
@@ -1399,7 +1399,7 @@
<string name="alert_windows_notification_turn_off_action" msgid="7805857234839123780">"Turn off"</string>
<string name="ext_media_checking_notification_title" msgid="8299199995416510094">"Checking <xliff:g id="NAME">%s</xliff:g>…"</string>
<string name="ext_media_checking_notification_message" msgid="2231566971425375542">"Reviewing current content"</string>
- <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Analysing media storage"</string>
+ <string name="ext_media_checking_notification_message" product="tv" msgid="7986154434946021415">"Analyzing media storage"</string>
<string name="ext_media_new_notification_title" msgid="3517407571407687677">"New <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="ext_media_new_notification_title" product="automotive" msgid="9085349544984742727">"<xliff:g id="NAME">%s</xliff:g> isn’t working"</string>
<string name="ext_media_new_notification_message" msgid="6095403121990786986">"Tap to set up"</string>
@@ -1414,7 +1414,7 @@
<string name="ext_media_unmountable_notification_message" product="automotive" msgid="2274596120715020680">"You may need to reformat the device. Tap to eject."</string>
<string name="ext_media_unsupported_notification_title" msgid="3487534182861251401">"<xliff:g id="NAME">%s</xliff:g> detected"</string>
<string name="ext_media_unsupported_notification_title" product="automotive" msgid="6004193172658722381">"<xliff:g id="NAME">%s</xliff:g> isn’t working"</string>
- <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Tap to set up."</string>
+ <string name="ext_media_unsupported_notification_message" msgid="8463636521459807981">"Tap to set up ."</string>
<string name="ext_media_unsupported_notification_message" product="tv" msgid="1595482802187036532">"Select to set up <xliff:g id="NAME">%s</xliff:g> in a supported format."</string>
<string name="ext_media_unsupported_notification_message" product="automotive" msgid="3412494732736336330">"You may need to reformat the device"</string>
<string name="ext_media_badremoval_notification_title" msgid="4114625551266196872">"<xliff:g id="NAME">%s</xliff:g> unexpectedly removed"</string>
@@ -1423,7 +1423,7 @@
<string name="ext_media_nomedia_notification_message" msgid="2832724384636625852">"Some functionality may not work properly. Insert new storage."</string>
<string name="ext_media_unmounting_notification_title" msgid="4147986383917892162">"Ejecting <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="ext_media_unmounting_notification_message" msgid="5717036261538754203">"Don’t remove"</string>
- <string name="ext_media_init_action" msgid="2312974060585056709">"Set-up"</string>
+ <string name="ext_media_init_action" msgid="2312974060585056709">"Set up"</string>
<string name="ext_media_unmount_action" msgid="966992232088442745">"Eject"</string>
<string name="ext_media_browse_action" msgid="344865351947079139">"Explore"</string>
<string name="ext_media_seamless_action" msgid="8837030226009268080">"Switch output"</string>
@@ -1431,7 +1431,7 @@
<string name="ext_media_missing_message" msgid="4408988706227922909">"Insert device again"</string>
<string name="ext_media_move_specific_title" msgid="8492118544775964250">"Moving <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="ext_media_move_title" msgid="2682741525619033637">"Moving data"</string>
- <string name="ext_media_move_success_title" msgid="4901763082647316767">"Content transfer is finished"</string>
+ <string name="ext_media_move_success_title" msgid="4901763082647316767">"Content transfer is done"</string>
<string name="ext_media_move_success_message" msgid="9159542002276982979">"Content moved to <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="ext_media_move_failure_title" msgid="3184577479181333665">"Couldn’t move content"</string>
<string name="ext_media_move_failure_message" msgid="4197306718121869335">"Try moving content again"</string>
@@ -1455,8 +1455,8 @@
<string name="permdesc_requestInstallPackages" msgid="3969369278325313067">"Allows an application to request installation of packages."</string>
<string name="permlab_requestDeletePackages" msgid="2541172829260106795">"request delete packages"</string>
<string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"Allows an application to request deletion of packages."</string>
- <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"ask to ignore battery optimisations"</string>
- <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Allows an app to ask for permission to ignore battery optimisations for that app."</string>
+ <string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"ask to ignore battery optimizations"</string>
+ <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"Allows an app to ask for permission to ignore battery optimizations for that app."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"query all packages"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"Allows an app to see all installed packages."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"Tap twice for zoom control"</string>
@@ -1468,9 +1468,9 @@
<string name="ime_action_done" msgid="6299921014822891569">"Done"</string>
<string name="ime_action_previous" msgid="6548799326860401611">"Prev"</string>
<string name="ime_action_default" msgid="8265027027659800121">"Execute"</string>
- <string name="dial_number_using" msgid="6060769078933953531">"Dial number\n using <xliff:g id="NUMBER">%s</xliff:g>"</string>
- <string name="create_contact_using" msgid="6200708808003692594">"Create contact\n using <xliff:g id="NUMBER">%s</xliff:g>"</string>
- <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"The following one or more applications request permission to access your account, now and in the future."</string>
+ <string name="dial_number_using" msgid="6060769078933953531">"Dial number\nusing <xliff:g id="NUMBER">%s</xliff:g>"</string>
+ <string name="create_contact_using" msgid="6200708808003692594">"Create contact\nusing <xliff:g id="NUMBER">%s</xliff:g>"</string>
+ <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"The following one or more apps request permission to access your account, now and in the future."</string>
<string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"Do you want to allow this request?"</string>
<string name="grant_permissions_header_text" msgid="3420736827804657201">"Access request"</string>
<string name="allow" msgid="6195617008611933762">"Allow"</string>
@@ -1480,7 +1480,7 @@
<string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"Permission requested by <xliff:g id="APP">%1$s</xliff:g>\nfor account <xliff:g id="ACCOUNT">%2$s</xliff:g>."</string>
<string name="forward_intent_to_owner" msgid="4620359037192871015">"You\'re using this app outside of your work profile"</string>
<string name="forward_intent_to_work" msgid="3620262405636021151">"You\'re using this app in your work profile"</string>
- <string name="input_method_binding_label" msgid="1166731601721983656">"Input Method"</string>
+ <string name="input_method_binding_label" msgid="1166731601721983656">"Input method"</string>
<string name="sync_binding_label" msgid="469249309424662147">"Sync"</string>
<string name="accessibility_binding_label" msgid="1974602776545801715">"Accessibility"</string>
<string name="wallpaper_binding_label" msgid="1197440498000786738">"Wallpaper"</string>
@@ -1522,7 +1522,7 @@
<string name="gpsNotifMessage" msgid="7346649122793758032">"Requested by <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="SERVICE">%2$s</xliff:g>)"</string>
<string name="gpsVerifYes" msgid="3719843080744112940">"Yes"</string>
<string name="gpsVerifNo" msgid="1671201856091564741">"No"</string>
- <string name="sync_too_many_deletes" msgid="6999440774578705300">"Deletion limit exceeded"</string>
+ <string name="sync_too_many_deletes" msgid="6999440774578705300">"Delete limit exceeded"</string>
<string name="sync_too_many_deletes_desc" msgid="7409327940303504440">"There are <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g> deleted items for <xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>, account <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g>. What do you want to do?"</string>
<string name="sync_really_delete" msgid="5657871730315579051">"Delete the items"</string>
<string name="sync_undo_deletes" msgid="5786033331266418896">"Undo the deletes"</string>
@@ -1538,8 +1538,8 @@
<string name="time_picker_decrement_minute_button" msgid="230925389943411490">"Decrease minute"</string>
<string name="time_picker_increment_hour_button" msgid="3063572723197178242">"Increase hour"</string>
<string name="time_picker_decrement_hour_button" msgid="584101766855054412">"Decrease hour"</string>
- <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Set p.m."</string>
- <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Set a.m."</string>
+ <string name="time_picker_increment_set_pm_button" msgid="5889149366900376419">"Set PM"</string>
+ <string name="time_picker_decrement_set_am_button" msgid="1422608001541064087">"Set AM"</string>
<string name="date_picker_increment_month_button" msgid="3447263316096060309">"Increase month"</string>
<string name="date_picker_decrement_month_button" msgid="6531888937036883014">"Decrease month"</string>
<string name="date_picker_increment_day_button" msgid="4349336637188534259">"Increase day"</string>
@@ -1591,15 +1591,15 @@
<string name="issued_to" msgid="5975877665505297662">"Issued to:"</string>
<string name="common_name" msgid="1486334593631798443">"Common name:"</string>
<string name="org_name" msgid="7526331696464255245">"Organization:"</string>
- <string name="org_unit" msgid="995934486977223076">"Organisational unit:"</string>
+ <string name="org_unit" msgid="995934486977223076">"Organizational unit:"</string>
<string name="issued_by" msgid="7872459822431585684">"Issued by:"</string>
<string name="validity_period" msgid="1717724283033175968">"Validity:"</string>
<string name="issued_on" msgid="5855489688152497307">"Issued on:"</string>
<string name="expires_on" msgid="1623640879705103121">"Expires on:"</string>
<string name="serial_number" msgid="3479576915806623429">"Serial number:"</string>
<string name="fingerprints" msgid="148690767172613723">"Fingerprints:"</string>
- <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 fingerprint"</string>
- <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 fingerprint"</string>
+ <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 fingerprint:"</string>
+ <string name="sha1_fingerprint" msgid="2339915142825390774">"SHA-1 fingerprint:"</string>
<string name="activity_chooser_view_see_all" msgid="3917045206812726099">"See all"</string>
<string name="activity_chooser_view_dialog_title_default" msgid="8880731437191978314">"Choose activity"</string>
<string name="share_action_provider_share_with" msgid="1904096863622941880">"Share with"</string>
@@ -1651,9 +1651,9 @@
<string name="kg_invalid_sim_pin_hint" msgid="4821601451222564077">"Type a PIN that is 4 to 8 numbers."</string>
<string name="kg_invalid_sim_puk_hint" msgid="2539364558870734339">"PUK code should be 8 numbers."</string>
<string name="kg_invalid_puk" msgid="4809502818518963344">"Re-enter the correct PUK code. Repeated attempts will permanently disable the SIM."</string>
- <string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"PIN codes do not match"</string>
+ <string name="kg_invalid_confirm_pin_hint" product="default" msgid="4705368340409816254">"PIN codes does not match"</string>
<string name="kg_login_too_many_attempts" msgid="699292728290654121">"Too many pattern attempts"</string>
- <string name="kg_login_instructions" msgid="3619844310339066827">"To unlock, sign in with your Google Account."</string>
+ <string name="kg_login_instructions" msgid="3619844310339066827">"To unlock, sign in with your Google account."</string>
<string name="kg_login_username_hint" msgid="1765453775467133251">"Username (email)"</string>
<string name="kg_login_password_hint" msgid="3330530727273164402">"Password"</string>
<string name="kg_login_submit_button" msgid="893611277617096870">"Sign in"</string>
@@ -1670,13 +1670,13 @@
<string name="kg_failed_attempts_now_wiping" product="tv" msgid="5045460916106267585">"You have incorrectly attempted to unlock your Android TV device <xliff:g id="NUMBER">%d</xliff:g> times. Your Android TV device will now be reset to factory default."</string>
<string name="kg_failed_attempts_now_wiping" product="default" msgid="5043730590446071189">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER">%d</xliff:g> times. The phone will now be reset to factory default."</string>
<string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="7086799295109717623">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your tablet using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
- <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"You have drawn your unlock pattern incorrectly <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
+ <string name="kg_failed_attempts_almost_at_login" product="tv" msgid="4670840383567106114">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your Android TV device using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
<string name="kg_failed_attempts_almost_at_login" product="default" msgid="5270861875006378092">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, you will be asked to unlock your phone using an email account.\n\n Try again in <xliff:g id="NUMBER_2">%3$d</xliff:g> seconds."</string>
<string name="kg_text_message_separator" product="default" msgid="4503708889934976866">" — "</string>
<string name="kg_reordering_delete_drop_target_text" msgid="2034358143731750914">"Remove"</string>
<string name="safe_media_volume_warning" product="default" msgid="3751676824423049994">"Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."</string>
<string name="accessibility_shortcut_warning_dialog_title" msgid="4017995837692622933">"Use Accessibility Shortcut?"</string>
- <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for three seconds will start an accessibility feature."</string>
+ <string name="accessibility_shortcut_toogle_warning" msgid="4161716521310929544">"When the shortcut is on, pressing both volume buttons for 3 seconds will start an accessibility feature."</string>
<string name="accessibility_shortcut_multiple_service_warning_title" msgid="3135860819356676426">"Turn on shortcut for accessibility features?"</string>
<string name="accessibility_shortcut_multiple_service_warning" msgid="3740723309483706911">"Holding down both volume keys for a few seconds turns on accessibility features. This may change how your device works.\n\nCurrent features:\n<xliff:g id="SERVICE">%1$s</xliff:g>\nYou can change selected features in Settings > Accessibility."</string>
<string name="accessibility_shortcut_multiple_service_list" msgid="2128323171922023762">" • <xliff:g id="SERVICE">%1$s</xliff:g>\n"</string>
@@ -1695,7 +1695,7 @@
<string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Allow"</string>
<string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Deny"</string>
<string name="accessibility_select_shortcut_menu_title" msgid="6002726538854613272">"Tap a feature to start using it:"</string>
- <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the Accessibility button"</string>
+ <string name="accessibility_edit_shortcut_menu_button_title" msgid="239446795930436325">"Choose features to use with the accessibility button"</string>
<string name="accessibility_edit_shortcut_menu_volume_title" msgid="1077294237378645981">"Choose features to use with the volume key shortcut"</string>
<string name="accessibility_uncheck_legacy_item_warning" msgid="8047830891064817447">"<xliff:g id="SERVICE_NAME">%s</xliff:g> has been turned off"</string>
<string name="edit_accessibility_shortcut_menu_button" msgid="8885752738733772935">"Edit shortcuts"</string>
@@ -1704,15 +1704,15 @@
<string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Use Shortcut"</string>
<string name="color_inversion_feature_name" msgid="326050048927789012">"Colour inversion"</string>
<string name="color_correction_feature_name" msgid="3655077237805422597">"Colour correction"</string>
- <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-handed mode"</string>
+ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"One-Handed mode"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Extra dim"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Held volume keys. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> turned on."</string>
<string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Held volume keys. <xliff:g id="SERVICE_NAME">%1$s</xliff:g> turned off."</string>
<string name="accessibility_shortcut_spoken_feedback" msgid="4228997042855695090">"Press and hold both volume keys for three seconds to use <xliff:g id="SERVICE_NAME">%1$s</xliff:g>"</string>
- <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Choose a feature to use when you tap the Accessibility button:"</string>
+ <string name="accessibility_button_prompt_text" msgid="8343213623338605305">"Choose a feature to use when you tap the accessibility button:"</string>
<string name="accessibility_gesture_prompt_text" msgid="8742535972130563952">"Choose a feature to use with the accessibility gesture (swipe up from the bottom of the screen with two fingers):"</string>
<string name="accessibility_gesture_3finger_prompt_text" msgid="5211827854510660203">"Choose a feature to use with the accessibility gesture (swipe up from the bottom of the screen with three fingers):"</string>
- <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"To switch between features, touch and hold the Accessibility button."</string>
+ <string name="accessibility_button_instructional_text" msgid="8853928358872550500">"To switch between features, touch & hold the accessibility button."</string>
<string name="accessibility_gesture_instructional_text" msgid="9196230728837090497">"To switch between features, swipe up with two fingers and hold."</string>
<string name="accessibility_gesture_3finger_instructional_text" msgid="3425123684990193765">"To switch between features, swipe up with three fingers and hold."</string>
<string name="accessibility_magnification_chooser_text" msgid="1502075582164931596">"Magnification"</string>
@@ -1764,9 +1764,9 @@
<string name="mediasize_na_junior_legal" msgid="3398084874757748531">"Junior Legal"</string>
<string name="mediasize_na_ledger" msgid="1819497882853940248">"Ledger"</string>
<string name="mediasize_na_tabloid" msgid="6792611672983574375">"Tabloid"</string>
- <string name="mediasize_na_index_3x5" msgid="990821038991491710">"Index Card 3 x 5"</string>
- <string name="mediasize_na_index_4x6" msgid="4414381976602032401">"Index Card 4 x 6"</string>
- <string name="mediasize_na_index_5x8" msgid="4499341583361946948">"Index Card 5 x 8"</string>
+ <string name="mediasize_na_index_3x5" msgid="990821038991491710">"Index Card 3x5"</string>
+ <string name="mediasize_na_index_4x6" msgid="4414381976602032401">"Index Card 4x6"</string>
+ <string name="mediasize_na_index_5x8" msgid="4499341583361946948">"Index Card 5x8"</string>
<string name="mediasize_na_monarch" msgid="4396943937986136896">"Monarch"</string>
<string name="mediasize_na_quarto" msgid="2119101847712239885">"Quarto"</string>
<string name="mediasize_na_foolscap" msgid="5011612828564394648">"Foolscap"</string>
@@ -1829,12 +1829,12 @@
<string name="restr_pin_enter_admin_pin" msgid="1199419462726962697">"Enter admin PIN"</string>
<string name="restr_pin_enter_pin" msgid="373139384161304555">"Enter PIN"</string>
<string name="restr_pin_incorrect" msgid="3861383632940852496">"Incorrect"</string>
- <string name="restr_pin_enter_old_pin" msgid="7537079094090650967">"Current PIN:"</string>
+ <string name="restr_pin_enter_old_pin" msgid="7537079094090650967">"Current PIN"</string>
<string name="restr_pin_enter_new_pin" msgid="3267614461844565431">"New PIN"</string>
<string name="restr_pin_confirm_pin" msgid="7143161971614944989">"Confirm new PIN"</string>
<string name="restr_pin_create_pin" msgid="917067613896366033">"Create a PIN for modifying restrictions"</string>
<string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"PINs don\'t match. Try again."</string>
- <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN is too short. Must be at least four digits."</string>
+ <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN is too short. Must be at least 4 digits."</string>
<string name="restr_pin_try_later" msgid="5897719962541636727">"Try again later"</string>
<string name="immersive_cling_title" msgid="2307034298721541791">"Viewing full screen"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"To exit, swipe down from the top."</string>
@@ -1857,8 +1857,8 @@
<string name="package_updated_device_owner" msgid="7560272363805506941">"Updated by your admin"</string>
<string name="package_deleted_device_owner" msgid="2292335928930293023">"Deleted by your admin"</string>
<string name="confirm_battery_saver" msgid="5247976246208245754">"OK"</string>
- <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string>
- <string name="battery_saver_description" msgid="8518809702138617167">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features and some network connections."</string>
+ <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features, and some network connections."</string>
+ <string name="battery_saver_description" msgid="8518809702138617167">"Battery Saver turns on Dark theme and limits or turns off background activity, some visual effects, certain features, and some network connections."</string>
<string name="data_saver_description" msgid="4995164271550590517">"To help reduce data usage, Data Saver prevents some apps from sending or receiving data in the background. An app you\'re currently using can access data, but may do so less frequently. This may mean, for example, that images don\'t display until you tap them."</string>
<string name="data_saver_enable_title" msgid="7080620065745260137">"Turn on Data Saver?"</string>
<string name="data_saver_enable_button" msgid="4399405762586419726">"Turn on"</string>
@@ -1874,7 +1874,7 @@
<string name="zen_mode_until" msgid="2250286190237669079">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
<string name="zen_mode_alarm" msgid="7046911727540499275">"Until <xliff:g id="FORMATTEDTIME">%1$s</xliff:g> (next alarm)"</string>
<string name="zen_mode_forever" msgid="740585666364912448">"Until you turn off"</string>
- <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"Until you turn off Do not disturb"</string>
+ <string name="zen_mode_forever_dnd" msgid="3423201955704180067">"Until you turn off Do Not Disturb"</string>
<string name="zen_mode_rule_name_combination" msgid="7174598364351313725">"<xliff:g id="FIRST">%1$s</xliff:g> / <xliff:g id="REST">%2$s</xliff:g>"</string>
<string name="toolbar_collapse_description" msgid="8009920446193610996">"Collapse"</string>
<string name="zen_mode_feature_name" msgid="3785547207263754500">"Do not disturb"</string>
@@ -1906,22 +1906,22 @@
<string name="usb_midi_peripheral_product_name" msgid="2836276258480904434">"USB Peripheral Port"</string>
<string name="floating_toolbar_open_overflow_description" msgid="2260297653578167367">"More options"</string>
<string name="floating_toolbar_close_overflow_description" msgid="3949818077708138098">"Close overflow"</string>
- <string name="maximize_button_text" msgid="4258922519914732645">"Maximise"</string>
+ <string name="maximize_button_text" msgid="4258922519914732645">"Maximize"</string>
<string name="close_button_text" msgid="10603510034455258">"Close"</string>
<string name="notification_messaging_title_template" msgid="772857526770251989">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g>: <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string>
<string name="call_notification_answer_action" msgid="5999246836247132937">"Answer"</string>
<string name="call_notification_answer_video_action" msgid="2086030940195382249">"Video"</string>
<string name="call_notification_decline_action" msgid="3700345945214000726">"Decline"</string>
- <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Hang up"</string>
+ <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Hang Up"</string>
<string name="call_notification_incoming_text" msgid="6143109825406638201">"Incoming call"</string>
- <string name="call_notification_ongoing_text" msgid="3880832933933020875">"On-going call"</string>
+ <string name="call_notification_ongoing_text" msgid="3880832933933020875">"Ongoing call"</string>
<string name="call_notification_screening_text" msgid="8396931408268940208">"Screening an incoming call"</string>
- <string name="default_notification_channel_label" msgid="3697928973567217330">"Uncategorised"</string>
+ <string name="default_notification_channel_label" msgid="3697928973567217330">"Uncategorized"</string>
<string name="importance_from_user" msgid="2782756722448800447">"You set the importance of these notifications."</string>
<string name="importance_from_person" msgid="4235804979664465383">"This is important because of the people involved."</string>
<string name="notification_history_title_placeholder" msgid="7748630986182249599">"Custom app notification"</string>
- <string name="user_creation_account_exists" msgid="2239146360099708035">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists)?"</string>
- <string name="user_creation_adding" msgid="7305185499667958364">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g>?"</string>
+ <string name="user_creation_account_exists" msgid="2239146360099708035">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> (a User with this account already exists) ?"</string>
+ <string name="user_creation_adding" msgid="7305185499667958364">"Allow <xliff:g id="APP">%1$s</xliff:g> to create a new User with <xliff:g id="ACCOUNT">%2$s</xliff:g> ?"</string>
<string name="supervised_user_creation_label" msgid="6884904353827427515">"Add supervised user"</string>
<string name="language_selection_title" msgid="52674936078683285">"Add a language"</string>
<string name="country_selection_title" msgid="5221495687299014379">"Region preference"</string>
@@ -1932,7 +1932,7 @@
<string name="region_picker_section_all" msgid="756441309928774155">"All regions"</string>
<string name="locale_search_menu" msgid="6258090710176422934">"Search"</string>
<string name="app_suspended_title" msgid="888873445010322650">"App isn’t available"</string>
- <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> isn’t available at the moment. This is managed by <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string>
+ <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> isn’t available right now. This is managed by <xliff:g id="APP_NAME_1">%2$s</xliff:g>."</string>
<string name="app_suspended_more_details" msgid="211260942831587014">"Learn more"</string>
<string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Unpause app"</string>
<string name="work_mode_off_title" msgid="961171256005852058">"Turn on work apps?"</string>
@@ -1958,7 +1958,7 @@
<string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g>. Try on your Android TV device instead."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g>. Try on your tablet instead."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"This can’t be accessed on your <xliff:g id="DEVICE">%1$s</xliff:g>. Try on your phone instead."</string>
- <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates or contact the developer."</string>
+ <string name="deprecated_target_sdk_message" msgid="5203207875657579953">"This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer."</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Check for update"</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"You have new messages"</string>
<string name="new_sms_notification_content" msgid="3197949934153460639">"Open SMS app to view"</string>
@@ -1996,21 +1996,21 @@
<string name="time_picker_prompt_label" msgid="303588544656363889">"Type in time"</string>
<string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"Switch to text input mode for the time input."</string>
<string name="time_picker_radial_mode_description" msgid="1222342577115016953">"Switch to clock mode for the time input."</string>
- <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Auto-fill options"</string>
- <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Save for AutoFill"</string>
- <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Contents can’t be auto-filled"</string>
- <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"No auto-fill suggestions"</string>
- <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{One auto-fill suggestion}other{# auto-fill suggestions}}"</string>
+ <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Autofill options"</string>
+ <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Save for Autofill"</string>
+ <string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Contents can’t be autofilled"</string>
+ <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"No autofill suggestions"</string>
+ <string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{One autofill suggestion}other{# autofill suggestions}}"</string>
<string name="autofill_save_title" msgid="7719802414283739775">"Save to "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_title_with_type" msgid="3002460014579799605">"Save <xliff:g id="TYPE">%1$s</xliff:g> to "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_title_with_2types" msgid="3783270967447869241">"Save <xliff:g id="TYPE_0">%1$s</xliff:g> and <xliff:g id="TYPE_1">%2$s</xliff:g> to "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
- <string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Save <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> and <xliff:g id="TYPE_2">%3$s</xliff:g> to "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
+ <string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Save <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, and <xliff:g id="TYPE_2">%3$s</xliff:g> to "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>"?"</string>
<string name="autofill_update_title" msgid="3630695947047069136">"Update in "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
<string name="autofill_update_title_with_type" msgid="5264152633488495704">"Update <xliff:g id="TYPE">%1$s</xliff:g> in "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
<string name="autofill_update_title_with_2types" msgid="1797514386321086273">"Update <xliff:g id="TYPE_0">%1$s</xliff:g> and <xliff:g id="TYPE_1">%2$s</xliff:g> in "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>"?"</string>
- <string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Update these items in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> and <xliff:g id="TYPE_2">%3$s</xliff:g>?"</string>
+ <string name="autofill_update_title_with_3types" msgid="1312232153076212291">"Update these items in "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>": <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g>, and <xliff:g id="TYPE_2">%3$s</xliff:g> ?"</string>
<string name="autofill_save_yes" msgid="8035743017382012850">"Save"</string>
- <string name="autofill_save_no" msgid="9212826374207023544">"No, thanks"</string>
+ <string name="autofill_save_no" msgid="9212826374207023544">"No thanks"</string>
<string name="autofill_save_notnow" msgid="2853932672029024195">"Not now"</string>
<string name="autofill_save_never" msgid="6821841919831402526">"Never"</string>
<string name="autofill_update_yes" msgid="4608662968996874445">"Update"</string>
@@ -2037,9 +2037,9 @@
<string name="mmcc_imsi_unknown_in_hlr_msim_template" msgid="3688508325248599657">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> not provisioned"</string>
<string name="mmcc_illegal_ms_msim_template" msgid="832644375774599327">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> not allowed"</string>
<string name="mmcc_illegal_me_msim_template" msgid="4802735138861422802">"SIM <xliff:g id="SIMNUMBER">%d</xliff:g> not allowed"</string>
- <string name="popup_window_default_title" msgid="6907717596694826919">"Pop-Up Window"</string>
+ <string name="popup_window_default_title" msgid="6907717596694826919">"Popup Window"</string>
<string name="slice_more_content" msgid="3377367737876888459">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
- <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"App version downgraded or isn’t compatible with this shortcut"</string>
+ <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"App version downgraded, or isn’t compatible with this shortcut"</string>
<string name="shortcut_restore_not_supported" msgid="4763198938588468400">"Couldn’t restore shortcut because app doesn’t support backup and restore"</string>
<string name="shortcut_restore_signature_mismatch" msgid="579345304221605479">"Couldn’t restore shortcut because of app signature mismatch"</string>
<string name="shortcut_restore_unknown_issue" msgid="2478146134395982154">"Couldn’t restore shortcut"</string>
@@ -2050,17 +2050,17 @@
<string name="log_access_confirmation_title" msgid="2343578467290592708">"Allow <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> to access all device logs?"</string>
<string name="log_access_confirmation_allow" msgid="5302517782599389507">"Allow one-time access"</string>
<string name="log_access_confirmation_deny" msgid="7685790957455099845">"Don’t allow"</string>
- <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
- <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps that you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
+ <string name="log_access_confirmation_body" product="default" msgid="1806692062668620735">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device."</string>
+ <string name="log_access_confirmation_body" product="tv" msgid="7379536536425265262">"Device logs record what happens on your device. Apps can use these logs to find and fix issues.\n\nSome logs may contain sensitive info, so only allow apps you trust to access all device logs. \n\nIf you don’t allow this app to access all device logs, it can still access its own logs. Your device manufacturer may still be able to access some logs or info on your device.\n\nLearn more at g.co/android/devicelogs."</string>
<string name="log_access_do_not_show_again" msgid="1058690599083091552">"Don’t show again"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_0">%1$s</xliff:g> wants to show <xliff:g id="APP_2">%2$s</xliff:g> slices"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"Edit"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"Calls and notifications will vibrate"</string>
<string name="volume_dialog_ringer_guidance_silent" msgid="1011246774949993783">"Calls and notifications will be muted"</string>
<string name="notification_channel_system_changes" msgid="2462010596920209678">"System changes"</string>
- <string name="notification_channel_do_not_disturb" msgid="7832584281883687653">"Do not disturb"</string>
+ <string name="notification_channel_do_not_disturb" msgid="7832584281883687653">"Do Not Disturb"</string>
<string name="zen_upgrade_notification_visd_title" msgid="2001148984371968620">"New: Do Not Disturb is hiding notifications"</string>
- <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Tap to find out more and change."</string>
+ <string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"Tap to learn more and change."</string>
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Do Not Disturb has changed"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tap to check what\'s blocked."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Review notification settings"</string>
@@ -2072,17 +2072,17 @@
<string name="notification_appops_camera_active" msgid="8177643089272352083">"Camera"</string>
<string name="notification_appops_microphone_active" msgid="581333393214739332">"Microphone"</string>
<string name="notification_appops_overlay_active" msgid="5571732753262836481">"displaying over other apps on your screen"</string>
- <string name="notification_feedback_indicator" msgid="663476517711323016">"Provide feedback"</string>
- <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"This notification was promoted to default. Tap to provide feedback."</string>
- <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"This notification was demoted to silent. Tap to provide feedback."</string>
+ <string name="notification_feedback_indicator" msgid="663476517711323016">"Provide Feedback"</string>
+ <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"This notification was promoted to Default. Tap to provide feedback."</string>
+ <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"This notification was demoted to Silent. Tap to provide feedback."</string>
<string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"This notification was ranked higher. Tap to provide feedback."</string>
<string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"This notification was ranked lower. Tap to provide feedback."</string>
<string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Enhanced notifications"</string>
- <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Suggested actions and replies are now provided by enhanced notifications. Android adaptive notifications are no longer supported."</string>
+ <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"Suggested actions and replies are now provided by enhanced notifications. Android Adaptive Notifications are no longer supported."</string>
<string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
<string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Turn off"</string>
<string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Learn more"</string>
- <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Enhanced notifications replaced Android adaptive notifications in Android 12. This feature shows suggested actions and replies, and organises your notifications.\n\nEnhanced notifications can access notification content, including personal information like contact names and messages. This feature can also dismiss or respond to notifications, such as answering phone calls, and control Do Not Disturb."</string>
+ <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Enhanced notifications replaced Android Adaptive Notifications in Android 12. This feature shows suggested actions and replies, and organizes your notifications.\n\nEnhanced notifications can access notification content, including personal information like contact names and messages. This feature can also dismiss or respond to notifications, such as answering phone calls, and control Do Not Disturb."</string>
<string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string>
<string name="dynamic_mode_notification_title" msgid="9205715501274608016">"Battery may run out before usual charge"</string>
<string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"Battery Saver activated to extend battery life"</string>
@@ -2120,25 +2120,25 @@
<string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Recent Apps"</string>
<string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Notifications"</string>
<string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Quick Settings"</string>
- <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Power Dialogue"</string>
+ <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Power Dialog"</string>
<string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Lock Screen"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Screenshot"</string>
- <string name="accessibility_system_action_headset_hook_label" msgid="8524691721287425468">"Headset hook"</string>
- <string name="accessibility_system_action_on_screen_a11y_shortcut_label" msgid="8488701469459210309">"On-screen accessibility shortcut"</string>
- <string name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" msgid="1057878690209817886">"On-screen accessibility shortcut chooser"</string>
- <string name="accessibility_system_action_hardware_a11y_shortcut_label" msgid="5764644187715255107">"Accessibility shortcut"</string>
- <string name="accessibility_system_action_dismiss_notification_shade" msgid="8931637495533770352">"Dismiss notification shade"</string>
- <string name="accessibility_system_action_dpad_up_label" msgid="1029042950229333782">"Dpad up"</string>
- <string name="accessibility_system_action_dpad_down_label" msgid="3441918448624921461">"Dpad down"</string>
- <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad left"</string>
- <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad right"</string>
- <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad centre"</string>
+ <string name="accessibility_system_action_headset_hook_label" msgid="8524691721287425468">"Headset Hook"</string>
+ <string name="accessibility_system_action_on_screen_a11y_shortcut_label" msgid="8488701469459210309">"On-screen Accessibility Shortcut"</string>
+ <string name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" msgid="1057878690209817886">"On-screen Accessibility Shortcut Chooser"</string>
+ <string name="accessibility_system_action_hardware_a11y_shortcut_label" msgid="5764644187715255107">"Accessibility Shortcut"</string>
+ <string name="accessibility_system_action_dismiss_notification_shade" msgid="8931637495533770352">"Dismiss Notification Shade"</string>
+ <string name="accessibility_system_action_dpad_up_label" msgid="1029042950229333782">"Dpad Up"</string>
+ <string name="accessibility_system_action_dpad_down_label" msgid="3441918448624921461">"Dpad Down"</string>
+ <string name="accessibility_system_action_dpad_left_label" msgid="6557647179116479152">"Dpad Left"</string>
+ <string name="accessibility_system_action_dpad_right_label" msgid="9180196950365804081">"Dpad Right"</string>
+ <string name="accessibility_system_action_dpad_center_label" msgid="8149791419358224893">"Dpad Center"</string>
<string name="accessibility_freeform_caption" msgid="8377519323496290122">"Caption bar of <xliff:g id="APP_NAME">%1$s</xliff:g>."</string>
<string name="as_app_forced_to_restricted_bucket" msgid="8233871289353898964">"<xliff:g id="PACKAGE_NAME">%1$s</xliff:g> has been put into the RESTRICTED bucket"</string>
<string name="conversation_single_line_name_display" msgid="8958948312915255999">"<xliff:g id="SENDER_NAME">%1$s</xliff:g>:"</string>
<string name="conversation_single_line_image_placeholder" msgid="6983271082911936900">"sent an image"</string>
<string name="conversation_title_fallback_one_to_one" msgid="1980753619726908614">"Conversation"</string>
- <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Group conversation"</string>
+ <string name="conversation_title_fallback_group_chat" msgid="456073374993104303">"Group Conversation"</string>
<string name="unread_convo_overflow" msgid="920517615597353833">"<xliff:g id="MAX_UNREAD_COUNT">%1$d</xliff:g>+"</string>
<string name="resolver_personal_tab" msgid="2051260504014442073">"Personal"</string>
<string name="resolver_work_tab" msgid="2690019516263167035">"Work"</string>
@@ -2169,7 +2169,7 @@
<string name="PERSOSUBSTATE_SIM_SIM_PUK_ENTRY" msgid="3013902515773728996">"Enter PUK"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK1_ENTRY" msgid="2974411408893410289">"RUIM network1 unlock PIN"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK2_ENTRY" msgid="687618528751880721">"RUIM network2 unlock PIN"</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_ENTRY" msgid="6810596579655575381">"RUIM HRPD unlock PIN"</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_ENTRY" msgid="6810596579655575381">"RUIM hrpd unlock PIN"</string>
<string name="PERSOSUBSTATE_RUIM_CORPORATE_ENTRY" msgid="2715929642540980259">"RUIM corporate unlock PIN"</string>
<string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ENTRY" msgid="8557791623303951590">"RUIM service provider unlock PIN"</string>
<string name="PERSOSUBSTATE_RUIM_RUIM_ENTRY" msgid="7382468767274580323">"RUIM unlock PIN"</string>
@@ -2185,8 +2185,8 @@
<string name="PERSOSUBSTATE_SIM_IMPI_ENTRY" msgid="7043865376145617024">"IMPI unlock PIN"</string>
<string name="PERSOSUBSTATE_SIM_NS_SP_ENTRY" msgid="6144227308185112176">"Network subset service provider unlock PIN"</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_IN_PROGRESS" msgid="4233355366318061180">"Requesting SIM network unlock…"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_IN_PROGRESS" msgid="6742563947637715645">"Requesting SIM network subset unlock…"</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="2033399698172403560">"Requesting SIM service provider unlock…"</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_IN_PROGRESS" msgid="6742563947637715645">"Requesting SIM network subset unlock …"</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="2033399698172403560">"Requesting SIM service provider un lock…"</string>
<string name="PERSOSUBSTATE_SIM_CORPORATE_IN_PROGRESS" msgid="4795977251920732254">"Requesting SIM corporate unlock…"</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_PUK_IN_PROGRESS" msgid="1090425878157254446">"Requesting PUK unlock…"</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_IN_PROGRESS" msgid="6476898876518094438">"Requesting PUK unlock…"</string>
@@ -2196,14 +2196,14 @@
<string name="PERSOSUBSTATE_SIM_SIM_IN_PROGRESS" msgid="6709169861932992750">"Requesting SIM unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK1_IN_PROGRESS" msgid="4013870911606478520">"Requesting RUIM network1 unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK2_IN_PROGRESS" msgid="9032651188219523434">"Requesting RUIM network2 unlock…"</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_IN_PROGRESS" msgid="6584576506344491207">"Requesting RUIM HRPD unlock…"</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_IN_PROGRESS" msgid="6584576506344491207">"Requesting RUIM hrpd unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_IN_PROGRESS" msgid="830981927724888114">"Requesting RUIM service provider unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_CORPORATE_IN_PROGRESS" msgid="7851790973098894802">"Requesting RUIM corporate unlock…"</string>
<string name="PERSOSUBSTATE_SIM_SPN_IN_PROGRESS" msgid="1149560739586960121">"Requesting SPN unlock…"</string>
<string name="PERSOSUBSTATE_SIM_SP_EHPLMN_IN_PROGRESS" msgid="5708964693522116025">"Requesting SP Equivalent Home PLMN unlock…"</string>
<string name="PERSOSUBSTATE_SIM_ICCID_IN_PROGRESS" msgid="7288103122966483455">"Requesting ICCID unlock…"</string>
<string name="PERSOSUBSTATE_SIM_IMPI_IN_PROGRESS" msgid="4036752174056147753">"Requesting IMPI unlock…"</string>
- <string name="PERSOSUBSTATE_SIM_NS_SP_IN_PROGRESS" msgid="5089536274515338566">"Requesting network subset service provider unlock…"</string>
+ <string name="PERSOSUBSTATE_SIM_NS_SP_IN_PROGRESS" msgid="5089536274515338566">"Requesting Network subset service provider unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_RUIM_IN_PROGRESS" msgid="6737197986936251958">"Requesting RUIM unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK1_PUK_IN_PROGRESS" msgid="5658767775619998623">"Requesting PUK unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_NETWORK2_PUK_IN_PROGRESS" msgid="665978313257653727">"Requesting PUK unlock…"</string>
@@ -2211,16 +2211,16 @@
<string name="PERSOSUBSTATE_RUIM_CORPORATE_PUK_IN_PROGRESS" msgid="2695664012344346788">"Requesting PUK unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK_IN_PROGRESS" msgid="2695678959963807782">"Requesting PUK unlock…"</string>
<string name="PERSOSUBSTATE_RUIM_RUIM_PUK_IN_PROGRESS" msgid="1230605365926493599">"Requesting PUK unlock…"</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_ERROR" msgid="1924844017037151535">"SIM network unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ERROR" msgid="3372797822292089708">"SIM network subset unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ERROR" msgid="1878443146720411381">"SIM service provider unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_ERROR" msgid="7664778312218023192">"SIM corporate unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_ERROR" msgid="1924844017037151535">"SIM Network unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_ERROR" msgid="3372797822292089708">"SIM Network Subset unlock request unsucces sful."</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_ERROR" msgid="1878443146720411381">"SIM Service Provider unlock request unsu ccessful."</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_ERROR" msgid="7664778312218023192">"SIM Corporate unlock request unsuccessful."</string>
<string name="PERSOSUBSTATE_SIM_SIM_ERROR" msgid="2472944311643350302">"SIM unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_ERROR" msgid="828089694480999120">"RUIM network1 unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_ERROR" msgid="17619001007092511">"RUIM network2 unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_ERROR" msgid="807214229604353614">"RUIM HRPD unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_ERROR" msgid="8644184447744175747">"RUIM corporate unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ERROR" msgid="3801002648649640407">"RUIM service provider unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_ERROR" msgid="828089694480999120">"RUIM Network1 unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_ERROR" msgid="17619001007092511">"RUIM Network2 unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_ERROR" msgid="807214229604353614">"RUIM Hrpd unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_ERROR" msgid="8644184447744175747">"RUIM Corporate unlock request unsuccessful."</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_ERROR" msgid="3801002648649640407">"RUIM Service Provider unlock request un successful."</string>
<string name="PERSOSUBSTATE_RUIM_RUIM_ERROR" msgid="707397021218680753">"RUIM unlock request unsuccessful."</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_PUK_ERROR" msgid="894358680773257820">"PUK unlock unsuccessful."</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_ERROR" msgid="352466878146726991">"PUK unlock unsuccessful."</string>
@@ -2238,16 +2238,16 @@
<string name="PERSOSUBSTATE_SIM_ICCID_ERROR" msgid="7559167306794441462">"ICCID unlock request unsuccessful."</string>
<string name="PERSOSUBSTATE_SIM_IMPI_ERROR" msgid="2782926139511136588">"IMPI unlock request unsuccessful."</string>
<string name="PERSOSUBSTATE_SIM_NS_SP_ERROR" msgid="1890493954453456758">"Network subset service provider unlock request unsuccessful."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUCCESS" msgid="4886243367747126325">"SIM network unlock successful."</string>
- <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_SUCCESS" msgid="4053809277733513987">"SIM network subset unlock successful."</string>
- <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_SUCCESS" msgid="8249342930499801740">"SIM service provider unlock successful ."</string>
- <string name="PERSOSUBSTATE_SIM_CORPORATE_SUCCESS" msgid="2339794542560381270">"SIM corporate unlock successful."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUCCESS" msgid="4886243367747126325">"SIM Network unlock successful."</string>
+ <string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_SUCCESS" msgid="4053809277733513987">"SIM Network Subset unlock successful."</string>
+ <string name="PERSOSUBSTATE_SIM_SERVICE_PROVIDER_SUCCESS" msgid="8249342930499801740">"SIM Service Provider unlock successful ."</string>
+ <string name="PERSOSUBSTATE_SIM_CORPORATE_SUCCESS" msgid="2339794542560381270">"SIM Corporate unlock successful."</string>
<string name="PERSOSUBSTATE_SIM_SIM_SUCCESS" msgid="6975608174152828954">"SIM unlock successful."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK1_SUCCESS" msgid="2846699261330463192">"RUIM network1 unlock successful."</string>
- <string name="PERSOSUBSTATE_RUIM_NETWORK2_SUCCESS" msgid="5335414726057102801">"RUIM network2 unlock successful."</string>
- <string name="PERSOSUBSTATE_RUIM_HRPD_SUCCESS" msgid="8868100318474971969">"RUIM HRPD unlock successful."</string>
- <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_SUCCESS" msgid="6020936629725666932">"RUIM service provider unlock successful."</string>
- <string name="PERSOSUBSTATE_RUIM_CORPORATE_SUCCESS" msgid="6944873647584595489">"RUIM corporate unlock successful."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK1_SUCCESS" msgid="2846699261330463192">"RUIM Network1 unlock successful."</string>
+ <string name="PERSOSUBSTATE_RUIM_NETWORK2_SUCCESS" msgid="5335414726057102801">"RUIM Network2 unlock successful."</string>
+ <string name="PERSOSUBSTATE_RUIM_HRPD_SUCCESS" msgid="8868100318474971969">"RUIM Hrpd unlock successful."</string>
+ <string name="PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_SUCCESS" msgid="6020936629725666932">"RUIM Service Provider unlock successf ul."</string>
+ <string name="PERSOSUBSTATE_RUIM_CORPORATE_SUCCESS" msgid="6944873647584595489">"RUIM Corporate unlock successful."</string>
<string name="PERSOSUBSTATE_RUIM_RUIM_SUCCESS" msgid="2526483514124121988">"RUIM unlock successful."</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_PUK_SUCCESS" msgid="7662200333621664621">"PUK unlock successful."</string>
<string name="PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK_SUCCESS" msgid="2861223407953766632">"PUK unlock successful."</string>
@@ -2277,14 +2277,14 @@
<string name="sensor_privacy_start_use_camera_notification_content_title" msgid="7287720213963466672">"Unblock device camera"</string>
<string name="sensor_privacy_start_use_notification_content_text" msgid="7595608891015777346">"For <b><xliff:g id="APP">%s</xliff:g></b> and all apps and services"</string>
<string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"Unblock"</string>
- <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensor privacy"</string>
+ <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"Sensor Privacy"</string>
<string name="splash_screen_view_icon_description" msgid="180638751260598187">"Application icon"</string>
<string name="splash_screen_view_branding_description" msgid="7911129347402728216">"Application branding image"</string>
<string name="view_and_control_notification_title" msgid="4300765399209912240">"Check access settings"</string>
<string name="view_and_control_notification_content" msgid="8003766498562604034">"<xliff:g id="SERVICE_NAME">%s</xliff:g> can view and control your screen. Tap to review."</string>
- <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> translated."</string>
+ <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> Translated."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Message translated from <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> to <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
- <string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Background activity"</string>
+ <string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Background Activity"</string>
<string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"An app is draining battery"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"An app is still active"</string>
<string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> is running in the background. Tap to manage battery usage."</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index e8e5a51..13d4efe 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -447,7 +447,7 @@
<string name="permlab_accessBackgroundLocation" msgid="1721164702777366138">"acceder a la ubicación en segundo plano"</string>
<string name="permdesc_accessBackgroundLocation" msgid="8264885066095638105">"Esta aplicación puede acceder a la ubicación en cualquier momento, aunque no la estés usando."</string>
<string name="permlab_modifyAudioSettings" msgid="6129039778010031815">"cambiar tu configuración de audio"</string>
- <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global, por ejemplo, el volumen y el altavoz de salida."</string>
+ <string name="permdesc_modifyAudioSettings" msgid="8687227609663124921">"Permite que la aplicación modifique la configuración de audio global, por ejemplo, el volumen y la bocina de salida."</string>
<string name="permlab_recordAudio" msgid="1208457423054219147">"grabar audio"</string>
<string name="permdesc_recordAudio" msgid="5857246765327514062">"Esta app puede grabar audio con el micrófono mientras está en uso."</string>
<string name="permlab_recordBackgroundAudio" msgid="5891032812308878254">"grabar audio en segundo plano"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index fcbe2b8..d956e9a 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -91,7 +91,7 @@
<string name="notification_channel_network_alert" msgid="4788053066033851841">"Alertas"</string>
<string name="notification_channel_call_forward" msgid="8230490317314272406">"Desvío de llamadas"</string>
<string name="notification_channel_emergency_callback" msgid="54074839059123159">"Modo de devolución de llamada de emergencia"</string>
- <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Estado de los datos móviles"</string>
+ <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Estado de datos móviles"</string>
<string name="notification_channel_sms" msgid="1243384981025535724">"Mensajes SMS"</string>
<string name="notification_channel_voice_mail" msgid="8457433203106654172">"Mensajes de voz"</string>
<string name="notification_channel_wfc" msgid="9048240466765169038">"Llamada por Wi-Fi"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index b86ce04..40503f5 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -2122,7 +2122,7 @@
<string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Notifications"</string>
<string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Paramètres rapides"</string>
<string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Boîte de dialogue sur l\'alimentation"</string>
- <string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Écran de verrouillage"</string>
+ <string name="accessibility_system_action_lock_screen_label" msgid="5484190691945563838">"Verrouiller l\'écran"</string>
<string name="accessibility_system_action_screenshot_label" msgid="3581566515062741676">"Capture d\'écran"</string>
<string name="accessibility_system_action_headset_hook_label" msgid="8524691721287425468">"Crochet de casque d\'écoute"</string>
<string name="accessibility_system_action_on_screen_a11y_shortcut_label" msgid="8488701469459210309">"Raccourci d\'accessibilité à l\'écran"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index f8e9efb..6a81d24 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -2003,7 +2003,7 @@
<string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Aucune suggestion de saisie automatique"</string>
<string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{1 suggestion de saisie automatique}one{# suggestion de saisie automatique}many{# suggestions de saisie automatique}other{# suggestions de saisie automatique}}"</string>
<string name="autofill_save_title" msgid="7719802414283739775">"Enregistrer dans "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>" ?"</string>
- <string name="autofill_save_title_with_type" msgid="3002460014579799605">"Enregistrer la <xliff:g id="TYPE">%1$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>" ?"</string>
+ <string name="autofill_save_title_with_type" msgid="3002460014579799605">"Enregistrer le <xliff:g id="TYPE">%1$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>" ?"</string>
<string name="autofill_save_title_with_2types" msgid="3783270967447869241">"Enregistrer <xliff:g id="TYPE_0">%1$s</xliff:g> et <xliff:g id="TYPE_1">%2$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%3$s</xliff:g>"</b>" ?"</string>
<string name="autofill_save_title_with_3types" msgid="6598228952100102578">"Enregistrer <xliff:g id="TYPE_0">%1$s</xliff:g>, <xliff:g id="TYPE_1">%2$s</xliff:g> et <xliff:g id="TYPE_2">%3$s</xliff:g> dans "<b>"<xliff:g id="LABEL">%4$s</xliff:g>"</b>" ?"</string>
<string name="autofill_update_title" msgid="3630695947047069136">"Mettre à jour cet élément dans "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>" ?"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 9b68186..57dcc90 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -1949,13 +1949,13 @@
<string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"A configuración de Android TV non está dispoñible"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"A configuración da tableta non está dispoñible"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"A configuración do teléfono non está dispoñible"</string>
- <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo con Android TV."</string>
+ <string name="app_streaming_blocked_message" product="tv" msgid="4003011766528814377">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo Android TV."</string>
<string name="app_streaming_blocked_message" product="tablet" msgid="4242053045964946062">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde a tableta."</string>
<string name="app_streaming_blocked_message" product="default" msgid="6159168735030739398">"Nestes momentos, non podes acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o teléfono."</string>
- <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o dispositivo con Android TV."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o dispositivo Android TV."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Esta aplicación solicita seguranza adicional. Proba a facelo desde a tableta."</string>
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Esta aplicación solicita seguranza adicional. Proba a facelo desde o teléfono."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo con Android TV."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o dispositivo Android TV."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde a tableta."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Non se puido acceder a este contido desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>). Proba a facelo desde o teléfono."</string>
<string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Esta aplicación deseñouse para unha versión anterior de Android e quizais non funcione correctamente. Proba a buscar actualizacións ou contacta co programador."</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index c0d2526..af90807 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1958,7 +1958,7 @@
<string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Questa app richiede maggiore sicurezza. Prova a usare il telefono."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tv" msgid="820334666354451145">"Non è possibile accedere a questa impostazione su <xliff:g id="DEVICE">%1$s</xliff:g>. Prova a usare il dispositivo Android TV."</string>
<string name="app_streaming_blocked_message_for_settings_dialog" product="tablet" msgid="3286849551133045896">"Non è possibile accedere a questa impostazione su <xliff:g id="DEVICE">%1$s</xliff:g>. Prova a usare il tablet."</string>
- <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Non è possibile accedere a questa impostazione su <xliff:g id="DEVICE">%1$s</xliff:g>. Prova a usare il telefono."</string>
+ <string name="app_streaming_blocked_message_for_settings_dialog" product="default" msgid="6264287556598916295">"Non è possibile accedere su <xliff:g id="DEVICE">%1$s</xliff:g>. Prova a usare il telefono."</string>
<string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Questa app è stata realizzata per una versione precedente di Android e potrebbe non funzionare correttamente. Prova a verificare la disponibilità di aggiornamenti o contatta lo sviluppatore."</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Cerca aggiornamenti"</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"Hai nuovi messaggi"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index eab1b3e..76f9666 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1156,7 +1156,7 @@
<string name="input_method_ime_switch_button_desc" msgid="2736542240252198501">"Bytt inndatametode"</string>
<string name="low_internal_storage_view_title" msgid="9024241779284783414">"Lite ledig lagringsplass"</string>
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"Enkelte systemfunksjoner fungerer muligens ikke slik de skal"</string>
- <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Det er ikke nok lagringsplass for systemet. Kontrollér at du har 250 MB ledig plass, og start på nytt."</string>
+ <string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"Det er ikke nok lagringsplass for systemet. Kontroller at du har 250 MB ledig plass, og start på nytt."</string>
<string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> kjører"</string>
<string name="app_running_notification_text" msgid="5120815883400228566">"Trykk for å få mer informasjon eller for å stoppe appen."</string>
<string name="ok" msgid="2646370155170753815">"OK"</string>
@@ -1688,7 +1688,7 @@
<string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"AV"</string>
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"Vil du gi <xliff:g id="SERVICE">%1$s</xliff:g> full kontroll over enheten din?"</string>
<string name="accessibility_service_warning_description" msgid="291674995220940133">"Full kontroll er passende for apper som hjelper deg med tilgjengelighetsbehov, men ikke for de fleste apper."</string>
- <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Se og kontrollér skjermen"</string>
+ <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Se og kontroller skjermen"</string>
<string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Den kan lese alt innhold på skjermen og vise innhold over andre apper."</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Se og utfør handlinger"</string>
<string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan spore kommunikasjonen din med en app eller maskinvaresensor og kommunisere med apper på dine vegne."</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 251a53a..6b80aa3 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -260,7 +260,7 @@
<string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Stille modus"</string>
<string name="global_action_silent_mode_on_status" msgid="2371892537738632013">"Geluid is UIT"</string>
<string name="global_action_silent_mode_off_status" msgid="6608006545950920042">"Geluid is AAN"</string>
- <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Vliegtuigmodus"</string>
+ <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Vliegtuigmodus"</string>
<string name="global_actions_airplane_mode_on_status" msgid="5508025516695361936">"Vliegtuigmodus is AAN"</string>
<string name="global_actions_airplane_mode_off_status" msgid="8522219771500505475">"Vliegtuigmodus is UIT"</string>
<string name="global_action_settings" msgid="4671878836947494217">"Instellingen"</string>
@@ -990,7 +990,7 @@
<string name="keyguard_accessibility_user_selector" msgid="1466067610235696600">"Gebruikersselectie"</string>
<string name="keyguard_accessibility_status" msgid="6792745049712397237">"Status"</string>
<string name="keyguard_accessibility_camera" msgid="7862557559464986528">"Camera"</string>
- <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Mediabediening"</string>
+ <string name="keygaurd_accessibility_media_controls" msgid="2267379779900620614">"Mediabediening"</string>
<string name="keyguard_accessibility_widget_reorder_start" msgid="7066213328912939191">"Opnieuw indelen van widget gestart."</string>
<string name="keyguard_accessibility_widget_reorder_end" msgid="1083806817600593490">"Opnieuw indelen van widget beëindigd."</string>
<string name="keyguard_accessibility_widget_deleted" msgid="1509738950119878705">"Widget <xliff:g id="WIDGET_INDEX">%1$s</xliff:g> verwijderd."</string>
@@ -1482,7 +1482,7 @@
<string name="forward_intent_to_work" msgid="3620262405636021151">"U gebruikt deze app in je werkprofiel"</string>
<string name="input_method_binding_label" msgid="1166731601721983656">"Invoermethode"</string>
<string name="sync_binding_label" msgid="469249309424662147">"Synchroniseren"</string>
- <string name="accessibility_binding_label" msgid="1974602776545801715">"Toegankelijkheid"</string>
+ <string name="accessibility_binding_label" msgid="1974602776545801715">"Toegankelijkheid"</string>
<string name="wallpaper_binding_label" msgid="1197440498000786738">"Achtergrond"</string>
<string name="chooser_wallpaper" msgid="3082405680079923708">"Achtergrond wijzigen"</string>
<string name="notification_listener_binding_label" msgid="2702165274471499713">"Listener voor meldingen"</string>
@@ -1986,7 +1986,7 @@
<string name="app_category_news" msgid="1172762719574964544">"Nieuws en tijdschriften"</string>
<string name="app_category_maps" msgid="6395725487922533156">"Maps en navigatie"</string>
<string name="app_category_productivity" msgid="1844422703029557883">"Productiviteit"</string>
- <string name="app_category_accessibility" msgid="6643521607848547683">"Toegankelijkheid"</string>
+ <string name="app_category_accessibility" msgid="6643521607848547683">"Toegankelijkheid"</string>
<string name="device_storage_monitor_notification_channel" msgid="5164244565844470758">"Apparaatopslag"</string>
<string name="adb_debugging_notification_channel_tv" msgid="4764046459631031496">"USB-foutopsporing"</string>
<string name="time_picker_hour_label" msgid="4208590187662336864">"uur"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 26bfeb4..dfb64e2 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -90,7 +90,7 @@
<string name="notification_channel_network_alert" msgid="4788053066033851841">"ଆଲର୍ଟ"</string>
<string name="notification_channel_call_forward" msgid="8230490317314272406">"କଲ୍ ଫରୱାର୍ଡିଂ"</string>
<string name="notification_channel_emergency_callback" msgid="54074839059123159">"ଜରୁରୀକାଳୀନ କଲବ୍ୟାକ୍ ମୋଡ୍"</string>
- <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"ମୋବାଇଲ୍ ଡାଟା ଷ୍ଟାଟସ୍"</string>
+ <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"ମୋବାଇଲ ଡାଟା ଷ୍ଟାଟସ"</string>
<string name="notification_channel_sms" msgid="1243384981025535724">"SMS ମେସେଜ୍"</string>
<string name="notification_channel_voice_mail" msgid="8457433203106654172">"ଭଏସମେଲ୍ ମେସେଜ୍"</string>
<string name="notification_channel_wfc" msgid="9048240466765169038">"ୱାଇ-ଫାଇ କଲିଙ୍ଗ"</string>
@@ -799,30 +799,30 @@
<item msgid="8150904584178569699">"ୱାର୍କ ଫ୍ୟାକ୍ସ"</item>
<item msgid="4537253139152229577">"ହୋମ ଫାକ୍ସ"</item>
<item msgid="6751245029698664340">"ପେଜର୍"</item>
- <item msgid="1692790665884224905">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="1692790665884224905">"ଅନ୍ୟ"</item>
<item msgid="6216981255272016212">"କଷ୍ଟମ୍"</item>
</string-array>
<string-array name="emailAddressTypes">
<item msgid="7786349763648997741">"ହୋମ"</item>
<item msgid="435564470865989199">"ୱାର୍କ"</item>
- <item msgid="4199433197875490373">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="4199433197875490373">"ଅନ୍ୟ"</item>
<item msgid="3233938986670468328">"କଷ୍ଟମ୍"</item>
</string-array>
<string-array name="postalAddressTypes">
<item msgid="3861463339764243038">"ହୋମ"</item>
<item msgid="5472578890164979109">"ୱାର୍କ"</item>
- <item msgid="5718921296646594739">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="5718921296646594739">"ଅନ୍ୟ"</item>
<item msgid="5523122236731783179">"କଷ୍ଟମ୍"</item>
</string-array>
<string-array name="imAddressTypes">
<item msgid="588088543406993772">"ହୋମ"</item>
<item msgid="5503060422020476757">"ୱାର୍କ"</item>
- <item msgid="2530391194653760297">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="2530391194653760297">"ଅନ୍ୟ"</item>
<item msgid="7640927178025203330">"କଷ୍ଟମ୍"</item>
</string-array>
<string-array name="organizationTypes">
<item msgid="6144047813304847762">"ୱାର୍କ"</item>
- <item msgid="7402720230065674193">"ଅନ୍ୟାନ୍ୟ"</item>
+ <item msgid="7402720230065674193">"ଅନ୍ୟ"</item>
<item msgid="808230403067569648">"କଷ୍ଟମ୍"</item>
</string-array>
<string-array name="imProtocols">
@@ -842,7 +842,7 @@
<string name="phoneTypeFaxWork" msgid="6757519896109439123">"ୱାର୍କ ଫାକ୍ସ"</string>
<string name="phoneTypeFaxHome" msgid="6678559953115904345">"ହୋମ ଫାକ୍ସ"</string>
<string name="phoneTypePager" msgid="576402072263522767">"ପେଜର୍"</string>
- <string name="phoneTypeOther" msgid="6918196243648754715">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="phoneTypeOther" msgid="6918196243648754715">"ଅନ୍ୟ"</string>
<string name="phoneTypeCallback" msgid="3455781500844157767">"କଲବ୍ୟାକ୍"</string>
<string name="phoneTypeCar" msgid="4604775148963129195">"କାର୍"</string>
<string name="phoneTypeCompanyMain" msgid="4482773154536455441">"କମ୍ପାନୀର ମୁଖ୍ୟ"</string>
@@ -863,16 +863,16 @@
<string name="emailTypeCustom" msgid="1809435350482181786">"କଷ୍ଟମ୍"</string>
<string name="emailTypeHome" msgid="1597116303154775999">"ହୋମ"</string>
<string name="emailTypeWork" msgid="2020095414401882111">"ୱାର୍କ"</string>
- <string name="emailTypeOther" msgid="5131130857030897465">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="emailTypeOther" msgid="5131130857030897465">"ଅନ୍ୟ"</string>
<string name="emailTypeMobile" msgid="787155077375364230">"ମୋବାଇଲ୍"</string>
<string name="postalTypeCustom" msgid="5645590470242939129">"କଷ୍ଟମ୍"</string>
<string name="postalTypeHome" msgid="7562272480949727912">"ହୋମ"</string>
<string name="postalTypeWork" msgid="8553425424652012826">"ୱାର୍କ"</string>
- <string name="postalTypeOther" msgid="7094245413678857420">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="postalTypeOther" msgid="7094245413678857420">"ଅନ୍ୟ"</string>
<string name="imTypeCustom" msgid="5653384545085765570">"କଷ୍ଟମ୍"</string>
<string name="imTypeHome" msgid="6996507981044278216">"ହୋମ"</string>
<string name="imTypeWork" msgid="2099668940169903123">"ୱାର୍କ"</string>
- <string name="imTypeOther" msgid="8068447383276219810">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="imTypeOther" msgid="8068447383276219810">"ଅନ୍ୟ"</string>
<string name="imProtocolCustom" msgid="4437878287653764692">"କଷ୍ଟମ୍"</string>
<string name="imProtocolAim" msgid="4050198236506604378">"AIM"</string>
<string name="imProtocolMsn" msgid="2257148557766499232">"Windows Live"</string>
@@ -884,7 +884,7 @@
<string name="imProtocolJabber" msgid="7919269388889582015">"Jabber"</string>
<string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string>
<string name="orgTypeWork" msgid="8684458700669564172">"ୱାର୍କ"</string>
- <string name="orgTypeOther" msgid="5450675258408005553">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="orgTypeOther" msgid="5450675258408005553">"ଅନ୍ୟ"</string>
<string name="orgTypeCustom" msgid="1126322047677329218">"କଷ୍ଟମ୍"</string>
<string name="relationTypeCustom" msgid="282938315217441351">"କଷ୍ଟମ୍"</string>
<string name="relationTypeAssistant" msgid="4057605157116589315">"Assistant"</string>
@@ -904,7 +904,7 @@
<string name="sipAddressTypeCustom" msgid="6283889809842649336">"କଷ୍ଟମ୍"</string>
<string name="sipAddressTypeHome" msgid="5918441930656878367">"ହୋମ"</string>
<string name="sipAddressTypeWork" msgid="7873967986701216770">"ୱାର୍କ"</string>
- <string name="sipAddressTypeOther" msgid="6317012577345187275">"ଅନ୍ୟାନ୍ୟ"</string>
+ <string name="sipAddressTypeOther" msgid="6317012577345187275">"ଅନ୍ୟ"</string>
<string name="quick_contacts_not_available" msgid="1262709196045052223">"ଏହି କଣ୍ଟାକ୍ଟ ଦେଖିବାକୁ କୌଣସି ଆପ୍ଲିକେସନ ମିଳିଲା ନାହିଁ।"</string>
<string name="keyguard_password_enter_pin_code" msgid="6401406801060956153">"PIN କୋଡ୍ ଟାଇପ୍ କରନ୍ତୁ"</string>
<string name="keyguard_password_enter_puk_code" msgid="3112256684547584093">"PUK ଓ ନୂଆ PIN କୋଡ୍ ଟାଇପ୍ କରନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index b61b0fe..0023998 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -1287,7 +1287,7 @@
<string name="volume_icon_description_media" msgid="4997633254078171233">"ਮੀਡੀਆ ਦੀ ਅਵਾਜ਼"</string>
<string name="volume_icon_description_notification" msgid="579091344110747279">"ਸੂਚਨਾ ਵੌਲਿਊਮ"</string>
<string name="ringtone_default" msgid="9118299121288174597">"ਪੂਰਵ-ਨਿਰਧਾਰਤ ਰਿੰਗਟੋਨ"</string>
- <string name="ringtone_default_with_actual" msgid="2709686194556159773">"ਪੂਰਵ-ਨਿਰਧਾਰਤ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
+ <string name="ringtone_default_with_actual" msgid="2709686194556159773">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
<string name="ringtone_silent" msgid="397111123930141876">"ਕੋਈ ਨਹੀਂ"</string>
<string name="ringtone_picker_title" msgid="667342618626068253">"ਰਿੰਗਟੋਨਾਂ"</string>
<string name="ringtone_picker_title_alarm" msgid="7438934548339024767">"ਅਲਾਰਮ ਧੁਨੀਆਂ"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index c2fe6dd..318971d 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -92,7 +92,7 @@
<string name="notification_channel_network_alert" msgid="4788053066033851841">"Оповещения"</string>
<string name="notification_channel_call_forward" msgid="8230490317314272406">"Переадресация вызовов"</string>
<string name="notification_channel_emergency_callback" msgid="54074839059123159">"Режим экстренных обратных вызовов"</string>
- <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Состояние мобильного Интернета"</string>
+ <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"Состояние мобильного интернета"</string>
<string name="notification_channel_sms" msgid="1243384981025535724">"SMS"</string>
<string name="notification_channel_voice_mail" msgid="8457433203106654172">"Голосовые сообщения"</string>
<string name="notification_channel_wfc" msgid="9048240466765169038">"Звонки по Wi-Fi"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 66f0bdd..1d52fc3 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1209,7 +1209,7 @@
<string name="aerr_application_repeated" msgid="7804378743218496566">"<xliff:g id="APPLICATION">%1$s</xliff:g> tiếp tục dừng"</string>
<string name="aerr_process_repeated" msgid="1153152413537954974">"<xliff:g id="PROCESS">%1$s</xliff:g> tiếp tục dừng"</string>
<string name="aerr_restart" msgid="2789618625210505419">"Mở lại ứng dụng"</string>
- <string name="aerr_report" msgid="3095644466849299308">"Gửi phản hồi"</string>
+ <string name="aerr_report" msgid="3095644466849299308">"Gửi ý kiến phản hồi"</string>
<string name="aerr_close" msgid="3398336821267021852">"Đóng"</string>
<string name="aerr_mute" msgid="2304972923480211376">"Tắt tiếng cho đến khi thiết bị khởi động lại"</string>
<string name="aerr_wait" msgid="3198677780474548217">"Đợi"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 07e05c6..0c707fc 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1914,6 +1914,10 @@
-->
<string name="config_defaultCaptivePortalLoginPackageName" translatable="false">com.android.captiveportallogin</string>
+ <!-- The package name of the dock manager app. Must be granted the
+ POST_NOTIFICATIONS permission. -->
+ <string name="config_defaultDockManagerPackageName" translatable="false"></string>
+
<!-- Whether to enable geocoder overlay which allows geocoder to be replaced
by an app at run-time. When disabled, only the
config_geocoderProviderPackageName package will be searched for
@@ -2473,15 +2477,15 @@
<integer name="config_dreamsBatteryLevelDrainCutoff">5</integer>
<!-- Limit of how long the device can remain unlocked due to attention checking. -->
<integer name="config_attentionMaximumExtension">900000</integer> <!-- 15 minutes. -->
- <!-- Is the system user the only user allowed to dream. -->
- <bool name="config_dreamsOnlyEnabledForSystemUser">false</bool>
+ <!-- Whether there is to be a chosen Dock User who is the only user allowed to dream. -->
+ <bool name="config_dreamsOnlyEnabledForDockUser">false</bool>
<!-- Whether dreams are disabled when ambient mode is suppressed. -->
<bool name="config_dreamsDisabledByAmbientModeSuppressionConfig">false</bool>
<!-- The duration in milliseconds of the dream opening animation. -->
<integer name="config_dreamOpenAnimationDuration">250</integer>
<!-- The duration in milliseconds of the dream closing animation. -->
- <integer name="config_dreamCloseAnimationDuration">100</integer>
+ <integer name="config_dreamCloseAnimationDuration">300</integer>
<!-- Whether to dismiss the active dream when an activity is started. Doesn't apply to
assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
@@ -2712,9 +2716,9 @@
will be locked. -->
<bool name="config_multiuserDelayUserDataLocking">false</bool>
- <!-- Whether to automatically switch a non-primary user back to the primary user after a
- timeout when the device is docked. -->
- <bool name="config_enableTimeoutToUserZeroWhenDocked">false</bool>
+ <!-- Whether to automatically switch to the designated Dock User (the user chosen for
+ displaying dreams, etc.) after a timeout when the device is docked. -->
+ <bool name="config_enableTimeoutToDockUserWhenDocked">false</bool>
<!-- Whether to only install system packages on a user if they're allowlisted for that user
type. These are flags and can be freely combined.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8e7da4a..694040a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -466,7 +466,7 @@
<java-symbol type="integer" name="config_multiuserMaximumUsers" />
<java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
<java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
- <java-symbol type="bool" name="config_enableTimeoutToUserZeroWhenDocked" />
+ <java-symbol type="bool" name="config_enableTimeoutToDockUserWhenDocked" />
<java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
<java-symbol type="xml" name="config_user_types" />
<java-symbol type="integer" name="config_safe_media_volume_index" />
@@ -2235,7 +2235,7 @@
<java-symbol type="integer" name="config_dreamsBatteryLevelDrainCutoff" />
<java-symbol type="string" name="config_dreamsDefaultComponent" />
<java-symbol type="bool" name="config_dreamsDisabledByAmbientModeSuppressionConfig" />
- <java-symbol type="bool" name="config_dreamsOnlyEnabledForSystemUser" />
+ <java-symbol type="bool" name="config_dreamsOnlyEnabledForDockUser" />
<java-symbol type="integer" name="config_dreamOpenAnimationDuration" />
<java-symbol type="integer" name="config_dreamCloseAnimationDuration" />
<java-symbol type="array" name="config_supportedDreamComplications" />
@@ -3466,6 +3466,9 @@
<!-- Captive Portal Login -->
<java-symbol type="string" name="config_defaultCaptivePortalLoginPackageName" />
+ <!-- Dock Manager -->
+ <java-symbol type="string" name="config_defaultDockManagerPackageName" />
+
<!-- Optional IPsec algorithms -->
<java-symbol type="array" name="config_optionalIpSecAlgorithms" />
diff --git a/core/tests/coretests/src/android/os/VibratorTest.java b/core/tests/coretests/src/android/os/VibratorTest.java
index 7ebebc9..c59a3f5 100644
--- a/core/tests/coretests/src/android/os/VibratorTest.java
+++ b/core/tests/coretests/src/android/os/VibratorTest.java
@@ -246,10 +246,12 @@
@Test
public void getQFactorAndResonantFrequency_differentValues_returnsNaN() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(1f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
.build();
VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(2f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 2, 2, null))
.build();
@@ -258,6 +260,7 @@
assertTrue(Float.isNaN(info.getQFactor()));
assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ assertEmptyFrequencyProfileAndControl(info);
// One vibrator with values undefined.
VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3).build();
@@ -266,16 +269,19 @@
assertTrue(Float.isNaN(info.getQFactor()));
assertTrue(Float.isNaN(info.getResonantFrequencyHz()));
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getQFactorAndResonantFrequency_sameValues_returnsValue() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(10f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 11, 10, 0.5f, null))
.build();
VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setQFactor(10f)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(
/* resonantFrequencyHz= */ 11, 5, 1, null))
@@ -285,113 +291,131 @@
assertEquals(10f, info.getQFactor(), TEST_TOLERANCE);
assertEquals(11f, info.getResonantFrequencyHz(), TEST_TOLERANCE);
+
+ // No frequency range defined.
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
}
@Test
public void getFrequencyProfile_noVibrator_returnsEmpty() {
VibratorInfo info = new SystemVibrator.NoVibratorInfo();
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_differentResonantFrequencyOrResolutionValues_returnsEmpty() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo differentResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(2, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, differentResonantFrequency});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo differentFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 2,
new float[] { 0, 1 }))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, differentFrequencyResolution});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_missingValues_returnsEmpty() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo missingResonantFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(Float.NaN, 1, 1,
new float[] { 0, 1 }))
.build();
VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingResonantFrequency});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo missingMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, Float.NaN, 1,
new float[] { 0, 1 }))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingMinFrequency});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo missingFrequencyResolution = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, Float.NaN,
new float[] { 0, 1 }))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingFrequencyResolution});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
VibratorInfo missingMaxAmplitudes = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(1, 1, 1, null))
.build();
info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, missingMaxAmplitudes});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_unalignedMaxAmplitudes_returnsEmpty() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
new float[] { 0, 1, 1, 0 }))
.build();
VibratorInfo unalignedMinFrequency = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.1f, 0.5f,
new float[] { 0, 1, 1, 0 }))
.build();
VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
new float[] { 0, 1, 1, 0 }))
.build();
VibratorInfo info = new SystemVibrator.MultiVibratorInfo(
new VibratorInfo[]{firstVibrator, unalignedMinFrequency, thirdVibrator});
- assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEmptyFrequencyProfileAndControl(info);
}
@Test
public void getFrequencyProfile_alignedProfiles_returnsIntersection() {
VibratorInfo firstVibrator = new VibratorInfo.Builder(/* id= */ 1)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10, 0.5f,
new float[] { 0.5f, 1, 1, 0.5f }))
.build();
VibratorInfo secondVibrator = new VibratorInfo.Builder(/* id= */ 2)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
new float[] { 1, 1, 1 }))
.build();
VibratorInfo thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+ .setCapabilities(IVibrator.CAP_FREQUENCY_CONTROL)
.setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
new float[] { 0.8f, 1, 0.8f, 0.5f }))
.build();
@@ -401,6 +425,20 @@
assertEquals(
new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
info.getFrequencyProfile());
+ assertEquals(true, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+
+ // Third vibrator without frequency control capability.
+ thirdVibrator = new VibratorInfo.Builder(/* id= */ 3)
+ .setFrequencyProfile(new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f,
+ new float[] { 0.8f, 1, 0.8f, 0.5f }))
+ .build();
+ info = new SystemVibrator.MultiVibratorInfo(
+ new VibratorInfo[]{firstVibrator, secondVibrator, thirdVibrator});
+
+ assertEquals(
+ new VibratorInfo.FrequencyProfile(11, 10.5f, 0.5f, new float[] { 0.8f, 1, 0.5f }),
+ info.getFrequencyProfile());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
}
@Test
@@ -547,4 +585,12 @@
VibrationAttributes vibrationAttributes = captor.getValue();
assertEquals(new VibrationAttributes.Builder().build(), vibrationAttributes);
}
+
+ /**
+ * Asserts that the frequency profile is empty, and therefore frequency control isn't supported.
+ */
+ void assertEmptyFrequencyProfileAndControl(VibratorInfo info) {
+ assertTrue(info.getFrequencyProfile().isEmpty());
+ assertEquals(false, info.hasCapability(IVibrator.CAP_FREQUENCY_CONTROL));
+ }
}
diff --git a/core/tests/coretests/src/android/util/RotationUtilsTest.java b/core/tests/coretests/src/android/util/RotationUtilsTest.java
index 826eb30..1b1ee4f 100644
--- a/core/tests/coretests/src/android/util/RotationUtilsTest.java
+++ b/core/tests/coretests/src/android/util/RotationUtilsTest.java
@@ -18,6 +18,7 @@
import static android.util.RotationUtils.rotateBounds;
import static android.util.RotationUtils.rotatePoint;
+import static android.util.RotationUtils.rotatePointF;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -25,6 +26,7 @@
import static org.junit.Assert.assertEquals;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -79,4 +81,26 @@
rotatePoint(testResult, ROTATION_270, parentW, parentH);
assertEquals(new Point(560, 60), testResult);
}
+
+ @Test
+ public void testRotatePointF() {
+ float parentW = 1000f;
+ float parentH = 600f;
+ PointF testPt = new PointF(60f, 40f);
+
+ PointF testResult = new PointF(testPt);
+ rotatePointF(testResult, ROTATION_90, parentW, parentH);
+ assertEquals(40f, testResult.x, .1f);
+ assertEquals(940f, testResult.y, .1f);
+
+ testResult.set(testPt.x, testPt.y);
+ rotatePointF(testResult, ROTATION_180, parentW, parentH);
+ assertEquals(940f, testResult.x, .1f);
+ assertEquals(560f, testResult.y, .1f);
+
+ testResult.set(testPt.x, testPt.y);
+ rotatePointF(testResult, ROTATION_270, parentW, parentH);
+ assertEquals(560f, testResult.x, .1f);
+ assertEquals(60f, testResult.y, .1f);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
index 875cd0b..eead4ed 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
@@ -16,9 +16,11 @@
package com.android.internal.app;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
@@ -26,10 +28,12 @@
import android.os.UserHandle;
import android.util.Pair;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.logging.MetricsLogger;
-import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -71,7 +75,10 @@
public boolean isQuietModeEnabled;
public boolean isWorkProfileUserRunning;
public boolean isWorkProfileUserUnlocked;
- public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
+ public Integer myUserId;
+ public QuietModeManager mQuietModeManager;
+ public MyUserIdProvider mMyUserIdProvider;
+ public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public PackageManager packageManager;
public void reset() {
@@ -95,14 +102,9 @@
isQuietModeEnabled = false;
isWorkProfileUserRunning = true;
isWorkProfileUserUnlocked = true;
+ myUserId = null;
packageManager = null;
- multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
- @Override
- public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
- int targetUserId) {
- return hasCrossProfileIntents;
- }
-
+ mQuietModeManager = new QuietModeManager() {
@Override
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
return isQuietModeEnabled;
@@ -113,7 +115,27 @@
UserHandle workProfileUserHandle) {
isQuietModeEnabled = enabled;
}
+
+ @Override
+ public void markWorkProfileEnabledBroadcastReceived() {
+ }
+
+ @Override
+ public boolean isWaitingToEnableWorkProfile() {
+ return false;
+ }
};
+
+ mMyUserIdProvider = new MyUserIdProvider() {
+ @Override
+ public int getMyUserId() {
+ return myUserId != null ? myUserId : UserHandle.myUserId();
+ }
+ };
+
+ mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
+ when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
+ .thenAnswer(invocation -> hasCrossProfileIntents);
}
private ChooserActivityOverrideData() {}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
new file mode 100644
index 0000000..c6537c0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
@@ -0,0 +1,450 @@
+/*
+ * 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.app;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_ACCESS_BLOCKER;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_SHARE_BLOCKER;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_ACCESS_BLOCKER;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_SHARE_BLOCKER;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.Tab.PERSONAL;
+import static com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.Tab.WORK;
+import static com.android.internal.app.ChooserWrapperActivity.sOverrides;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.companion.DeviceFilter;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.internal.R;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.Tab;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@DeviceFilter.MediumType
+@RunWith(Parameterized.class)
+public class ChooserActivityWorkProfileTest {
+
+ private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+ .getInstrumentation().getTargetContext().getUser();
+ private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10);
+
+ @Rule
+ public ActivityTestRule<ChooserWrapperActivity> mActivityRule =
+ new ActivityTestRule<>(ChooserWrapperActivity.class, false,
+ false);
+ private final TestCase mTestCase;
+
+ public ChooserActivityWorkProfileTest(TestCase testCase) {
+ mTestCase = testCase;
+ }
+
+ @Before
+ public void cleanOverrideData() {
+ sOverrides.reset();
+ }
+
+ @Test
+ public void testBlocker() {
+ setUpPersonalAndWorkComponentInfos();
+ sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
+ sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+
+ launchActivity(mTestCase.getIsSendAction());
+ switchToTab(mTestCase.getTab());
+
+ switch (mTestCase.getExpectedBlocker()) {
+ case NO_BLOCKER:
+ assertNoBlockerDisplayed();
+ break;
+ case PERSONAL_PROFILE_SHARE_BLOCKER:
+ assertCantSharePersonalAppsBlockerDisplayed();
+ break;
+ case WORK_PROFILE_SHARE_BLOCKER:
+ assertCantShareWorkAppsBlockerDisplayed();
+ break;
+ case PERSONAL_PROFILE_ACCESS_BLOCKER:
+ assertCantAccessPersonalAppsBlockerDisplayed();
+ break;
+ case WORK_PROFILE_ACCESS_BLOCKER:
+ assertCantAccessWorkAppsBlockerDisplayed();
+ break;
+ }
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection tests() {
+ return Arrays.asList(
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+// TODO(b/256869196) ChooserActivity goes into requestLayout loop
+// new TestCase(
+// /* isSendAction= */ true,
+// /* hasCrossProfileIntents= */ false,
+// /* myUserHandle= */ WORK_USER_HANDLE,
+// /* tab= */ WORK,
+// /* expectedBlocker= */ NO_BLOCKER
+// ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ WORK_PROFILE_SHARE_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+// TODO(b/256869196) ChooserActivity goes into requestLayout loop
+// new TestCase(
+// /* isSendAction= */ true,
+// /* hasCrossProfileIntents= */ false,
+// /* myUserHandle= */ WORK_USER_HANDLE,
+// /* tab= */ PERSONAL,
+// /* expectedBlocker= */ PERSONAL_PROFILE_SHARE_BLOCKER
+// ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ true,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ WORK_PROFILE_ACCESS_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ PERSONAL_PROFILE_ACCESS_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* isSendAction= */ false,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ )
+ );
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+ int numberOfResults, int userId) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(
+ ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ }
+ return infoList;
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ }
+ return infoList;
+ }
+
+ private void setUpPersonalAndWorkComponentInfos() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3,
+ /* userId */ WORK_USER_HANDLE.getIdentifier());
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ }
+
+ private void setupResolverControllers(
+ List<ResolvedComponentInfo> personalResolvedComponentInfos,
+ List<ResolvedComponentInfo> workResolvedComponentInfos) {
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class),
+ eq(UserHandle.SYSTEM)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ }
+
+ private void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private void markWorkProfileUserAvailable() {
+ ChooserWrapperActivity.sOverrides.workProfileUserHandle = WORK_USER_HANDLE;
+ }
+
+ private void assertCantAccessWorkAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_access_work_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertCantAccessPersonalAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_access_personal_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertCantShareWorkAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_share_with_work_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertCantSharePersonalAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_share_with_personal_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertNoBlockerDisplayed() {
+ try {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(not(isDisplayed())));
+ } catch (NoMatchingViewException ignored) {
+ }
+ }
+
+ private void switchToTab(Tab tab) {
+ final int stringId = tab == Tab.WORK ? R.string.resolver_work_tab
+ : R.string.resolver_personal_tab;
+
+ onView(withText(stringId)).perform(click());
+ waitForIdle();
+
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ waitForIdle();
+ }
+
+ private Intent createTextIntent(boolean isSendAction) {
+ Intent sendIntent = new Intent();
+ if (isSendAction) {
+ sendIntent.setAction(Intent.ACTION_SEND);
+ }
+ sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+ sendIntent.setType("text/plain");
+ return sendIntent;
+ }
+
+ private void launchActivity(boolean isSendAction) {
+ Intent sendIntent = createTextIntent(isSendAction);
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Test"));
+ waitForIdle();
+ }
+
+ public static class TestCase {
+ private final boolean mIsSendAction;
+ private final boolean mHasCrossProfileIntents;
+ private final UserHandle mMyUserHandle;
+ private final Tab mTab;
+ private final ExpectedBlocker mExpectedBlocker;
+
+ public enum ExpectedBlocker {
+ NO_BLOCKER,
+ PERSONAL_PROFILE_SHARE_BLOCKER,
+ WORK_PROFILE_SHARE_BLOCKER,
+ PERSONAL_PROFILE_ACCESS_BLOCKER,
+ WORK_PROFILE_ACCESS_BLOCKER
+ }
+
+ public enum Tab {
+ WORK,
+ PERSONAL
+ }
+
+ public TestCase(boolean isSendAction, boolean hasCrossProfileIntents,
+ UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) {
+ mIsSendAction = isSendAction;
+ mHasCrossProfileIntents = hasCrossProfileIntents;
+ mMyUserHandle = myUserHandle;
+ mTab = tab;
+ mExpectedBlocker = expectedBlocker;
+ }
+
+ public boolean getIsSendAction() {
+ return mIsSendAction;
+ }
+
+ public boolean hasCrossProfileIntents() {
+ return mHasCrossProfileIntents;
+ }
+
+ public UserHandle getMyUserHandle() {
+ return mMyUserHandle;
+ }
+
+ public Tab getTab() {
+ return mTab;
+ }
+
+ public ExpectedBlocker getExpectedBlocker() {
+ return mExpectedBlocker;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder("test");
+
+ if (mTab == WORK) {
+ result.append("WorkTab_");
+ } else {
+ result.append("PersonalTab_");
+ }
+
+ if (mIsSendAction) {
+ result.append("sendAction_");
+ } else {
+ result.append("notSendAction_");
+ }
+
+ if (mHasCrossProfileIntents) {
+ result.append("hasCrossProfileIntents_");
+ } else {
+ result.append("doesNotHaveCrossProfileIntents_");
+ }
+
+ if (mMyUserHandle.equals(PERSONAL_USER_HANDLE)) {
+ result.append("myUserIsPersonal_");
+ } else {
+ result.append("myUserIsWork_");
+ }
+
+ if (mExpectedBlocker == ExpectedBlocker.NO_BLOCKER) {
+ result.append("thenNoBlocker");
+ } else if (mExpectedBlocker == PERSONAL_PROFILE_ACCESS_BLOCKER) {
+ result.append("thenAccessBlockerOnPersonalProfile");
+ } else if (mExpectedBlocker == PERSONAL_PROFILE_SHARE_BLOCKER) {
+ result.append("thenShareBlockerOnPersonalProfile");
+ } else if (mExpectedBlocker == WORK_PROFILE_ACCESS_BLOCKER) {
+ result.append("thenAccessBlockerOnWorkProfile");
+ } else if (mExpectedBlocker == WORK_PROFILE_SHARE_BLOCKER) {
+ result.append("thenShareBlockerOnWorkProfile");
+ }
+
+ return result.toString();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 4c3235c..5dc0c8b 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -16,6 +16,10 @@
package com.android.internal.app;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
@@ -34,6 +38,8 @@
import android.util.Pair;
import android.util.Size;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.TargetInfo;
@@ -60,15 +66,6 @@
}
@Override
- protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
- Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
- AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
- super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
- multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
- return multiProfilePagerAdapter;
- }
-
- @Override
public ChooserListAdapter createChooserListAdapter(Context context, List<Intent> payloadIntents,
Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed,
ResolverListController resolverListController) {
@@ -135,6 +132,30 @@
}
@Override
+ protected MyUserIdProvider createMyUserIdProvider() {
+ if (sOverrides.mMyUserIdProvider != null) {
+ return sOverrides.mMyUserIdProvider;
+ }
+ return super.createMyUserIdProvider();
+ }
+
+ @Override
+ protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+ if (sOverrides.mCrossProfileIntentsChecker != null) {
+ return sOverrides.mCrossProfileIntentsChecker;
+ }
+ return super.createCrossProfileIntentsChecker();
+ }
+
+ @Override
+ protected AbstractMultiProfilePagerAdapter.QuietModeManager createQuietModeManager() {
+ if (sOverrides.mQuietModeManager != null) {
+ return sOverrides.mQuietModeManager;
+ }
+ return super.createQuietModeManager();
+ }
+
+ @Override
public void safelyStartActivity(com.android.internal.app.chooser.TargetInfo cti) {
if (sOverrides.onSafelyStartCallback != null &&
sOverrides.onSafelyStartCallback.apply(cti)) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
new file mode 100644
index 0000000..ce68906
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
@@ -0,0 +1,429 @@
+/*
+ * 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.app;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.ExpectedBlocker.NO_BLOCKER;
+import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.ExpectedBlocker.PERSONAL_PROFILE_BLOCKER;
+import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.ExpectedBlocker.WORK_PROFILE_BLOCKER;
+import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.Tab.PERSONAL;
+import static com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.Tab.WORK;
+import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.companion.DeviceFilter;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.espresso.NoMatchingViewException;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.internal.R;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.ResolverActivityWorkProfileTest.TestCase.Tab;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@DeviceFilter.MediumType
+@RunWith(Parameterized.class)
+public class ResolverActivityWorkProfileTest {
+
+ private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+ .getInstrumentation().getTargetContext().getUser();
+ private static final UserHandle WORK_USER_HANDLE = UserHandle.of(10);
+
+ @Rule
+ public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
+ new ActivityTestRule<>(ResolverWrapperActivity.class, false,
+ false);
+ private final TestCase mTestCase;
+
+ public ResolverActivityWorkProfileTest(TestCase testCase) {
+ mTestCase = testCase;
+ }
+
+ @Before
+ public void cleanOverrideData() {
+ sOverrides.reset();
+ }
+
+ @Test
+ public void testBlocker() {
+ setUpPersonalAndWorkComponentInfos();
+ sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
+ sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+
+ launchActivity(/* callingUser= */ mTestCase.getExtraCallingUser());
+ switchToTab(mTestCase.getTab());
+
+ switch (mTestCase.getExpectedBlocker()) {
+ case NO_BLOCKER:
+ assertNoBlockerDisplayed();
+ break;
+ case PERSONAL_PROFILE_BLOCKER:
+ assertCantAccessPersonalAppsBlockerDisplayed();
+ break;
+ case WORK_PROFILE_BLOCKER:
+ assertCantAccessWorkAppsBlockerDisplayed();
+ break;
+ }
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection tests() {
+ return Arrays.asList(
+ new TestCase(
+ /* extraCallingUser= */ null,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ null,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ null,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ null,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ WORK_PROFILE_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ null,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ null,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ PERSONAL_PROFILE_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ null,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ null,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+
+ new TestCase(
+ /* extraCallingUser= */ WORK_USER_HANDLE,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ WORK_USER_HANDLE,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ WORK_USER_HANDLE,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ WORK_USER_HANDLE,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ WORK,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ WORK_USER_HANDLE,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ WORK_USER_HANDLE,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ WORK_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ WORK_USER_HANDLE,
+ /* hasCrossProfileIntents= */ true,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ ),
+ new TestCase(
+ /* extraCallingUser= */ WORK_USER_HANDLE,
+ /* hasCrossProfileIntents= */ false,
+ /* myUserHandle= */ PERSONAL_USER_HANDLE,
+ /* tab= */ PERSONAL,
+ /* expectedBlocker= */ NO_BLOCKER
+ )
+ );
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
+ int numberOfResults, int userId) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(
+ ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ }
+ return infoList;
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ }
+ return infoList;
+ }
+
+ private void setUpPersonalAndWorkComponentInfos() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3,
+ /* userId */ WORK_USER_HANDLE.getIdentifier());
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ }
+
+ private void setupResolverControllers(
+ List<ResolvedComponentInfo> personalResolvedComponentInfos,
+ List<ResolvedComponentInfo> workResolvedComponentInfos) {
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class),
+ eq(UserHandle.SYSTEM)))
+ .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
+ }
+
+ private void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private void markWorkProfileUserAvailable() {
+ ResolverWrapperActivity.sOverrides.workProfileUserHandle = WORK_USER_HANDLE;
+ }
+
+ private void assertCantAccessWorkAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_access_work_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertCantAccessPersonalAppsBlockerDisplayed() {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(isDisplayed()));
+ onView(withText(R.string.resolver_cant_access_personal_apps_explanation))
+ .check(matches(isDisplayed()));
+ }
+
+ private void assertNoBlockerDisplayed() {
+ try {
+ onView(withText(R.string.resolver_cross_profile_blocked))
+ .check(matches(not(isDisplayed())));
+ } catch (NoMatchingViewException ignored) {
+ }
+ }
+
+ private void switchToTab(Tab tab) {
+ final int stringId = tab == Tab.WORK ? R.string.resolver_work_tab
+ : R.string.resolver_personal_tab;
+
+ onView(withText(stringId)).perform(click());
+ waitForIdle();
+
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ waitForIdle();
+ }
+
+ private Intent createSendImageIntent() {
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending");
+ sendIntent.setType("image/jpeg");
+ return sendIntent;
+ }
+
+ private void launchActivity(UserHandle callingUser) {
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+
+ if (callingUser != null) {
+ sendIntent.putExtra(ResolverActivity.EXTRA_CALLING_USER, callingUser);
+ }
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ }
+
+ public static class TestCase {
+ @Nullable
+ private final UserHandle mExtraCallingUser;
+ private final boolean mHasCrossProfileIntents;
+ private final UserHandle mMyUserHandle;
+ private final Tab mTab;
+ private final ExpectedBlocker mExpectedBlocker;
+
+ public enum ExpectedBlocker {
+ NO_BLOCKER,
+ PERSONAL_PROFILE_BLOCKER,
+ WORK_PROFILE_BLOCKER
+ }
+
+ public enum Tab {
+ WORK,
+ PERSONAL
+ }
+
+ public TestCase(@Nullable UserHandle extraCallingUser, boolean hasCrossProfileIntents,
+ UserHandle myUserHandle, Tab tab, ExpectedBlocker expectedBlocker) {
+ mExtraCallingUser = extraCallingUser;
+ mHasCrossProfileIntents = hasCrossProfileIntents;
+ mMyUserHandle = myUserHandle;
+ mTab = tab;
+ mExpectedBlocker = expectedBlocker;
+ }
+
+ @Nullable
+ public UserHandle getExtraCallingUser() {
+ return mExtraCallingUser;
+ }
+
+ public boolean hasCrossProfileIntents() {
+ return mHasCrossProfileIntents;
+ }
+
+ public UserHandle getMyUserHandle() {
+ return mMyUserHandle;
+ }
+
+ public Tab getTab() {
+ return mTab;
+ }
+
+ public ExpectedBlocker getExpectedBlocker() {
+ return mExpectedBlocker;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder("test");
+
+ if (mTab == WORK) {
+ result.append("WorkTab_");
+ } else {
+ result.append("PersonalTab_");
+ }
+
+ if (mExtraCallingUser != null
+ && !mExtraCallingUser.equals(PERSONAL_USER_HANDLE)) {
+ result.append("callingUserIsNonPersonal_");
+ } else {
+ result.append("callingUserIsPersonal_");
+ }
+
+ if (mHasCrossProfileIntents) {
+ result.append("hasCrossProfileIntents_");
+ } else {
+ result.append("doesNotHaveCrossProfileIntents_");
+ }
+
+ if (mMyUserHandle.equals(PERSONAL_USER_HANDLE)) {
+ result.append("myUserIsPersonal_");
+ } else {
+ result.append("myUserIsWork_");
+ }
+
+ if (mExpectedBlocker == ExpectedBlocker.NO_BLOCKER) {
+ result.append("thenNoBlocker");
+ } else if (mExpectedBlocker == ExpectedBlocker.PERSONAL_PROFILE_BLOCKER) {
+ result.append("thenBlockerOnPersonalProfile");
+ } else if (mExpectedBlocker == WORK_PROFILE_BLOCKER) {
+ result.append("thenBlockerOnWorkProfile");
+ }
+
+ return result.toString();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 4cf9c3f..c778dfe 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -16,6 +16,8 @@
package com.android.internal.app;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -27,6 +29,9 @@
import android.os.Bundle;
import android.os.UserHandle;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
+import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.internal.app.chooser.TargetInfo;
import java.util.List;
@@ -52,12 +57,27 @@
}
@Override
- protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
- Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed) {
- AbstractMultiProfilePagerAdapter multiProfilePagerAdapter =
- super.createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
- multiProfilePagerAdapter.setInjector(sOverrides.multiPagerAdapterInjector);
- return multiProfilePagerAdapter;
+ protected MyUserIdProvider createMyUserIdProvider() {
+ if (sOverrides.mMyUserIdProvider != null) {
+ return sOverrides.mMyUserIdProvider;
+ }
+ return super.createMyUserIdProvider();
+ }
+
+ @Override
+ protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
+ if (sOverrides.mCrossProfileIntentsChecker != null) {
+ return sOverrides.mCrossProfileIntentsChecker;
+ }
+ return super.createCrossProfileIntentsChecker();
+ }
+
+ @Override
+ protected QuietModeManager createQuietModeManager() {
+ if (sOverrides.mQuietModeManager != null) {
+ return sOverrides.mQuietModeManager;
+ }
+ return super.createQuietModeManager();
}
ResolverWrapperAdapter getAdapter() {
@@ -137,9 +157,12 @@
public ResolverListController workResolverListController;
public Boolean isVoiceInteraction;
public UserHandle workProfileUserHandle;
+ public Integer myUserId;
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
- public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
+ public QuietModeManager mQuietModeManager;
+ public MyUserIdProvider mMyUserIdProvider;
+ public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public void reset() {
onSafelyStartCallback = null;
@@ -148,15 +171,11 @@
resolverListController = mock(ResolverListController.class);
workResolverListController = mock(ResolverListController.class);
workProfileUserHandle = null;
+ myUserId = null;
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
- multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
- @Override
- public boolean hasCrossProfileIntents(List<Intent> intents, int sourceUserId,
- int targetUserId) {
- return hasCrossProfileIntents;
- }
+ mQuietModeManager = new QuietModeManager() {
@Override
public boolean isQuietModeEnabled(UserHandle workProfileUserHandle) {
return isQuietModeEnabled;
@@ -167,7 +186,27 @@
UserHandle workProfileUserHandle) {
isQuietModeEnabled = enabled;
}
+
+ @Override
+ public void markWorkProfileEnabledBroadcastReceived() {
+ }
+
+ @Override
+ public boolean isWaitingToEnableWorkProfile() {
+ return false;
+ }
};
+
+ mMyUserIdProvider = new MyUserIdProvider() {
+ @Override
+ public int getMyUserId() {
+ return myUserId != null ? myUserId : UserHandle.myUserId();
+ }
+ };
+
+ mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
+ when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
+ .thenAnswer(invocation -> hasCrossProfileIntents);
}
}
}
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 9e39e13..3e3c77b 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -377,6 +377,11 @@
notifyDeviceStateInfoChanged();
}
+ // No-op in the test since DeviceStateManagerGlobal just calls into the system server with
+ // no business logic around it.
+ @Override
+ public void onStateRequestOverlayDismissed(boolean shouldCancelMode) {}
+
public void setSupportedStates(int[] states) {
mSupportedStates = states;
notifyDeviceStateInfoChanged();
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index abf7e99..42c892a 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -1667,6 +1667,9 @@
* effectively treating them as zeros. In API level {@value Build.VERSION_CODES#P} and above
* these parameters will be respected.
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param bitmap The bitmap to draw using the mesh
* @param meshWidth The number of columns in the mesh. Nothing is drawn if this is 0
* @param meshHeight The number of rows in the mesh. Nothing is drawn if this is 0
@@ -1678,7 +1681,7 @@
* null, there must be at least (meshWidth+1) * (meshHeight+1) + colorOffset values
* in the array.
* @param colorOffset Number of color elements to skip before drawing
- * @param paint May be null. The paint used to draw the bitmap
+ * @param paint May be null. The paint used to draw the bitmap. Antialiasing is not supported.
*/
public void drawBitmapMesh(@NonNull Bitmap bitmap, int meshWidth, int meshHeight,
@NonNull float[] verts, int vertOffset, @Nullable int[] colors, int colorOffset,
@@ -1832,9 +1835,12 @@
/**
* Draws the specified bitmap as an N-patch (most often, a 9-patch.)
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param patch The ninepatch object to render
* @param dst The destination rectangle.
- * @param paint The paint to draw the bitmap with. may be null
+ * @param paint The paint to draw the bitmap with. May be null. Antialiasing is not supported.
*/
public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
super.drawPatch(patch, dst, paint);
@@ -1843,9 +1849,12 @@
/**
* Draws the specified bitmap as an N-patch (most often, a 9-patch.)
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param patch The ninepatch object to render
* @param dst The destination rectangle.
- * @param paint The paint to draw the bitmap with. may be null
+ * @param paint The paint to draw the bitmap with. May be null. Antialiasing is not supported.
*/
public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
super.drawPatch(patch, dst, paint);
@@ -2278,6 +2287,9 @@
* array is optional, but if it is present, then it is used to specify the index of each
* triangle, rather than just walking through the arrays in order.
*
+ * <p>Note: antialiasing is not supported, therefore {@link Paint#ANTI_ALIAS_FLAG} is
+ * ignored.</p>
+ *
* @param mode How to interpret the array of vertices
* @param vertexCount The number of values in the vertices array (and corresponding texs and
* colors arrays if non-null). Each logical vertex is two values (x, y), vertexCount
@@ -2292,8 +2304,9 @@
* @param colorOffset Number of values in colors to skip before drawing.
* @param indices If not null, array of indices to reference into the vertex (texs, colors)
* array.
- * @param indexCount number of entries in the indices array (if not null).
- * @param paint Specifies the shader to use if the texs array is non-null.
+ * @param indexCount Number of entries in the indices array (if not null).
+ * @param paint Specifies the shader to use if the texs array is non-null. Antialiasing is not
+ * supported.
*/
public void drawVertices(@NonNull VertexMode mode, int vertexCount, @NonNull float[] verts,
int vertOffset, @Nullable float[] texs, int texOffset, @Nullable int[] colors,
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 451b99e..f438a03 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -137,6 +137,13 @@
* <p>Enabling this flag will cause all draw operations that support
* antialiasing to use it.</p>
*
+ * <p>Notable draw operations that do <b>not</b> support antialiasing include:</p>
+ * <ul>
+ * <li>{@link android.graphics.Canvas#drawBitmapMesh}</li>
+ * <li>{@link android.graphics.Canvas#drawPatch}</li>
+ * <li>{@link android.graphics.Canvas#drawVertices}</li>
+ * </ul>
+ *
* @see #Paint(int)
* @see #setFlags(int)
*/
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 74303e2..d7d43aa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -18,6 +18,12 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary;
+
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
@@ -31,7 +37,6 @@
import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction;
-import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -79,26 +84,20 @@
@Override
public void unregisterOrganizer() {
if (mAnimationController != null) {
- mAnimationController.unregisterAllRemoteAnimations();
+ mAnimationController.unregisterRemoteAnimations();
mAnimationController = null;
}
super.unregisterOrganizer();
}
- /** Overrides the animation if the transition is on the given Task. */
- void startOverrideSplitAnimation(int taskId) {
+ /**
+ * Overrides the animation for transitions of embedded activities organized by this organizer.
+ */
+ void overrideSplitAnimation() {
if (mAnimationController == null) {
mAnimationController = new TaskFragmentAnimationController(this);
}
- mAnimationController.registerRemoteAnimations(taskId);
- }
-
- /** No longer overrides the animation if the transition is on the given Task. */
- @GuardedBy("mLock")
- void stopOverrideSplitAnimation(int taskId) {
- if (mAnimationController != null) {
- mAnimationController.unregisterRemoteAnimations(taskId);
- }
+ mAnimationController.registerRemoteAnimations();
}
/**
@@ -140,6 +139,8 @@
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule);
+ setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule,
+ false /* isStacked */);
}
/**
@@ -215,6 +216,28 @@
wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
}
+ void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule,
+ boolean isStacked) {
+ final boolean finishPrimaryWithSecondary;
+ if (isStacked) {
+ finishPrimaryWithSecondary = shouldFinishAssociatedContainerWhenStacked(
+ getFinishPrimaryWithSecondaryBehavior(splitRule));
+ } else {
+ finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule);
+ }
+ wct.setCompanionTaskFragment(primary, finishPrimaryWithSecondary ? secondary : null);
+
+ final boolean finishSecondaryWithPrimary;
+ if (isStacked) {
+ finishSecondaryWithPrimary = shouldFinishAssociatedContainerWhenStacked(
+ getFinishSecondaryWithPrimaryBehavior(splitRule));
+ } else {
+ finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule);
+ }
+ wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
+ }
+
TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
if (mFragmentInfos.containsKey(fragmentToken)) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 16760e26..4df2d7d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -41,7 +41,6 @@
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
-import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.app.Activity;
@@ -193,7 +192,6 @@
continue;
}
updateContainersInTask(wct, taskContainer);
- updateAnimationOverride(taskContainer);
}
// The WCT should be applied and merged to the device state change transition if
// there is one.
@@ -208,9 +206,6 @@
synchronized (mLock) {
mSplitRules.clear();
mSplitRules.addAll(rules);
- for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- updateAnimationOverride(mTaskContainers.valueAt(i));
- }
}
}
@@ -389,6 +384,10 @@
// launching activity in the Task.
mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+ } else if (taskFragmentInfo.isClearedForReorderActivityToFront()) {
+ // Do not finish the dependents if this TaskFragment was cleared to reorder
+ // the launching Activity to front of the Task.
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (!container.isWaitingActivityAppear()) {
// Do not finish the container before the expected activity appear until
// timeout.
@@ -464,7 +463,6 @@
// parentInfo#isVisibleRequested is true.
return;
}
- onTaskContainerInfoChanged(taskContainer, parentInfo.getConfiguration());
if (isInPictureInPicture(parentInfo.getConfiguration())) {
// No need to update presentation in PIP until the Task exit PIP.
return;
@@ -608,55 +606,12 @@
}
if (taskContainer.isEmpty()) {
// Cleanup the TaskContainer if it becomes empty.
- mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
mTaskContainers.remove(taskContainer.getTaskId());
}
return;
}
}
- @GuardedBy("mLock")
- private void onTaskContainerInfoChanged(@NonNull TaskContainer taskContainer,
- @NonNull Configuration config) {
- final boolean wasInPip = taskContainer.isInPictureInPicture();
- final boolean isInPIp = isInPictureInPicture(config);
-
- // We need to check the animation override when enter/exit PIP or has bounds changed.
- boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
- if (taskContainer.setTaskBounds(config.windowConfiguration.getBounds())
- && !isInPIp) {
- // We don't care the bounds change when it has already entered PIP.
- shouldUpdateAnimationOverride = true;
- }
- if (shouldUpdateAnimationOverride) {
- updateAnimationOverride(taskContainer);
- }
- }
-
- /**
- * Updates if we should override transition animation. We only want to override if the Task
- * bounds is large enough for at least one split rule.
- */
- @GuardedBy("mLock")
- private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
- if (ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/207070762): cleanup with legacy app transition
- // Animation will be handled by WM Shell with Shell transition enabled.
- return;
- }
- if (!taskContainer.isTaskBoundsInitialized()) {
- // We don't know about the Task bounds/windowingMode yet.
- return;
- }
-
- // We only want to override if the TaskContainer may show split.
- if (mayShowSplit(taskContainer)) {
- mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
- } else {
- mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
- }
- }
-
/** Returns whether the given {@link TaskContainer} may show in split. */
// Suppress GuardedBy warning because lint asks to mark this method as
// @GuardedBy(mPresenter.mController.mLock), which is mLock itself
@@ -1272,14 +1227,6 @@
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
pendingAppearedIntent, taskContainer, this);
- if (!taskContainer.isTaskBoundsInitialized()) {
- // Get the initial bounds before the TaskFragment has appeared.
- final Rect taskBounds = getNonEmbeddedActivityBounds(activityInTask);
- if (!taskContainer.setTaskBounds(taskBounds)) {
- Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
- }
- }
- updateAnimationOverride(taskContainer);
return container;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 362f1fa..5395fb2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -139,6 +139,11 @@
super(executor, controller);
mController = controller;
registerOrganizer();
+ if (!SplitController.ENABLE_SHELL_TRANSITIONS) {
+ // TODO(b/207070762): cleanup with legacy app transition
+ // Animation will be handled by WM Shell when Shell transition is enabled.
+ overrideSplitAnimation();
+ }
}
/**
@@ -371,13 +376,16 @@
@NonNull SplitAttributes splitAttributes) {
// Clear adjacent TaskFragments if the container is shown in fullscreen, or the
// secondaryContainer could not be finished.
- if (!shouldShowSplit(splitAttributes)) {
+ boolean isStacked = !shouldShowSplit(splitAttributes);
+ if (isStacked) {
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
null /* secondary */, null /* splitRule */);
} else {
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
secondaryContainer.getTaskFragmentToken(), splitRule);
}
+ setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(),
+ secondaryContainer.getTaskFragmentToken(), splitRule, isStacked);
}
/**
@@ -489,8 +497,15 @@
|| splitContainer.getSecondaryContainer().getInfo() == null) {
return RESULT_EXPAND_FAILED_NO_TF_INFO;
}
- expandTaskFragment(wct, splitContainer.getPrimaryContainer().getTaskFragmentToken());
- expandTaskFragment(wct, splitContainer.getSecondaryContainer().getTaskFragmentToken());
+ final IBinder primaryToken =
+ splitContainer.getPrimaryContainer().getTaskFragmentToken();
+ final IBinder secondaryToken =
+ splitContainer.getSecondaryContainer().getTaskFragmentToken();
+ expandTaskFragment(wct, primaryToken);
+ expandTaskFragment(wct, secondaryToken);
+ // Set the companion TaskFragment when the two containers stacked.
+ setCompanionTaskFragment(wct, primaryToken, secondaryToken,
+ splitContainer.getSplitRule(), true /* isStacked */);
return RESULT_EXPANDED;
}
return RESULT_NOT_EXPANDED;
@@ -917,11 +932,7 @@
if (taskContainer != null) {
return taskContainer.getTaskProperties();
}
- // Use a copy of configuration because activity's configuration may be updated later,
- // or we may get unexpected TaskContainer's configuration if Activity's configuration is
- // updated. An example is Activity is going to be in split.
- return new TaskProperties(activity.getDisplayId(),
- new Configuration(activity.getResources().getConfiguration()));
+ return TaskProperties.getTaskPropertiesFromActivity(activity);
}
@NonNull
@@ -935,16 +946,4 @@
// TODO(b/190433398): Supply correct insets.
return new WindowMetrics(taskBounds, WindowInsets.CONSUMED);
}
-
- /** Obtains the bounds from a non-embedded Activity. */
- @NonNull
- static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) {
- final WindowConfiguration windowConfiguration =
- activity.getResources().getConfiguration().windowConfiguration;
- if (!activity.isInMultiWindowMode()) {
- // In fullscreen mode the max bounds should correspond to the task bounds.
- return windowConfiguration.getMaxBounds();
- }
- return windowConfiguration.getBounds();
- }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 231da05..dba5a7a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -20,14 +20,17 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.inMultiWindowMode;
import android.app.Activity;
+import android.app.ActivityClient;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
+import android.util.Log;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
@@ -41,14 +44,11 @@
/** Represents TaskFragments and split pairs below a Task. */
class TaskContainer {
+ private static final String TAG = TaskContainer.class.getSimpleName();
/** The unique task id. */
private final int mTaskId;
- // TODO(b/240219484): consolidate to mConfiguration
- /** Available window bounds of this Task. */
- private final Rect mTaskBounds = new Rect();
-
/** Active TaskFragments in this Task. */
@NonNull
final List<TaskFragmentContainer> mContainers = new ArrayList<>();
@@ -86,10 +86,10 @@
throw new IllegalArgumentException("Invalid Task id");
}
mTaskId = taskId;
- // Make a copy in case the activity's config is updated, and updates the TaskContainer's
- // config unexpectedly.
- mConfiguration = new Configuration(activityInTask.getResources().getConfiguration());
- mDisplayId = activityInTask.getDisplayId();
+ final TaskProperties taskProperties = TaskProperties
+ .getTaskPropertiesFromActivity(activityInTask);
+ mConfiguration = taskProperties.getConfiguration();
+ mDisplayId = taskProperties.getDisplayId();
// Note that it is always called when there's a new Activity is started, which implies
// the host task is visible.
mIsVisible = true;
@@ -108,25 +108,6 @@
}
@NonNull
- Rect getTaskBounds() {
- return mTaskBounds;
- }
-
- /** Returns {@code true} if the bounds is changed. */
- boolean setTaskBounds(@NonNull Rect taskBounds) {
- if (!taskBounds.isEmpty() && !mTaskBounds.equals(taskBounds)) {
- mTaskBounds.set(taskBounds);
- return true;
- }
- return false;
- }
-
- /** Whether the Task bounds has been initialized. */
- boolean isTaskBoundsInitialized() {
- return !mTaskBounds.isEmpty();
- }
-
- @NonNull
Configuration getConfiguration() {
// Make a copy in case the config is updated unexpectedly.
return new Configuration(mConfiguration);
@@ -261,5 +242,45 @@
Configuration getConfiguration() {
return mConfiguration;
}
+
+ /**
+ * Obtains the {@link TaskProperties} for the task that the provided {@link Activity} is
+ * associated with.
+ * <p>
+ * Note that for most case, caller should use
+ * {@link SplitPresenter#getTaskProperties(Activity)} instead. This method is used before
+ * the {@code activity} goes into split.
+ * </p><p>
+ * If the {@link Activity} is in fullscreen, override
+ * {@link WindowConfiguration#getBounds()} with {@link WindowConfiguration#getMaxBounds()}
+ * in case the {@link Activity} is letterboxed. Otherwise, get the Task
+ * {@link Configuration} from the server side or use {@link Activity}'s
+ * {@link Configuration} as a fallback if the Task {@link Configuration} cannot be obtained.
+ */
+ @NonNull
+ static TaskProperties getTaskPropertiesFromActivity(@NonNull Activity activity) {
+ final int displayId = activity.getDisplayId();
+ // Use a copy of configuration because activity's configuration may be updated later,
+ // or we may get unexpected TaskContainer's configuration if Activity's configuration is
+ // updated. An example is Activity is going to be in split.
+ final Configuration activityConfig = new Configuration(
+ activity.getResources().getConfiguration());
+ final WindowConfiguration windowConfiguration = activityConfig.windowConfiguration;
+ final int windowingMode = windowConfiguration.getWindowingMode();
+ if (!inMultiWindowMode(windowingMode)) {
+ // Use the max bounds in fullscreen in case the Activity is letterboxed.
+ windowConfiguration.setBounds(windowConfiguration.getMaxBounds());
+ return new TaskProperties(displayId, activityConfig);
+ }
+ final Configuration taskConfig = ActivityClient.getInstance()
+ .getTaskConfiguration(activity.getActivityToken());
+ if (taskConfig == null) {
+ Log.w(TAG, "Could not obtain task configuration for activity:" + activity);
+ // Still report activity config if task config cannot be obtained from the server
+ // side.
+ return new TaskProperties(displayId, activityConfig);
+ }
+ return new TaskProperties(displayId, taskConfig);
+ }
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index ee2e139..d7eb9a0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -18,13 +18,10 @@
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
-import android.util.ArraySet;
import android.util.Log;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
@@ -44,8 +41,7 @@
private final TaskFragmentAnimationRunner mRemoteRunner = new TaskFragmentAnimationRunner();
@VisibleForTesting
final RemoteAnimationDefinition mDefinition;
- /** Task Ids that we have registered for remote animation. */
- private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
+ private boolean mIsRegistered;
TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
mOrganizer = organizer;
@@ -54,39 +50,30 @@
new RemoteAnimationAdapter(mRemoteRunner, 0, 0, true /* changeNeedsSnapshot */);
mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_OPEN, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, animationAdapter);
- mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_CLOSE, animationAdapter);
mDefinition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, animationAdapter);
}
- void registerRemoteAnimations(int taskId) {
+ void registerRemoteAnimations() {
if (DEBUG) {
Log.v(TAG, "registerRemoteAnimations");
}
- if (mRegisterTasks.contains(taskId)) {
+ if (mIsRegistered) {
return;
}
- mOrganizer.registerRemoteAnimations(taskId, mDefinition);
- mRegisterTasks.add(taskId);
+ mOrganizer.registerRemoteAnimations(mDefinition);
+ mIsRegistered = true;
}
- void unregisterRemoteAnimations(int taskId) {
+ void unregisterRemoteAnimations() {
if (DEBUG) {
Log.v(TAG, "unregisterRemoteAnimations");
}
- if (!mRegisterTasks.contains(taskId)) {
+ if (!mIsRegistered) {
return;
}
- mOrganizer.unregisterRemoteAnimations(taskId);
- mRegisterTasks.remove(taskId);
- }
-
- void unregisterAllRemoteAnimations() {
- final ArraySet<Integer> tasks = new ArraySet<>(mRegisterTasks);
- for (int taskId : tasks) {
- unregisterRemoteAnimations(taskId);
- }
+ mOrganizer.unregisterRemoteAnimations();
+ mIsRegistered = false;
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 8c416e8..0e13c59 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -20,11 +20,9 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
-import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
import android.animation.Animator;
@@ -169,11 +167,9 @@
switch (transit) {
case TRANSIT_OLD_ACTIVITY_OPEN:
case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- case TRANSIT_OLD_TASK_OPEN:
return createOpenAnimationAdapters(targets);
case TRANSIT_OLD_ACTIVITY_CLOSE:
case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
- case TRANSIT_OLD_TASK_CLOSE:
return createCloseAnimationAdapters(targets);
case TRANSIT_OLD_TASK_FRAGMENT_CHANGE:
return createChangeAnimationAdapters(targets);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index a7d47ef..13afa49 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -86,13 +86,23 @@
/** Animation for target that is opening in a change transition. */
@NonNull
Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
- final Rect bounds = target.localBounds;
- // The target will be animated in from left or right depends on its position.
- final int startLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+ final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+ final Rect bounds = target.screenSpaceBounds;
+ final int startLeft;
+ final int startTop;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated in from left or right depending on its position.
+ startTop = 0;
+ startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated in from top or bottom depending on its position.
+ startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ startLeft = 0;
+ }
// The position should be 0-based as we will post translate in
// TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+ final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
@@ -103,13 +113,24 @@
/** Animation for target that is closing in a change transition. */
@NonNull
Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
- final Rect bounds = target.localBounds;
- // The target will be animated out to left or right depends on its position.
- final int endLeft = bounds.left == 0 ? -bounds.width() : bounds.width();
+ final Rect parentBounds = target.taskInfo.configuration.windowConfiguration.getBounds();
+ // TODO(b/258126915): we want to keep track of the closing start bounds
+ final Rect bounds = target.screenSpaceBounds;
+ final int endTop;
+ final int endLeft;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated out to left or right depending on its position.
+ endTop = 0;
+ endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated out to top or bottom depending on its position.
+ endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ endLeft = 0;
+ }
// The position should be 0-based as we will post translate in
// TaskFragmentAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+ final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index b516e140..b70b320 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -35,7 +35,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
-import android.window.WindowContext;
+import android.window.WindowProvider;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -71,7 +71,7 @@
private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
- private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
+ private final Map<IBinder, ConfigurationChangeListener> mConfigurationChangeListeners =
new ArrayMap<>();
public WindowLayoutComponentImpl(@NonNull Context context) {
@@ -121,21 +121,21 @@
}
if (!context.isUiContext()) {
throw new IllegalArgumentException("Context must be a UI Context, which should be"
- + " an Activity or a WindowContext");
+ + " an Activity, WindowContext or InputMethodService");
}
mFoldingFeatureProducer.getData((features) -> {
- // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
consumer.accept(newWindowLayout);
});
mWindowLayoutChangeListeners.put(context, consumer);
- if (context instanceof WindowContext) {
+ // TODO(b/258065175) Further extend this to ContextWrappers.
+ if (context instanceof WindowProvider) {
final IBinder windowContextToken = context.getWindowContextToken();
- final WindowContextConfigListener listener =
- new WindowContextConfigListener(windowContextToken);
+ final ConfigurationChangeListener listener =
+ new ConfigurationChangeListener(windowContextToken);
context.registerComponentCallbacks(listener);
- mWindowContextConfigListeners.put(windowContextToken, listener);
+ mConfigurationChangeListeners.put(windowContextToken, listener);
}
}
@@ -150,10 +150,10 @@
if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
continue;
}
- if (context instanceof WindowContext) {
+ if (context instanceof WindowProvider) {
final IBinder token = context.getWindowContextToken();
- context.unregisterComponentCallbacks(mWindowContextConfigListeners.get(token));
- mWindowContextConfigListeners.remove(token);
+ context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token));
+ mConfigurationChangeListeners.remove(token);
}
break;
}
@@ -309,20 +309,21 @@
}
final int windowingMode;
if (context instanceof Activity) {
- windowingMode = ActivityClient.getInstance().getTaskWindowingMode(
+ final Configuration taskConfig = ActivityClient.getInstance().getTaskConfiguration(
context.getActivityToken());
+ if (taskConfig == null) {
+ // If we cannot determine the task configuration for any reason, it is likely that
+ // we won't be able to determine its position correctly as well. DisplayFeatures'
+ // bounds in this case can't be computed correctly, so we should skip.
+ return false;
+ }
+ windowingMode = taskConfig.windowConfiguration.getWindowingMode();
} else {
// TODO(b/242674941): use task windowing mode for window context that associates with
// activity.
windowingMode = context.getResources().getConfiguration().windowConfiguration
.getWindowingMode();
}
- if (windowingMode == -1) {
- // If we cannot determine the task windowing mode for any reason, it is likely that we
- // won't be able to determine its position correctly as well. DisplayFeatures' bounds
- // in this case can't be computed correctly, so we should skip.
- return false;
- }
// It is recommended not to report any display features in multi-window mode, since it
// won't be possible to synchronize the display feature positions with window movement.
return !WindowConfiguration.inMultiWindowMode(windowingMode);
@@ -349,10 +350,10 @@
}
}
- private final class WindowContextConfigListener implements ComponentCallbacks {
+ private final class ConfigurationChangeListener implements ComponentCallbacks {
final IBinder mToken;
- WindowContextConfigListener(IBinder token) {
+ ConfigurationChangeListener(IBinder token) {
mToken = token;
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 40f7a27..92011af 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -169,6 +169,7 @@
new Point(),
false /* isTaskClearedForReuse */,
false /* isTaskFragmentClearedForPip */,
+ false /* isClearedForReorderActivityToFront */,
new Point());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 957a248..31aa09c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -18,7 +18,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -26,10 +25,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import android.content.Intent;
import android.content.res.Configuration;
@@ -85,35 +82,20 @@
@Test
public void testUnregisterOrganizer() {
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
- mOrganizer.startOverrideSplitAnimation(TASK_ID + 1);
+ mOrganizer.overrideSplitAnimation();
mOrganizer.unregisterOrganizer();
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+ verify(mOrganizer).unregisterRemoteAnimations();
}
@Test
- public void testStartOverrideSplitAnimation() {
+ public void testOverrideSplitAnimation() {
assertNull(mOrganizer.mAnimationController);
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
+ mOrganizer.overrideSplitAnimation();
assertNotNull(mOrganizer.mAnimationController);
- verify(mOrganizer).registerRemoteAnimations(TASK_ID,
- mOrganizer.mAnimationController.mDefinition);
- }
-
- @Test
- public void testStopOverrideSplitAnimation() {
- mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
- verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
-
- mOrganizer.startOverrideSplitAnimation(TASK_ID);
- mOrganizer.stopOverrideSplitAnimation(TASK_ID);
-
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+ verify(mOrganizer).registerRemoteAnimations(mOrganizer.mAnimationController.mDefinition);
}
@Test
@@ -144,6 +126,6 @@
mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
false /* isVisible */, new ArrayList<>(), new Point(),
false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */,
- new Point());
+ false /* isClearedForReorderActivityToFront */, new Point());
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 87d0278..8c1b87a 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -261,7 +261,7 @@
assertNotNull(tf);
assertNotNull(taskContainer);
- assertEquals(TASK_BOUNDS, taskContainer.getTaskBounds());
+ assertEquals(TASK_BOUNDS, taskContainer.getConfiguration().windowConfiguration.getBounds());
}
@Test
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index af9c6ba..95328ce 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -23,7 +23,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
import static org.junit.Assert.assertEquals;
@@ -68,28 +67,6 @@
}
@Test
- public void testIsTaskBoundsInitialized() {
- final TaskContainer taskContainer = createTestTaskContainer();
-
- assertFalse(taskContainer.isTaskBoundsInitialized());
-
- taskContainer.setTaskBounds(TASK_BOUNDS);
-
- assertTrue(taskContainer.isTaskBoundsInitialized());
- }
-
- @Test
- public void testSetTaskBounds() {
- final TaskContainer taskContainer = createTestTaskContainer();
-
- assertFalse(taskContainer.setTaskBounds(new Rect()));
-
- assertTrue(taskContainer.setTaskBounds(TASK_BOUNDS));
-
- assertFalse(taskContainer.setTaskBounds(TASK_BOUNDS));
- }
-
- @Test
public void testGetWindowingModeForSplitTaskFragment() {
final TaskContainer taskContainer = createTestTaskContainer();
final Rect splitBounds = new Rect(0, 0, 500, 1000);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
index d31342b..379ea0c 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java
@@ -16,11 +16,8 @@
package androidx.window.extensions.embedding;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import android.platform.test.annotations.Presubmit;
@@ -57,41 +54,31 @@
@Test
public void testRegisterRemoteAnimations() {
- mAnimationController.registerRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
- verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+ verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
- mAnimationController.registerRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
// No extra call if it has been registered.
- verify(mOrganizer).registerRemoteAnimations(TASK_ID, mAnimationController.mDefinition);
+ verify(mOrganizer).registerRemoteAnimations(mAnimationController.mDefinition);
}
@Test
public void testUnregisterRemoteAnimations() {
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.unregisterRemoteAnimations();
// No call if it is not registered.
- verify(mOrganizer, never()).unregisterRemoteAnimations(anyInt());
+ verify(mOrganizer, never()).unregisterRemoteAnimations();
- mAnimationController.registerRemoteAnimations(TASK_ID);
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.registerRemoteAnimations();
+ mAnimationController.unregisterRemoteAnimations();
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
+ verify(mOrganizer).unregisterRemoteAnimations();
- mAnimationController.unregisterRemoteAnimations(TASK_ID);
+ mAnimationController.unregisterRemoteAnimations();
// No extra call if it has been unregistered.
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- }
-
- @Test
- public void testUnregisterAllRemoteAnimations() {
- mAnimationController.registerRemoteAnimations(TASK_ID);
- mAnimationController.registerRemoteAnimations(TASK_ID + 1);
- mAnimationController.unregisterAllRemoteAnimations();
-
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID);
- verify(mOrganizer).unregisterRemoteAnimations(TASK_ID + 1);
+ verify(mOrganizer).unregisterRemoteAnimations();
}
}
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
new file mode 100644
index 0000000..416287d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+<shape android:shape="rectangle"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/white" />
+ <corners android:radius="20dp" />
+</shape>
diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
index d9a140b..582a11c 100644
--- a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
@@ -20,7 +20,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
-android:background="@drawable/decor_caption_title">
+android:background="@drawable/decor_caption_menu_background">
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/fullscreen_button"
diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml
index f328d59..3d50d22 100644
--- a/libs/WindowManager/Shell/res/values-af/strings.xml
+++ b/libs/WindowManager/Shell/res/values-af/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Laat los"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Program sal dalk nie met verdeelde skerm werk nie."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Program steun nie verdeelde skerm nie."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Hierdie app kan net in 1 venster oopgemaak word."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Program sal dalk nie op \'n sekondêre skerm werk nie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Program steun nie begin op sekondêre skerms nie."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skermverdeler"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index f85103e..343e10e 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም።"</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"</string>
<string name="accessibility_divider" msgid="703810061635792791">"የተከፈለ የማያ ገጽ ከፋይ"</string>
@@ -86,14 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ዝጋ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ተመለስ"</string>
<string name="handle_text" msgid="1766582106752184456">"መያዣ"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"ሙሉ ማያ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"የዴስክቶፕ ሁነታ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"የተከፈለ ማያ ገጽ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ተጨማሪ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ተንሳፋፊ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index 5eda65f..bf7b638 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"إظهار"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"قد لا يعمل التطبيق بشكل سليم في وضع \"تقسيم الشاشة\"."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"التطبيق لا يتيح تقسيم الشاشة."</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."</string>
<string name="accessibility_divider" msgid="703810061635792791">"أداة تقسيم الشاشة"</string>
@@ -86,14 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"إغلاق"</string>
<string name="back_button_text" msgid="1469718707134137085">"رجوع"</string>
<string name="handle_text" msgid="1766582106752184456">"مقبض"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"ملء الشاشة"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"وضع سطح المكتب"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"تقسيم الشاشة"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"المزيد"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"نافذة عائمة"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 5a2e6cd..a0213f4 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"দেখুৱাওক"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"এপ্টোৱে বিভাজিত স্ক্ৰীনৰ সৈতে কাম নকৰিব পাৰে।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"এপ্টোৱে বিভাজিত স্ক্ৰীন সমৰ্থন নকৰে।"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই এপ্টো কেৱল ১ খন ৱিণ্ড’ত খুলিব পাৰি।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"গৌণ ডিছপ্লেত এপে সঠিকভাৱে কাম নকৰিব পাৰে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"গৌণ ডিছপ্লেত এপ্ লঞ্চ কৰিব নোৱাৰি।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"স্প্লিট স্ক্ৰীনৰ বিভাজক"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"বন্ধ কৰক"</string>
<string name="back_button_text" msgid="1469718707134137085">"উভতি যাওক"</string>
<string name="handle_text" msgid="1766582106752184456">"হেণ্ডেল"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"সম্পূৰ্ণ স্ক্ৰীন"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ ম’ড"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"বিভাজিত স্ক্ৰীন"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"অধিক"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ওপঙা"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-az/strings.xml b/libs/WindowManager/Shell/res/values-az/strings.xml
index 06539a1..f842bfe 100644
--- a/libs/WindowManager/Shell/res/values-az/strings.xml
+++ b/libs/WindowManager/Shell/res/values-az/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Güvənli məkandan çıxarın"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Tətbiq bölünmüş ekran ilə işləməyə bilər."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Tətbiq ekran bölünməsini dəstəkləmir."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu tətbiq yalnız 1 pəncərədə açıla bilər."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Tətbiq ikinci ekranda işləməyə bilər."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Tətbiq ikinci ekranda başlamağı dəstəkləmir."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcısı"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Bağlayın"</string>
<string name="back_button_text" msgid="1469718707134137085">"Geriyə"</string>
<string name="handle_text" msgid="1766582106752184456">"Hər kəsə açıq istifadəçi adı"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Rejimi"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Ardı"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Üzən pəncərə"</string>
</resources>
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 c91473e..540ae7c 100644
--- a/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-b+sr+Latn/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Uklonite iz tajne memorije"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi sa podeljenim ekranom."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podeljeni ekran."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija može da se otvori samo u jednom prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionisati na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdelnik podeljenog ekrana"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zatvorite"</string>
<string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
<string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Preko celog ekrana"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Režim za računare"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Podeljeni ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Još"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Plutajuće"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-be/strings.xml b/libs/WindowManager/Shell/res/values-be/strings.xml
index 9d0cd77..bea7538 100644
--- a/libs/WindowManager/Shell/res/values-be/strings.xml
+++ b/libs/WindowManager/Shell/res/values-be/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Паказаць"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Праграма можа не працаваць у рэжыме падзеленага экрана."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Праграма не падтрымлівае функцыю дзялення экрана."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Гэту праграму можна адкрыць толькі ў адным акне."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Праграма можа не працаваць на дадатковых экранах."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Праграма не падтрымлівае запуск на дадатковых экранах."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Раздзяляльнік падзеленага экрана"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Закрыць"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"На ўвесь экран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Рэжым працоўнага стала"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Падзяліць экран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Яшчэ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Зрабіць рухомым акном"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index 230f71d..59915e6 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Отмяна на съхраняването"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Приложението може да не работи в режим на разделен екран."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложението не поддържа разделен екран."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Това приложение може да се отвори само в 1 прозорец."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Възможно е приложението да не работи на алтернативни дисплеи."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложението не поддържа използването на алтернативни дисплеи."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделител в режима за разделен екран"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Затваряне"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="1766582106752184456">"Манипулатор"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Цял екран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим за настолни компютри"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Разделяне на екрана"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Още"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плаващо"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 71a2fc2..63c9684 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"আনস্ট্যাস করুন"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"অ্যাপটি স্প্লিট স্ক্রিনে কাজ নাও করতে পারে।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"অ্যাপ্লিকেশান বিভক্ত-স্ক্রিন সমর্থন করে না৷"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"এই অ্যাপটি শুধু ১টি উইন্ডোয় খোলা যেতে পারে।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"সেকেন্ডারি ডিসপ্লেতে অ্যাপটি কাজ নাও করতে পারে।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"সেকেন্ডারি ডিসপ্লেতে অ্যাপ লঞ্চ করা যাবে না।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"বিভক্ত-স্ক্রিন বিভাজক"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"বন্ধ করুন"</string>
<string name="back_button_text" msgid="1469718707134137085">"ফিরে যান"</string>
<string name="handle_text" msgid="1766582106752184456">"হাতল"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"ফুলস্ক্রিন"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ডেস্কটপ মোড"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"স্প্লিট স্ক্রিন"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"আরও"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ফ্লোট"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 29e83b4..b725efe 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vađenje iz stasha"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće raditi na podijeljenom ekranu."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava dijeljenje ekrana."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova aplikacija se može otvoriti samo u 1 prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće raditi na sekundarnom ekranu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim ekranima."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog ekrana"</string>
@@ -86,9 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zatvaranje"</string>
<string name="back_button_text" msgid="1469718707134137085">"Nazad"</string>
<string name="handle_text" msgid="1766582106752184456">"Identifikator"</string>
- <string name="fullscreen_text" msgid="1162316685217676079">"Puni zaslon"</string>
- <string name="desktop_text" msgid="1077633567027630454">"Stolni način rada"</string>
- <string name="split_screen_text" msgid="1396336058129570886">"Razdvojeni zaslon"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Cijeli ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Način rada radne površine"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Podijeljeni ekran"</string>
<string name="more_button_text" msgid="3655388105592893530">"Više"</string>
- <string name="float_button_text" msgid="9221657008391364581">"Plutajući"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Lebdeći"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ca/strings.xml b/libs/WindowManager/Shell/res/values-ca/strings.xml
index 29ce75e..4383916 100644
--- a/libs/WindowManager/Shell/res/values-ca/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ca/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Deixa d\'amagar"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"És possible que l\'aplicació no funcioni amb la pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'aplicació no admet la pantalla dividida."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aquesta aplicació només pot obrir-se en 1 finestra."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"És possible que l\'aplicació no funcioni en una pantalla secundària."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'aplicació no es pot obrir en pantalles secundàries."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalles"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Tanca"</string>
<string name="back_button_text" msgid="1469718707134137085">"Enrere"</string>
<string name="handle_text" msgid="1766582106752184456">"Ansa"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode d\'escriptori"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Més"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotant"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 7c7cf40..e5cb26f 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušit uložení"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikace v režimu rozdělené obrazovky nemusí fungovat."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikace nepodporuje režim rozdělené obrazovky."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tuto aplikaci lze otevřít jen na jednom okně."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikace na sekundárním displeji nemusí fungovat."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikace nepodporuje spuštění na sekundárních displejích."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Čára rozdělující obrazovku"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zavřít"</string>
<string name="back_button_text" msgid="1469718707134137085">"Zpět"</string>
<string name="handle_text" msgid="1766582106752184456">"Úchyt"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Celá obrazovka"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Režim počítače"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Rozdělená obrazovka"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Více"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Plovoucí"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-da/strings.xml b/libs/WindowManager/Shell/res/values-da/strings.xml
index 0f5b701..46f7c69 100644
--- a/libs/WindowManager/Shell/res/values-da/strings.xml
+++ b/libs/WindowManager/Shell/res/values-da/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Vis"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Appen fungerer muligvis ikke i opdelt skærm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen understøtter ikke opdelt skærm."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne app kan kun åbnes i 1 vindue."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer muligvis ikke på sekundære skærme."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke åbnes på sekundære skærme."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Adskiller til opdelt skærm"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Luk"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tilbage"</string>
<string name="handle_text" msgid="1766582106752184456">"Håndtag"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fuld skærm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Computertilstand"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Opdelt skærm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mere"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Svævende"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-de/strings.xml b/libs/WindowManager/Shell/res/values-de/strings.xml
index 10f8836..1269d36 100644
--- a/libs/WindowManager/Shell/res/values-de/strings.xml
+++ b/libs/WindowManager/Shell/res/values-de/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Aus Stash entfernen"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Die App funktioniert unter Umständen im Modus für geteilten Bildschirm nicht."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Das Teilen des Bildschirms wird in dieser App nicht unterstützt."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Diese App kann nur in einem einzigen Fenster geöffnet werden."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Die App funktioniert auf einem sekundären Display möglicherweise nicht."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Die App unterstützt den Start auf sekundären Displays nicht."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bildschirmteiler"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Schließen"</string>
<string name="back_button_text" msgid="1469718707134137085">"Zurück"</string>
<string name="handle_text" msgid="1766582106752184456">"Ziehpunkt"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Vollbild"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Geteilter Bildschirm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mehr"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Frei schwebend"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index 9630352..f8a69ef 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Κατάργηση απόκρυψης"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Η εφαρμογή ενδέχεται να μην λειτουργεί με διαχωρισμό οθόνης."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Η εφαρμογή δεν υποστηρίζει διαχωρισμό οθόνης."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Αυτή η εφαρμογή μπορεί να ανοιχθεί μόνο σε 1 παράθυρο."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Η εφαρμογή ίσως να μην λειτουργήσει σε δευτερεύουσα οθόνη."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Η εφαρμογή δεν υποστηρίζει την εκκίνηση σε δευτερεύουσες οθόνες."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Διαχωριστικό οθόνης"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Κλείσιμο"</string>
<string name="back_button_text" msgid="1469718707134137085">"Πίσω"</string>
<string name="handle_text" msgid="1766582106752184456">"Λαβή"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Πλήρης οθόνη"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Λειτουργία επιφάνειας εργασίας"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Διαχωρισμός οθόνης"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Περισσότερα"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Κινούμενο"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index f714ca2..8e46c3e 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index f714ca2..7cbbf64 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -33,9 +33,10 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
- <string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
+ <string name="accessibility_divider" msgid="703810061635792791">"Split-screen divider"</string>
<string name="accessibility_action_divider_left_full" msgid="1792313656305328536">"Left full screen"</string>
<string name="accessibility_action_divider_left_70" msgid="8859845045360659250">"Left 70%"</string>
<string name="accessibility_action_divider_left_50" msgid="3488317024557521561">"Left 50%"</string>
@@ -64,9 +65,9 @@
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Don’t bubble conversation"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat using bubbles"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it."</string>
- <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Control bubbles at any time"</string>
+ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Control bubbles anytime"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Tap Manage to turn off bubbles from this app"</string>
- <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"OK"</string>
+ <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Got it"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"No recent bubbles"</string>
<string name="bubble_overflow_empty_subtitle" msgid="2627417924958633713">"Recent bubbles and dismissed bubbles will appear here"</string>
<string name="notification_bubble_title" msgid="6082910224488253378">"Bubble"</string>
@@ -81,14 +82,14 @@
<string name="letterbox_education_reposition_text" msgid="4589957299813220661">"Double-tap outside an app to reposition it"</string>
<string name="letterbox_education_got_it" msgid="4057634570866051177">"Got it"</string>
<string name="letterbox_education_expand_button_description" msgid="1729796567101129834">"Expand for more information."</string>
- <string name="maximize_button_text" msgid="1650859196290301963">"Maximise"</string>
- <string name="minimize_button_text" msgid="271592547935841753">"Minimise"</string>
+ <string name="maximize_button_text" msgid="1650859196290301963">"Maximize"</string>
+ <string name="minimize_button_text" msgid="271592547935841753">"Minimize"</string>
<string name="close_button_text" msgid="2913281996024033299">"Close"</string>
<string name="back_button_text" msgid="1469718707134137085">"Back"</string>
<string name="handle_text" msgid="1766582106752184456">"Handle"</string>
- <string name="fullscreen_text" msgid="1162316685217676079">"Full screen"</string>
- <string name="desktop_text" msgid="1077633567027630454">"Desktop mode"</string>
- <string name="split_screen_text" msgid="1396336058129570886">"Split screen"</string>
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
<string name="more_button_text" msgid="3655388105592893530">"More"</string>
<string name="float_button_text" msgid="9221657008391364581">"Float"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
index 839789b..5a811e1 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings_tv.xml
@@ -17,15 +17,15 @@
<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">"Picture-in-picture"</string>
+ <string name="notification_channel_tv_pip" msgid="2576686079160402435">"Picture-in-Picture"</string>
<string name="pip_notification_unknown_title" msgid="2729870284350772311">"(No title program)"</string>
<string name="pip_close" msgid="2955969519031223530">"Close"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"Full screen"</string>
<string name="pip_move" msgid="158770205886688553">"Move"</string>
<string name="pip_expand" msgid="1051966011679297308">"Expand"</string>
<string name="pip_collapse" msgid="3903295106641385962">"Collapse"</string>
- <string name="pip_edu_text" msgid="3672999496647508701">" Double-press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</string>
- <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-picture menu"</string>
+ <string name="pip_edu_text" msgid="3672999496647508701">" Double press "<annotation icon="home_icon">" HOME "</annotation>" for controls"</string>
+ <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"Picture-in-Picture menu."</string>
<string name="a11y_action_pip_move_left" msgid="6612980937817141583">"Move left"</string>
<string name="a11y_action_pip_move_right" msgid="1119409122645529936">"Move right"</string>
<string name="a11y_action_pip_move_up" msgid="98502616918621959">"Move up"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index f714ca2..8e46c3e 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index f714ca2..8e46c3e 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in one window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index 4a9cc93..b2720be 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"App may not work with split-screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App does not support split-screen."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"This app can only be opened in 1 window."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App may not work on a secondary display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App does not support launch on secondary displays."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Split-screen divider"</string>
diff --git a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
index 9a1b9e9..47445a7 100644
--- a/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es-rUS/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Dejar de almacenar de manera segura"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la app no funcione en el modo de pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La app no es compatible con la función de pantalla dividida."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app solo puede estar abierta en 1 ventana."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la app no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La app no puede iniciarse en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
diff --git a/libs/WindowManager/Shell/res/values-es/strings.xml b/libs/WindowManager/Shell/res/values-es/strings.xml
index aedca54..6c45231 100644
--- a/libs/WindowManager/Shell/res/values-es/strings.xml
+++ b/libs/WindowManager/Shell/res/values-es/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"No esconder"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Es posible que la aplicación no funcione con la pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"La aplicación no admite la pantalla dividida."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación solo puede abrirse en una ventana."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Es posible que la aplicación no funcione en una pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"La aplicación no se puede abrir en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Dividir la pantalla"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Cerrar"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
<string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo Escritorio"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Más"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-et/strings.xml b/libs/WindowManager/Shell/res/values-et/strings.xml
index 4cbc733..a8dc08c 100644
--- a/libs/WindowManager/Shell/res/values-et/strings.xml
+++ b/libs/WindowManager/Shell/res/values-et/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Eemalda hoidlast"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Rakendus ei pruugi poolitatud ekraaniga töötada."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Rakendus ei toeta jagatud ekraani."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Selle rakenduse saab avada ainult ühes aknas."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Rakendus ei pruugi teisesel ekraanil töötada."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Rakendus ei toeta teisestel ekraanidel käivitamist."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekraanijagaja"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Sule"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tagasi"</string>
<string name="handle_text" msgid="1766582106752184456">"Käepide"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Täisekraan"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Lauaarvuti režiim"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Jagatud ekraanikuva"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Rohkem"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Hõljuv"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index e3da48a..ae2bfff 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikazioak ez du onartzen pantaila zatitua"</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da abiarazi bigarren mailako pantailatan."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pantaila-zatitzailea"</string>
@@ -86,14 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Itxi"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atzera"</string>
<string name="handle_text" msgid="1766582106752184456">"Kontu-izena"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantaila osoa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Ordenagailuetarako modua"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantaila zatitua"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Gehiago"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Leiho gainerakorra"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings.xml b/libs/WindowManager/Shell/res/values-fa/strings.xml
index 501f366..e7cb5f4 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"لغو مخفیسازی"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن است برنامه با «صفحهٔ دونیمه» کار نکند."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"برنامه از تقسیم صفحه پشتیبانی نمیکند."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"این برنامه فقط در ۱ پنجره میتواند باز شود."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن است برنامه در نمایشگر ثانویه کار نکند."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"برنامه از راهاندازی در نمایشگرهای ثانویه پشتیبانی نمیکند."</string>
<string name="accessibility_divider" msgid="703810061635792791">"تقسیمکننده صفحه"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"بستن"</string>
<string name="back_button_text" msgid="1469718707134137085">"برگشتن"</string>
<string name="handle_text" msgid="1766582106752184456">"دستگیره"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"تمامصفحه"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"حالت رایانه"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"صفحهٔ دونیمه"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"بیشتر"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"شناور"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fi/strings.xml b/libs/WindowManager/Shell/res/values-fi/strings.xml
index c9b95dc..86199f3 100644
--- a/libs/WindowManager/Shell/res/values-fi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fi/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poista turvasäilytyksestä"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Sovellus ei ehkä toimi jaetulla näytöllä."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Sovellus ei tue jaetun näytön tilaa."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Tämän sovelluksen voi avata vain yhdessä ikkunassa."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Sovellus ei ehkä toimi toissijaisella näytöllä."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Sovellus ei tue käynnistämistä toissijaisilla näytöillä."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Näytön jakaja"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Sulje"</string>
<string name="back_button_text" msgid="1469718707134137085">"Takaisin"</string>
<string name="handle_text" msgid="1766582106752184456">"Kahva"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Koko näyttö"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Työpöytätila"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Jaettu näyttö"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Lisää"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Kelluva ikkuna"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
index 79b01de..1f3ac9e 100644
--- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Retirer de la réserve"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'application n\'est pas compatible avec l\'écran partagé."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette application ne peut être ouverte que dans une fenêtre."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
<string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
<string name="handle_text" msgid="1766582106752184456">"Identifiant"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode Bureau"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flottant"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml
index 0456cfb..f1dbb35 100644
--- a/libs/WindowManager/Shell/res/values-fr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-fr/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Il est possible que l\'application ne fonctionne pas en mode Écran partagé."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Application incompatible avec l\'écran partagé."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Cette appli ne peut être ouverte que dans 1 fenêtre."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Il est possible que l\'application ne fonctionne pas sur un écran secondaire."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'application ne peut pas être lancée sur des écrans secondaires."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Séparateur d\'écran partagé"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Fermer"</string>
<string name="back_button_text" msgid="1469718707134137085">"Retour"</string>
<string name="handle_text" msgid="1766582106752184456">"Poignée"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Plein écran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode ordinateur"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Écran partagé"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Plus"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flottante"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gl/strings.xml b/libs/WindowManager/Shell/res/values-gl/strings.xml
index 7c28e4e..6e215a1 100644
--- a/libs/WindowManager/Shell/res/values-gl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gl/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Non esconder"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Pode que a aplicación non funcione coa pantalla dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A aplicación non é compatible coa función de pantalla dividida."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta aplicación só se pode abrir en 1 ventá."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É posible que a aplicación non funcione nunha pantalla secundaria."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A aplicación non se pode iniciar en pantallas secundarias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de pantalla dividida"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Pechar"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atrás"</string>
<string name="handle_text" msgid="1766582106752184456">"Controlador"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pantalla completa"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modo de escritorio"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Pantalla dividida"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Máis"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotante"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index 73b2e41..4bde170 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"બતાવો"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"વિભાજિત-સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ઍપ્લિકેશન સ્ક્રીન-વિભાજનનું સમર્થન કરતી નથી."</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."</string>
<string name="accessibility_divider" msgid="703810061635792791">"સ્પ્લિટ-સ્ક્રીન વિભાજક"</string>
@@ -86,14 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"બંધ કરો"</string>
<string name="back_button_text" msgid="1469718707134137085">"પાછળ"</string>
<string name="handle_text" msgid="1766582106752184456">"હૅન્ડલ"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"પૂર્ણસ્ક્રીન"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ડેસ્કટૉપ મોડ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"સ્ક્રીનને વિભાજિત કરો"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"વધુ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ફ્લોટિંગ વિન્ડો"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index e70b15d..9229fc2 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"दिखाएं"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ऐप्लिकेशन शायद स्प्लिट स्क्रीन मोड में काम न करे."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ऐप विभाजित स्क्रीन का समर्थन नहीं करता है."</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"हो सकता है कि ऐप प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर काम न करे."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"प्राइमरी (मुख्य) डिस्प्ले के अलावा बाकी दूसरे डिस्प्ले पर ऐप लॉन्च नहीं किया जा सकता."</string>
<string name="accessibility_divider" msgid="703810061635792791">"विभाजित स्क्रीन विभाजक"</string>
@@ -86,14 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"बंद करें"</string>
<string name="back_button_text" msgid="1469718707134137085">"वापस जाएं"</string>
<string name="handle_text" msgid="1766582106752184456">"हैंडल"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"फ़ुलस्क्रीन"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन मोड"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ज़्यादा देखें"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"फ़्लोट"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 1d61854..1446e70 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Poništite sakrivanje"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija možda neće funkcionirati s podijeljenim zaslonom."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podržava podijeljeni zaslon."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ova se aplikacija može otvoriti samo u jednom prozoru."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija možda neće funkcionirati na sekundarnom zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podržava pokretanje na sekundarnim zaslonima."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdjelnik podijeljenog zaslona"</string>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index d816c50..221c329 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Félretevés megszüntetése"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Lehet, hogy az alkalmazás nem működik osztott képernyős nézetben."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Az alkalmazás nem támogatja az osztott képernyős nézetet."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ez az alkalmazás csak egy ablakban nyitható meg."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Előfordulhat, hogy az alkalmazás nem működik másodlagos kijelzőn."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Az alkalmazást nem lehet másodlagos kijelzőn elindítani."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Elválasztó az osztott nézetben"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Bezárás"</string>
<string name="back_button_text" msgid="1469718707134137085">"Vissza"</string>
<string name="handle_text" msgid="1766582106752184456">"Fogópont"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Teljes képernyő"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Asztali üzemmód"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Osztott képernyő"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Továbbiak"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Lebegő"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hy/strings.xml b/libs/WindowManager/Shell/res/values-hy/strings.xml
index e9ab5f4..7be9941 100644
--- a/libs/WindowManager/Shell/res/values-hy/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hy/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ցուցադրել"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Հավելվածը չի կարող աշխատել տրոհված էկրանի ռեժիմում։"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Հավելվածը չի աջակցում էկրանի տրոհումը:"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Այս հավելվածը հնարավոր է բացել միայն մեկ պատուհանում։"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Հավելվածը կարող է չաշխատել լրացուցիչ էկրանի վրա"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Հավելվածը չի աջակցում գործարկումը լրացուցիչ էկրանների վրա"</string>
<string name="accessibility_divider" msgid="703810061635792791">"Տրոհված էկրանի բաժանիչ"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Փակել"</string>
<string name="back_button_text" msgid="1469718707134137085">"Հետ"</string>
<string name="handle_text" msgid="1766582106752184456">"Նշիչ"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Լիաէկրան"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Համակարգչի ռեժիմ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Տրոհված էկրան"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Ավելին"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Լողացող պատուհան"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-in/strings.xml b/libs/WindowManager/Shell/res/values-in/strings.xml
index cd983f5..4e760ef 100644
--- a/libs/WindowManager/Shell/res/values-in/strings.xml
+++ b/libs/WindowManager/Shell/res/values-in/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Batalkan stash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikasi mungkin tidak berfungsi dengan layar terpisah."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App tidak mendukung layar terpisah."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplikasi ini hanya dapat dibuka di 1 jendela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikasi mungkin tidak berfungsi pada layar sekunder."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikasi tidak mendukung peluncuran pada layar sekunder."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pembagi layar terpisah"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Tutup"</string>
<string name="back_button_text" msgid="1469718707134137085">"Kembali"</string>
<string name="handle_text" msgid="1766582106752184456">"Tuas"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Layar Penuh"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Mode Desktop"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Layar Terpisah"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Lainnya"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Mengambang"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index 0a20007..50d4ee7 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Taka úr geymslu"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Hugsanlega virkar forritið ekki með skjáskiptingu."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Forritið styður ekki að skjánum sé skipt."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aðeins er hægt að opna þetta forrit í 1 glugga."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Hugsanlegt er að forritið virki ekki á öðrum skjá."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Forrit styður ekki opnun á öðrum skjá."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skjáskipting"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Loka"</string>
<string name="back_button_text" msgid="1469718707134137085">"Til baka"</string>
<string name="handle_text" msgid="1766582106752184456">"Handfang"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Allur skjárinn"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Skjáborðsstilling"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Skjáskipting"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Meira"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Reikult"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 7e232d4..d2595f7 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Annulla accantonamento"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"L\'app potrebbe non funzionare con lo schermo diviso."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"L\'app non supporta la modalità Schermo diviso."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Questa app può essere aperta soltanto in 1 finestra."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"L\'app potrebbe non funzionare su un display secondario."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"L\'app non supporta l\'avvio su display secondari."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Strumento per schermo diviso"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Chiudi"</string>
<string name="back_button_text" msgid="1469718707134137085">"Indietro"</string>
<string name="handle_text" msgid="1766582106752184456">"Handle"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Schermo intero"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modalità desktop"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Schermo diviso"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Altro"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Mobile"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-iw/strings.xml b/libs/WindowManager/Shell/res/values-iw/strings.xml
index 68cf8aa..883596e 100644
--- a/libs/WindowManager/Shell/res/values-iw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-iw/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ביטול ההסתרה הזמנית"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ייתכן שהאפליקציה לא תפעל במסך מפוצל."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"האפליקציה אינה תומכת במסך מפוצל."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ניתן לפתוח את האפליקציה הזו רק בחלון אחד."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ייתכן שהאפליקציה לא תפעל במסך משני."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"האפליקציה אינה תומכת בהפעלה במסכים משניים."</string>
<string name="accessibility_divider" msgid="703810061635792791">"מחלק מסך מפוצל"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"סגירה"</string>
<string name="back_button_text" msgid="1469718707134137085">"חזרה"</string>
<string name="handle_text" msgid="1766582106752184456">"נקודת אחיזה"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"מסך מלא"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ממשק המחשב"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"מסך מפוצל"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"עוד"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"בלונים"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 6f22e3dc..6bb22a2 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"表示"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"アプリは分割画面では動作しないことがあります。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"アプリで分割画面がサポートされていません。"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"このアプリはウィンドウが 1 つの場合のみ開くことができます。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"アプリはセカンダリ ディスプレイでは動作しないことがあります。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"アプリはセカンダリ ディスプレイでの起動に対応していません。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割画面の分割線"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"閉じる"</string>
<string name="back_button_text" msgid="1469718707134137085">"戻る"</string>
<string name="handle_text" msgid="1766582106752184456">"ハンドル"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"全画面表示"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"デスクトップ モード"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分割画面"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"その他"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"フローティング"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 6989dca..6cf7d78 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"გადანახვის გაუქმება"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"აპმა შეიძლება არ იმუშაოს გაყოფილი ეკრანის რეჟიმში."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ეკრანის გაყოფა არ არის მხარდაჭერილი აპის მიერ."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ამ აპის გახსნა შესაძლებელია მხოლოდ 1 ფანჯარაში."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"აპმა შეიძლება არ იმუშაოს მეორეულ ეკრანზე."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"აპს არ გააჩნია მეორეული ეკრანის მხარდაჭერა."</string>
<string name="accessibility_divider" msgid="703810061635792791">"გაყოფილი ეკრანის რეჟიმის გამყოფი"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"დახურვა"</string>
<string name="back_button_text" msgid="1469718707134137085">"უკან"</string>
<string name="handle_text" msgid="1766582106752184456">"იდენტიფიკატორი"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"სრულ ეკრანზე"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"დესკტოპის რეჟიმი"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ეკრანის გაყოფა"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"სხვა"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ფარფატი"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kk/strings.xml b/libs/WindowManager/Shell/res/values-kk/strings.xml
index c83486b..216619a 100644
--- a/libs/WindowManager/Shell/res/values-kk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kk/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Көрсету"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Қолданба экранды бөлу режимінде жұмыс істемеуі мүмкін."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Қодланба бөлінген экранды қолдамайды."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бұл қолданбаны тек 1 терезеден ашуға болады."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Қолданба қосымша дисплейде жұмыс істемеуі мүмкін."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Қолданба қосымша дисплейлерде іске қосуды қолдамайды."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Бөлінген экран бөлгіші"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Жабу"</string>
<string name="back_button_text" msgid="1469718707134137085">"Артқа"</string>
<string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Толық экран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Компьютер режимі"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлу"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Қосымша"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Қалқыма"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index dd25f20..79aca62 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ឈប់លាក់ជាបណ្ដោះអាសន្ន"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"កម្មវិធីអាចនឹងមិនដំណើរការជាមួយមុខងារបំបែកអេក្រង់ទេ។"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"កម្មវិធីមិនគាំទ្រអេក្រង់បំបែកជាពីរទេ"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"កម្មវិធីនេះអាចបើកនៅក្នុងវិនដូតែ 1 ប៉ុណ្ណោះ។"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"កម្មវិធីនេះប្រហែលជាមិនដំណើរការនៅលើអេក្រង់បន្ទាប់បន្សំទេ។"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"កម្មវិធីនេះមិនអាចចាប់ផ្តើមនៅលើអេក្រង់បន្ទាប់បន្សំបានទេ។"</string>
<string name="accessibility_divider" msgid="703810061635792791">"កម្មវិធីចែកអេក្រង់បំបែក"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"បិទ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ថយក្រោយ"</string>
<string name="handle_text" msgid="1766582106752184456">"ឈ្មោះអ្នកប្រើប្រាស់"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"អេក្រង់ពេញ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"មុខងារកុំព្យូទ័រ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"មុខងារបំបែកអេក្រង់"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ច្រើនទៀត"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"អណ្ដែត"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 473cbe1..9e9333e 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ಅನ್ಸ್ಟ್ಯಾಶ್ ಮಾಡಿ"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ವಿಭಜಿಸಿದ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ಆ್ಯಪ್ ಕೆಲಸ ಮಾಡದೇ ಇರಬಹುದು."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ಅಪ್ಲಿಕೇಶನ್ ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ಈ ಆ್ಯಪ್ ಅನ್ನು 1 ವಿಂಡೋದಲ್ಲಿ ಮಾತ್ರ ತೆರೆಯಬಹುದು."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ಸೆಕೆಂಡರಿ ಡಿಸ್ಪ್ಲೇಗಳಲ್ಲಿ ಅಪ್ಲಿಕೇಶನ್ ಕಾರ್ಯ ನಿರ್ವಹಿಸದೇ ಇರಬಹುದು."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ಸೆಕೆಂಡರಿ ಡಿಸ್ಪ್ಲೇಗಳಲ್ಲಿ ಪ್ರಾರಂಭಿಸುವಿಕೆಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"ಸ್ಪ್ಲಿಟ್-ಪರದೆ ಡಿವೈಡರ್"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ಮುಚ್ಚಿರಿ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ಹಿಂದಕ್ಕೆ"</string>
<string name="handle_text" msgid="1766582106752184456">"ಹ್ಯಾಂಡಲ್"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"ಫುಲ್ಸ್ಕ್ರೀನ್"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ಡೆಸ್ಕ್ಟಾಪ್ ಮೋಡ್"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ಇನ್ನಷ್ಟು"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ಫ್ಲೋಟ್"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ko/strings.xml b/libs/WindowManager/Shell/res/values-ko/strings.xml
index 6975095..f9b495a 100644
--- a/libs/WindowManager/Shell/res/values-ko/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ko/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"숨기기 취소"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"앱이 분할 화면에서 작동하지 않을 수 있습니다."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"앱이 화면 분할을 지원하지 않습니다."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"이 앱은 창 1개에서만 열 수 있습니다."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"앱이 보조 디스플레이에서 작동하지 않을 수도 있습니다."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"앱이 보조 디스플레이에서의 실행을 지원하지 않습니다."</string>
<string name="accessibility_divider" msgid="703810061635792791">"화면 분할기"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"닫기"</string>
<string name="back_button_text" msgid="1469718707134137085">"뒤로"</string>
<string name="handle_text" msgid="1766582106752184456">"핸들"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"전체 화면"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"데스크톱 모드"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"화면 분할"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"더보기"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"플로팅"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml
index 88d02a6..a57f9ae 100644
--- a/libs/WindowManager/Shell/res/values-ky/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ky/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Сейфтен чыгаруу"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Колдонмодо экран бөлүнбөшү мүмкүн."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Колдонмодо экран бөлүнбөйт."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Бул колдонмону 1 терезеде гана ачууга болот."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Колдонмо кошумча экранда иштебей коюшу мүмкүн."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Колдонмону кошумча экрандарда иштетүүгө болбойт."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Экранды бөлгүч"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Жабуу"</string>
<string name="back_button_text" msgid="1469718707134137085">"Артка"</string>
<string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Толук экран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Компьютер режими"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Экранды бөлүү"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Дагы"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Калкыма"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 91cd78d..8e42aa3 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ເອົາອອກຈາກບ່ອນເກັບສ່ວນຕົວ"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ແອັບອາດໃຊ້ບໍ່ໄດ້ກັບການແບ່ງໜ້າຈໍ."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ແອັບບໍ່ຮອງຮັບໜ້າຈໍແບບແຍກກັນ."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ແອັບນີ້ສາມາດເປີດໄດ້ໃນ 1 ໜ້າຈໍເທົ່ານັ້ນ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ແອັບອາດບໍ່ສາມາດໃຊ້ໄດ້ໃນໜ້າຈໍທີສອງ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ແອັບບໍ່ຮອງຮັບການເປີດໃນໜ້າຈໍທີສອງ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"ຕົວຂັ້ນການແບ່ງໜ້າຈໍ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 58056fa..dc99690 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Nebeslėpti"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Programa gali neveikti naudojant išskaidyto ekrano režimą."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programoje nepalaikomas skaidytas ekranas."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šią programą galima atidaryti tik viename lange."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Programa gali neveikti antriniame ekrane."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programa nepalaiko paleisties antriniuose ekranuose."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skaidyto ekrano daliklis"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Uždaryti"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atgal"</string>
<string name="handle_text" msgid="1766582106752184456">"Rankenėlė"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Visas ekranas"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Stalinio kompiuterio režimas"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Išskaidyto ekrano režimas"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Daugiau"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Slankusis langas"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lv/strings.xml b/libs/WindowManager/Shell/res/values-lv/strings.xml
index 30d6ef1..bd2eef4 100644
--- a/libs/WindowManager/Shell/res/values-lv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lv/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Rādīt"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Iespējams, lietotne nedarbosies ekrāna sadalīšanas režīmā."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Lietotnē netiek atbalstīta ekrāna sadalīšana."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Šo lietotni var atvērt tikai vienā logā."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Lietotne, iespējams, nedarbosies sekundārajā displejā."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Lietotnē netiek atbalstīta palaišana sekundārajos displejos."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekrāna sadalītājs"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Aizvērt"</string>
<string name="back_button_text" msgid="1469718707134137085">"Atpakaļ"</string>
<string name="handle_text" msgid="1766582106752184456">"Turis"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pilnekrāna režīms"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Darbvirsmas režīms"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Sadalīt ekrānu"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Vairāk"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Peldošs"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mk/strings.xml b/libs/WindowManager/Shell/res/values-mk/strings.xml
index 5125180..d133654 100644
--- a/libs/WindowManager/Shell/res/values-mk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mk/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Прикажете"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Апликацијата може да не работи со поделен екран."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликацијата не поддржува поделен екран."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Апликацијава може да се отвори само во еден прозорец."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликацијата може да не функционира на друг екран."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликацијата не поддржува стартување на други екрани."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделник на поделен екран"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 1367c03..05e28ac 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"അൺസ്റ്റാഷ് ചെയ്യൽ"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"സ്ക്രീൻ വിഭജന മോഡിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"സ്പ്ലിറ്റ്-സ്ക്രീനിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല."</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"രണ്ടാം ഡിസ്പ്ലേയിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"രണ്ടാം ഡിസ്പ്ലേകളിൽ സമാരംഭിക്കുന്നതിനെ ആപ്പ് അനുവദിക്കുന്നില്ല."</string>
<string name="accessibility_divider" msgid="703810061635792791">"സ്പ്ലിറ്റ്-സ്ക്രീൻ ഡിവൈഡർ"</string>
@@ -86,14 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"അടയ്ക്കുക"</string>
<string name="back_button_text" msgid="1469718707134137085">"മടങ്ങുക"</string>
<string name="handle_text" msgid="1766582106752184456">"ഹാൻഡിൽ"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"പൂർണ്ണസ്ക്രീൻ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ഡെസ്ക്ടോപ്പ് മോഡ്"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"സ്ക്രീൻ വിഭജനം"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"കൂടുതൽ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ഫ്ലോട്ട്"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml
index 83cf9a9..264f9a0 100644
--- a/libs/WindowManager/Shell/res/values-mn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mn/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ил гаргах"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Апп хуваагдсан дэлгэц дээр ажиллахгүй байж болзошгүй."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Энэ апп нь дэлгэц хуваах тохиргоог дэмждэггүй."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Энэ аппыг зөвхөн 1 цонхонд нээх боломжтой."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апп хоёрдогч дэлгэцэд ажиллахгүй."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Аппыг хоёрдогч дэлгэцэд эхлүүлэх боломжгүй."</string>
<string name="accessibility_divider" msgid="703810061635792791">"\"Дэлгэц хуваах\" хуваагч"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Хаах"</string>
<string name="back_button_text" msgid="1469718707134137085">"Буцах"</string>
<string name="handle_text" msgid="1766582106752184456">"Бариул"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Бүтэн дэлгэц"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Дэлгэцийн горим"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Дэлгэцийг хуваах"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Бусад"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Хөвөгч"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index 380270b..7a475ed 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्टॅश करा"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"अॅप कदाचित स्प्लिट स्क्रीनसह काम करू शकत नाही."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अॅप स्क्रीन-विभाजनास समर्थन देत नाही."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"हे अॅप फक्त एका विंडोमध्ये उघडले जाऊ शकते."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"दुसऱ्या डिस्प्लेवर अॅप कदाचित चालणार नाही."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"दुसऱ्या डिस्प्लेवर अॅप लाँच होणार नाही."</string>
<string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रीन विभाजक"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"बंद करा"</string>
<string name="back_button_text" msgid="1469718707134137085">"मागे जा"</string>
<string name="handle_text" msgid="1766582106752184456">"हँडल"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"फुलस्क्रीन"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"डेस्कटॉप मोड"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रीन"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"आणखी"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index e1d4947..be1dc24 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Tunjukkan"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Apl mungkin tidak berfungsi dengan skrin pisah."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Apl tidak menyokong skrin pisah."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Apl ini hanya boleh dibuka dalam 1 tetingkap."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Apl mungkin tidak berfungsi pada paparan kedua."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Apl tidak menyokong pelancaran pada paparan kedua."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pembahagi skrin pisah"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index c85fce5..6081a1c 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"မသိုဝှက်ရန်"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ။"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"အက်ပ်သည် မျက်နှာပြင်ခွဲပြရန် ပံ့ပိုးထားခြင်းမရှိပါ။"</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"</string>
<string name="accessibility_divider" msgid="703810061635792791">"မျက်နှာပြင်ခွဲခြမ်း ပိုင်းခြားပေးသည့်စနစ်"</string>
@@ -86,14 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ပိတ်ရန်"</string>
<string name="back_button_text" msgid="1469718707134137085">"နောက်သို့"</string>
<string name="handle_text" msgid="1766582106752184456">"သုံးသူအမည်"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"ဖန်သားပြင်အပြည့်"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ဒက်စ်တော့မုဒ်"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"မျက်နှာပြင် ခွဲ၍ပြသရန်"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ပိုပြပါ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"မျှောရန်"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nb/strings.xml b/libs/WindowManager/Shell/res/values-nb/strings.xml
index 71608c6..3e18b49 100644
--- a/libs/WindowManager/Shell/res/values-nb/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nb/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Avslutt oppbevaring"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Det kan hende at appen ikke fungerer med delt skjerm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen støtter ikke delt skjerm."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denne appen kan bare åpnes i ett vindu."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen fungerer kanskje ikke på en sekundær skjerm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan ikke kjøres på sekundære skjermer."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Skilleelement for delt skjerm"</string>
@@ -64,7 +65,7 @@
<string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Ikke vis samtaler i bobler"</string>
<string name="bubbles_user_education_title" msgid="2112319053732691899">"Chat med bobler"</string>
<string name="bubbles_user_education_description" msgid="4215862563054175407">"Nye samtaler vises som flytende ikoner eller bobler. Trykk for å åpne en boble. Dra for å flytte den."</string>
- <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Kontrollér bobler når som helst"</string>
+ <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Kontroller bobler når som helst"</string>
<string name="bubbles_user_education_manage" msgid="3460756219946517198">"Trykk på Administrer for å slå av bobler for denne appen"</string>
<string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Greit"</string>
<string name="bubble_overflow_empty_title" msgid="2397251267073294968">"Ingen nylige bobler"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Lukk"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tilbake"</string>
<string name="handle_text" msgid="1766582106752184456">"Håndtak"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fullskjerm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Skrivebordmodus"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Delt skjerm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Svevende"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index 28b64c2..4a937cf 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"अनस्ट्यास गर्नुहोस्"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"एप विभाजित स्क्रिनमा काम नगर्न सक्छ।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"अनुप्रयोगले विभाजित-स्क्रिनलाई समर्थन गर्दैन।"</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"यो एपले सहायक प्रदर्शनमा काम नगर्नसक्छ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"अनुप्रयोगले सहायक प्रदर्शनहरूमा लञ्च सुविधालाई समर्थन गर्दैन।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"विभाजित-स्क्रिन छुट्याउने"</string>
@@ -86,14 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"बन्द गर्नुहोस्"</string>
<string name="back_button_text" msgid="1469718707134137085">"पछाडि"</string>
<string name="handle_text" msgid="1766582106752184456">"ह्यान्डल"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"फुल स्क्रिन"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"डेस्कटप मोड"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"स्प्लिट स्क्रिन"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"थप"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"फ्लोट"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-nl/strings.xml b/libs/WindowManager/Shell/res/values-nl/strings.xml
index b0bc07b..b056483 100644
--- a/libs/WindowManager/Shell/res/values-nl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-nl/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Niet meer verbergen"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"De app werkt mogelijk niet met gesplitst scherm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"App biedt geen ondersteuning voor gesplitst scherm."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Deze app kan slechts in 1 venster worden geopend."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"App werkt mogelijk niet op een secundair scherm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"App kan niet op secundaire displays worden gestart."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Scheiding voor gesplitst scherm"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Sluiten"</string>
<string name="back_button_text" msgid="1469718707134137085">"Terug"</string>
<string name="handle_text" msgid="1766582106752184456">"Gebruikersnaam"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Volledig scherm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktopmodus"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Gesplitst scherm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Meer"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Zwevend"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-or/strings.xml b/libs/WindowManager/Shell/res/values-or/strings.xml
index c0a2b81..5fd81f4 100644
--- a/libs/WindowManager/Shell/res/values-or/strings.xml
+++ b/libs/WindowManager/Shell/res/values-or/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ଦେଖାନ୍ତୁ"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରିନରେ ଆପ୍ କାମ କରିନପାରେ।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ଆପ୍ ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରୀନକୁ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ଏହି ଆପକୁ କେବଳ 1ଟି ୱିଣ୍ଡୋରେ ଖୋଲାଯାଇପାରିବ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍ କାମ ନକରିପାରେ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ସେକେଣ୍ଡାରୀ ଡିସପ୍ଲେରେ ଆପ୍ ଲଞ୍ଚ ସପୋର୍ଟ କରେ ନାହିଁ।"</string>
<string name="accessibility_divider" msgid="703810061635792791">"ସ୍ପ୍ଲିଟ୍-ସ୍କ୍ରୀନ ବିଭାଜକ"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ପଛକୁ ଫେରନ୍ତୁ"</string>
<string name="handle_text" msgid="1766582106752184456">"ହେଣ୍ଡେଲ"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"ପୂର୍ଣ୍ଣସ୍କ୍ରିନ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ଡେସ୍କଟପ ମୋଡ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ସ୍ପ୍ଲିଟ ସ୍କ୍ରିନ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ଅଧିକ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ଫ୍ଲୋଟ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 5cdaeaf..ada79d1 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ਅਣਸਟੈਸ਼"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ।"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ਐਪ ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਨੂੰ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ।"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ਇਹ ਐਪ ਸਿਰਫ਼ 1 ਵਿੰਡੋ ਵਿੱਚ ਖੋਲ੍ਹੀ ਜਾ ਸਕਦੀ ਹੈ।"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇ \'ਤੇ ਕੰਮ ਨਾ ਕਰੇ।"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ਐਪ ਸੈਕੰਡਰੀ ਡਿਸਪਲੇਆਂ \'ਤੇ ਲਾਂਚ ਕਰਨ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ"</string>
<string name="accessibility_divider" msgid="703810061635792791">"ਸਪਲਿਟ-ਸਕ੍ਰੀਨ ਡਿਵਾਈਡਰ"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"ਬੰਦ ਕਰੋ"</string>
<string name="back_button_text" msgid="1469718707134137085">"ਪਿੱਛੇ"</string>
<string name="handle_text" msgid="1766582106752184456">"ਹੈਂਡਲ"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"ਪੂਰੀ-ਸਕ੍ਰੀਨ"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ਡੈਸਕਟਾਪ ਮੋਡ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"ਹੋਰ"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ਫ਼ਲੋਟ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index af65b76..a97fd5c 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zabierz ze schowka"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacja może nie działać przy podzielonym ekranie."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacja nie obsługuje dzielonego ekranu."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ta aplikacja może być otwarta tylko w 1 oknie."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacja może nie działać na dodatkowym ekranie."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacja nie obsługuje uruchamiania na dodatkowych ekranach."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Linia dzielenia ekranu"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zamknij"</string>
<string name="back_button_text" msgid="1469718707134137085">"Wstecz"</string>
<string name="handle_text" msgid="1766582106752184456">"Uchwyt"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Pełny ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Tryb pulpitu"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Podzielony ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Więcej"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Pływające"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index cb43953..0a77c64 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 19b1771..e0636d4 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Remover do armazenamento"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"A app pode não funcionar com o ecrã dividido."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"A app não é compatível com o ecrã dividido."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Esta app só pode ser aberta em 1 janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"A app pode não funcionar num ecrã secundário."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"A app não é compatível com o início em ecrãs secundários."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor do ecrã dividido"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index cb43953..0a77c64 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
diff --git a/libs/WindowManager/Shell/res/values-ro/strings.xml b/libs/WindowManager/Shell/res/values-ro/strings.xml
index 68bfda4..9227216 100644
--- a/libs/WindowManager/Shell/res/values-ro/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ro/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Anulează stocarea"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Este posibil ca aplicația să nu funcționeze cu ecranul împărțit."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplicația nu acceptă ecranul împărțit."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Aplicația poate fi deschisă într-o singură fereastră."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Este posibil ca aplicația să nu funcționeze pe un ecran secundar."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplicația nu acceptă lansare pe ecrane secundare."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Separator pentru ecranul împărțit"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Închide"</string>
<string name="back_button_text" msgid="1469718707134137085">"Înapoi"</string>
<string name="handle_text" msgid="1766582106752184456">"Ghidaj"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Ecran complet"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modul desktop"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Ecran împărțit"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mai multe"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Flotantă"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index a0512b8..f5fa1a4 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показать"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"В режиме разделения экрана приложение может работать нестабильно."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Приложение не поддерживает разделение экрана."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Это приложение можно открыть только в одном окне."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Приложение может не работать на дополнительном экране"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Приложение не поддерживает запуск на дополнительных экранах"</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделитель экрана"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Закрыть"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Полноэкранный режим"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим компьютера"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Разделить экран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Ещё"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плавающее окно"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-si/strings.xml b/libs/WindowManager/Shell/res/values-si/strings.xml
index a8d4fe5..53e52f2 100644
--- a/libs/WindowManager/Shell/res/values-si/strings.xml
+++ b/libs/WindowManager/Shell/res/values-si/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"සඟවා තැබීම ඉවත් කරන්න"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"යෙදුම බෙදුම් තිරය සමග ක්රියා නොකළ හැකිය"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"යෙදුම බෙදුණු-තිරය සඳහා සහාය නොදක්වයි."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"මෙම යෙදුම විවෘත කළ හැක්කේ 1 කවුළුවක පමණයි."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"යෙදුම ද්විතියික සංදර්ශකයක ක්රියා නොකළ හැකිය."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"යෙදුම ද්විතීයික සංදර්ශක මත දියත් කිරීම සඳහා සහාය නොදක්වයි."</string>
<string name="accessibility_divider" msgid="703810061635792791">"බෙදුම්-තිර වෙන්කරණය"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"වසන්න"</string>
<string name="back_button_text" msgid="1469718707134137085">"ආපසු"</string>
<string name="handle_text" msgid="1766582106752184456">"හැඬලය"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"පූර්ණ තිරය"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ඩෙස්ක්ටොප් ප්රකාරය"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"බෙදුම් තිරය"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"තව"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"පාවෙන"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sk/strings.xml b/libs/WindowManager/Shell/res/values-sk/strings.xml
index f597113..f004fd4 100644
--- a/libs/WindowManager/Shell/res/values-sk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sk/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Zrušiť skrytie"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikácia nemusí fungovať s rozdelenou obrazovkou."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikácia nepodporuje rozdelenú obrazovku."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Táto aplikácia môže byť otvorená iba v jednom okne."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikácia nemusí fungovať na sekundárnej obrazovke."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikácia nepodporuje spúšťanie na sekundárnych obrazovkách."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Rozdeľovač obrazovky"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 3716f35..c4808059 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Razkrij"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacija morda ne deluje v načinu razdeljenega zaslona."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacija ne podpira načina razdeljenega zaslona."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"To aplikacijo je mogoče odpreti samo v enem oknu."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacija morda ne bo delovala na sekundarnem zaslonu."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacija ne podpira zagona na sekundarnih zaslonih."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Razdelilnik zaslonov"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Zapri"</string>
<string name="back_button_text" msgid="1469718707134137085">"Nazaj"</string>
<string name="handle_text" msgid="1766582106752184456">"Ročica"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Celozaslonsko"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Namizni način"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Razdeljen zaslon"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Več"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Lebdeče"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sq/strings.xml b/libs/WindowManager/Shell/res/values-sq/strings.xml
index b5203fd..b59d4db 100644
--- a/libs/WindowManager/Shell/res/values-sq/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sq/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Mos e fshih"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Aplikacioni mund të mos funksionojë me ekranin e ndarë."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikacioni nuk mbështet ekranin e ndarë."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ky aplikacion mund të hapet vetëm në 1 dritare."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Aplikacioni mund të mos funksionojë në një ekran dytësor."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikacioni nuk mbështet nisjen në ekrane dytësore."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ndarësi i ekranit të ndarë"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Mbyll"</string>
<string name="back_button_text" msgid="1469718707134137085">"Pas"</string>
<string name="handle_text" msgid="1766582106752184456">"Emërtimi"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Ekrani i plotë"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Modaliteti i desktopit"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Ekrani i ndarë"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Më shumë"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Pluskuese"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sr/strings.xml b/libs/WindowManager/Shell/res/values-sr/strings.xml
index 20bb380..78d74d74 100644
--- a/libs/WindowManager/Shell/res/values-sr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sr/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Уклоните из тајне меморије"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Апликација можда неће радити са подељеним екраном."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Апликација не подржава подељени екран."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ова апликација може да се отвори само у једном прозору."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Апликација можда неће функционисати на секундарном екрану."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Апликација не подржава покретање на секундарним екранима."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Разделник подељеног екрана"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Затворите"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="1766582106752184456">"Идентификатор"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Преко целог екрана"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим за рачунаре"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Подељени екран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Још"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плутајуће"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index 9e31581..cda3040 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Återställ stash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Appen kanske inte fungerar med delad skärm."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Appen har inte stöd för delad skärm."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Denna app kan bara vara öppen i ett fönster."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Appen kanske inte fungerar på en sekundär skärm."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Appen kan inte köras på en sekundär skärm."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Avdelare för delad skärm"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Stäng"</string>
<string name="back_button_text" msgid="1469718707134137085">"Tillbaka"</string>
<string name="handle_text" msgid="1766582106752184456">"Handtag"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Helskärm"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Datorläge"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Delad skärm"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Mer"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Svävande"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index 1b7c651..2f73425 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Huenda programu isifanye kazi kwenye skrini inayogawanywa."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programu haiwezi kutumia skrini iliyogawanywa."</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Kitenganishi cha skrini inayogawanywa"</string>
@@ -86,14 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Funga"</string>
<string name="back_button_text" msgid="1469718707134137085">"Rudi nyuma"</string>
<string name="handle_text" msgid="1766582106752184456">"Ncha"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Skrini nzima"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Hali ya Kompyuta ya mezani"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Gawa Skrini"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Zaidi"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Inayoelea"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 4db168e..6c152ce 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"திரைப் பிரிப்பு அம்சத்தில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"திரையைப் பிரிப்பதைப் ஆப்ஸ் ஆதரிக்கவில்லை."</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"இரண்டாம்நிலைத் திரையில் ஆப்ஸ் வேலை செய்யாமல் போகக்கூடும்."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"இரண்டாம்நிலைத் திரைகளில் பயன்பாட்டைத் தொடங்க முடியாது."</string>
<string name="accessibility_divider" msgid="703810061635792791">"திரையைப் பிரிக்கும் பிரிப்பான்"</string>
@@ -86,14 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"மூடும்"</string>
<string name="back_button_text" msgid="1469718707134137085">"பின்செல்லும்"</string>
<string name="handle_text" msgid="1766582106752184456">"ஹேண்டில்"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"முழுத்திரை"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"டெஸ்க்டாப் பயன்முறை"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"திரையைப் பிரிக்கும்"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"கூடுதல் விருப்பத்தேர்வுகள்"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"மிதக்கும் சாளரம்"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index e677268..f0c8be5 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"ఆన్స్టాచ్"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"స్క్రీన్ విభజనతో యాప్ పని చేయకపోవచ్చు."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"యాప్లో స్క్రీన్ విభజనకు మద్దతు లేదు."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ఈ యాప్ను 1 విండోలో మాత్రమే తెరవవచ్చు."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ప్రత్యామ్నాయ డిస్ప్లేలో యాప్ పని చేయకపోవచ్చు."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ప్రత్యామ్నాయ డిస్ప్లేల్లో ప్రారంభానికి యాప్ మద్దతు లేదు."</string>
<string name="accessibility_divider" msgid="703810061635792791">"విభజన స్క్రీన్ విభాగిని"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"మూసివేయండి"</string>
<string name="back_button_text" msgid="1469718707134137085">"వెనుకకు"</string>
<string name="handle_text" msgid="1766582106752184456">"హ్యాండిల్"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"ఫుల్-స్క్రీన్"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"డెస్క్టాప్ మోడ్"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"స్ప్లిట్ స్క్రీన్"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"మరిన్ని"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"ఫ్లోట్"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index d1d0d9f..2437e03 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"เอาออกจากที่เก็บส่วนตัว"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"แอปอาจใช้ไม่ได้กับโหมดแบ่งหน้าจอ"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"แอปไม่สนับสนุนการแยกหน้าจอ"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"แอปนี้เปิดได้ใน 1 หน้าต่างเท่านั้น"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"แอปอาจไม่ทำงานในจอแสดงผลรอง"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"แอปไม่รองรับการเรียกใช้ในจอแสดงผลรอง"</string>
<string name="accessibility_divider" msgid="703810061635792791">"เส้นแบ่งหน้าจอ"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 8395e2a..86ef757 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"I-unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Posibleng hindi gumana ang app sa split screen."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Hindi sinusuportahan ng app ang split-screen."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Sa 1 window lang puwedeng buksan ang app na ito."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Maaaring hindi gumana ang app sa pangalawang display."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Hindi sinusuportahan ng app ang paglulunsad sa mga pangalawang display."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divider ng split-screen"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Isara"</string>
<string name="back_button_text" msgid="1469718707134137085">"Bumalik"</string>
<string name="handle_text" msgid="1766582106752184456">"Handle"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Fullscreen"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Desktop Mode"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Split Screen"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Higit pa"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Float"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tr/strings.xml b/libs/WindowManager/Shell/res/values-tr/strings.xml
index 5516a73..c4060cc 100644
--- a/libs/WindowManager/Shell/res/values-tr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tr/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Depolama"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Uygulama bölünmüş ekranda çalışmayabilir."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uygulama bölünmüş ekranı desteklemiyor."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu uygulama yalnızca 1 pencerede açılabilir."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uygulama ikincil ekranda çalışmayabilir."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uygulama ikincil ekranlarda başlatılmayı desteklemiyor."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bölünmüş ekran ayırıcı"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Kapat"</string>
<string name="back_button_text" msgid="1469718707134137085">"Geri"</string>
<string name="handle_text" msgid="1766582106752184456">"Herkese açık kullanıcı adı"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Tam Ekran"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Masaüstü Modu"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Bölünmüş Ekran"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Daha Fazla"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Havada Süzülen"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 73cb70d..2650b76 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -33,6 +33,8 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показати"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Додаток може не працювати в режимі розділеного екрана."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Додаток не підтримує розділення екрана."</string>
+ <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
+ <skip />
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Додаток не підтримує запуск на додаткових екранах."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Розділювач екрана"</string>
@@ -86,14 +88,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Закрити"</string>
<string name="back_button_text" msgid="1469718707134137085">"Назад"</string>
<string name="handle_text" msgid="1766582106752184456">"Маркер"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"На весь екран"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Режим комп’ютера"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Розділити екран"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Більше"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Плаваюче вікно"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ur/strings.xml b/libs/WindowManager/Shell/res/values-ur/strings.xml
index 842a255..ca6a937 100644
--- a/libs/WindowManager/Shell/res/values-ur/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ur/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"ممکن ہے کہ ایپ اسپلٹ اسکرین کے ساتھ کام نہ کرے۔"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ایپ سپلٹ اسکرین کو سپورٹ نہیں کرتی۔"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"یہ ایپ صرف 1 ونڈو میں کھولی جا سکتی ہے۔"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ممکن ہے ایپ ثانوی ڈسپلے پر کام نہ کرے۔"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ایپ ثانوی ڈسپلیز پر شروعات کا تعاون نہیں کرتی۔"</string>
<string name="accessibility_divider" msgid="703810061635792791">"سپلٹ اسکرین تقسیم کار"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"بند کریں"</string>
<string name="back_button_text" msgid="1469718707134137085">"پیچھے"</string>
<string name="handle_text" msgid="1766582106752184456">"ہینڈل"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"مکمل اسکرین"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"ڈیسک ٹاپ موڈ"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"اسپلٹ اسکرین"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"مزید"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"فلوٹ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 7f8ec01..8f173d5 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Chiqarish"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Bu ilova ekranni ikkiga ajratish rejimini dastaklamaydi."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Bu ilova ekranni bo‘lish xususiyatini qo‘llab-quvvatlamaydi."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Bu ilovani faqat 1 ta oynada ochish mumkin."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Bu ilova qo‘shimcha ekranda ishlamasligi mumkin."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Bu ilova qo‘shimcha ekranlarda ishga tushmaydi."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Ekranni ikkiga bo‘lish chizig‘i"</string>
diff --git a/libs/WindowManager/Shell/res/values-vi/strings.xml b/libs/WindowManager/Shell/res/values-vi/strings.xml
index 9f8b686..1d5b9d6 100644
--- a/libs/WindowManager/Shell/res/values-vi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-vi/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Hiện"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Ứng dụng có thể không hoạt động với tính năng chia đôi màn hình."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Ứng dụng không hỗ trợ chia đôi màn hình."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Ứng dụng này chỉ có thể mở 1 cửa sổ."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Ứng dụng có thể không hoạt động trên màn hình phụ."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Ứng dụng không hỗ trợ khởi chạy trên màn hình phụ."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Bộ chia chia đôi màn hình"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Đóng"</string>
<string name="back_button_text" msgid="1469718707134137085">"Quay lại"</string>
<string name="handle_text" msgid="1766582106752184456">"Xử lý"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Toàn màn hình"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Chế độ máy tính"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Chia đôi màn hình"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Tuỳ chọn khác"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Nổi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index c8e3b99..87f2973 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消隐藏"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"应用可能无法在分屏模式下正常运行。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"应用不支持分屏。"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此应用只能在 1 个窗口中打开。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"应用可能无法在辅显示屏上正常运行。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"应用不支持在辅显示屏上启动。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分屏分隔线"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"关闭"</string>
<string name="back_button_text" msgid="1469718707134137085">"返回"</string>
<string name="handle_text" msgid="1766582106752184456">"处理"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"全屏"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分屏"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"悬浮"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
index 8e5fd7f..f9b22d22 100644
--- a/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rHK/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消保護"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"應用程式不支援分割畫面。"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"此應用程式只可在 1 個視窗中開啟"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示屏上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示屏上啟動。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
<string name="back_button_text" msgid="1469718707134137085">"返去"</string>
<string name="handle_text" msgid="1766582106752184456">"控點"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"桌面模式"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分割螢幕"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
index 17557f9..1438e52 100644
--- a/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rTW/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"取消暫時隱藏"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"應用程式可能無法在分割畫面中運作。"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"這個應用程式不支援分割畫面。"</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"這個應用程式只能在 1 個視窗中開啟。"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"應用程式可能無法在次要顯示器上運作。"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"應用程式無法在次要顯示器上啟動。"</string>
<string name="accessibility_divider" msgid="703810061635792791">"分割畫面分隔線"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"關閉"</string>
<string name="back_button_text" msgid="1469718707134137085">"返回"</string>
<string name="handle_text" msgid="1766582106752184456">"控點"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"全螢幕"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"電腦模式"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"分割畫面"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"更多"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"浮動"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-zu/strings.xml b/libs/WindowManager/Shell/res/values-zu/strings.xml
index 01af7b8..e9238dc 100644
--- a/libs/WindowManager/Shell/res/values-zu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zu/strings.xml
@@ -33,6 +33,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Susa isiteshi"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Izinhlelo zokusebenza kungenzeka zingasebenzi ngesikrini esihlukanisiwe."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Uhlelo lokusebenza alusekeli isikrini esihlukanisiwe."</string>
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Le-app ingavulwa kuphela ewindini eli-1."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Uhlelo lokusebenza kungenzeka lungasebenzi kusibonisi sesibili."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Uhlelo lokusebenza alusekeli ukuqalisa kuzibonisi zesibili."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Isihlukanisi sokuhlukanisa isikrini"</string>
@@ -86,14 +87,9 @@
<string name="close_button_text" msgid="2913281996024033299">"Vala"</string>
<string name="back_button_text" msgid="1469718707134137085">"Emuva"</string>
<string name="handle_text" msgid="1766582106752184456">"Isibambo"</string>
- <!-- no translation found for fullscreen_text (1162316685217676079) -->
- <skip />
- <!-- no translation found for desktop_text (1077633567027630454) -->
- <skip />
- <!-- no translation found for split_screen_text (1396336058129570886) -->
- <skip />
- <!-- no translation found for more_button_text (3655388105592893530) -->
- <skip />
- <!-- no translation found for float_button_text (9221657008391364581) -->
- <skip />
+ <string name="fullscreen_text" msgid="1162316685217676079">"Isikrini esigcwele"</string>
+ <string name="desktop_text" msgid="1077633567027630454">"Imodi Yedeskithophu"</string>
+ <string name="split_screen_text" msgid="1396336058129570886">"Hlukanisa isikrini"</string>
+ <string name="more_button_text" msgid="3655388105592893530">"Okwengeziwe"</string>
+ <string name="float_button_text" msgid="9221657008391364581">"Iflowuthi"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index df5f921..c6197c8 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -111,4 +111,8 @@
<!-- Whether to dim a split-screen task when the other is the IME target -->
<bool name="config_dimNonImeAttachedSide">true</bool>
+
+ <!-- Components support to launch multiple instances into split-screen -->
+ <string-array name="config_componentsSupportMultiInstancesSplit">
+ </string-array>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 9fab3a1..25eddf8 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -66,8 +66,10 @@
<!-- Multi-Window strings -->
<!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed in split-screen and that things might crash/not work properly [CHAR LIMIT=NONE] -->
<string name="dock_forced_resizable">App may not work with split-screen.</string>
- <!-- Warning message when we try to dock a non-resizeable task and launch it in fullscreen instead. -->
+ <!-- Warning message when we try to dock a non-resizeable task and launch it in fullscreen instead [CHAR LIMIT=NONE] -->
<string name="dock_non_resizeble_failed_to_dock_text">App does not support split-screen.</string>
+ <!-- Warning message when we try to dock an app not supporting multiple instances split into multiple sides [CHAR LIMIT=NONE] -->
+ <string name="dock_multi_instances_not_supported_text">This app can only be opened in 1 window.</string>
<!-- Text that gets shown on top of current activity to inform the user that the system force-resized the current activity to be displayed on a secondary display and that things might crash/not work properly [CHAR LIMIT=NONE] -->
<string name="forced_resizable_secondary_display">App may not work on a secondary display.</string>
<!-- Warning message when we try to launch a non-resizeable activity on a secondary display and launch it on the primary instead. -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 4367936..e58e785 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,14 +16,12 @@
package com.android.wm.shell;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -48,7 +46,6 @@
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
-import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -567,6 +564,22 @@
}
}
+ /**
+ * Return list of {@link RunningTaskInfo}s for the given display.
+ *
+ * @return filtered list of tasks or empty list
+ */
+ public ArrayList<RunningTaskInfo> getRunningTasks(int displayId) {
+ ArrayList<RunningTaskInfo> result = new ArrayList<>();
+ for (int i = 0; i < mTasks.size(); i++) {
+ RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+ if (taskInfo.displayId == displayId) {
+ result.add(taskInfo);
+ }
+ }
+ return result;
+ }
+
/** Gets running task by taskId. Returns {@code null} if no such task observed. */
@Nullable
public RunningTaskInfo getRunningTaskInfo(int taskId) {
@@ -693,57 +706,6 @@
taskListener.reparentChildSurfaceToTask(taskId, sc, t);
}
- /**
- * Create a {@link WindowContainerTransaction} to clear task bounds.
- *
- * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
- * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- *
- * @param displayId display id for tasks that will have bounds cleared
- * @return {@link WindowContainerTransaction} with pending operations to clear bounds
- */
- public WindowContainerTransaction prepareClearBoundsForStandardTasks(int displayId) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
- WindowContainerTransaction wct = new WindowContainerTransaction();
- for (int i = 0; i < mTasks.size(); i++) {
- RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
- if ((taskInfo.displayId == displayId) && (taskInfo.getActivityType()
- == WindowConfiguration.ACTIVITY_TYPE_STANDARD)) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
- taskInfo.token, taskInfo);
- wct.setBounds(taskInfo.token, null);
- }
- }
- return wct;
- }
-
- /**
- * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
- *
- * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
- * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- *
- * @param displayId display id for tasks that will have windowing mode reset to {@link
- * WindowConfiguration#WINDOWING_MODE_UNDEFINED}
- * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
- */
- public WindowContainerTransaction prepareClearFreeformForStandardTasks(int displayId) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
- WindowContainerTransaction wct = new WindowContainerTransaction();
- for (int i = 0; i < mTasks.size(); i++) {
- RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
- if (taskInfo.displayId == displayId
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
- taskInfo);
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
- }
- }
- return wct;
- }
-
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 215308d..00b9fced 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -30,6 +30,8 @@
import androidx.annotation.NonNull;
+import com.android.wm.shell.transition.Transitions;
+
/**
* Wrapper to handle the ActivityEmbedding animation update in one
* {@link SurfaceControl.Transaction}.
@@ -50,6 +52,16 @@
/** Area in absolute coordinate that the animation surface shouldn't go beyond. */
@NonNull
private final Rect mWholeAnimationBounds = new Rect();
+ /**
+ * Area in absolute coordinate that should represent all the content to show for this window.
+ * This should be the end bounds for opening window, and start bounds for closing window in case
+ * the window is resizing during the open/close transition.
+ */
+ @NonNull
+ private final Rect mContentBounds = new Rect();
+ /** Offset relative to the window parent surface for {@link #mContentBounds}. */
+ @NonNull
+ private final Point mContentRelOffset = new Point();
@NonNull
final Transformation mTransformation = new Transformation();
@@ -80,6 +92,21 @@
mChange = change;
mLeash = leash;
mWholeAnimationBounds.set(wholeAnimationBounds);
+ if (Transitions.isClosingType(change.getMode())) {
+ // When it is closing, we want to show the content at the start position in case the
+ // window is resizing as well. For example, when the activities is changing from split
+ // to stack, the bottom TaskFragment will be resized to fullscreen when hiding.
+ final Rect startBounds = change.getStartAbsBounds();
+ final Rect endBounds = change.getEndAbsBounds();
+ mContentBounds.set(startBounds);
+ mContentRelOffset.set(change.getEndRelOffset());
+ mContentRelOffset.offset(
+ startBounds.left - endBounds.left,
+ startBounds.top - endBounds.top);
+ } else {
+ mContentBounds.set(change.getEndAbsBounds());
+ mContentRelOffset.set(change.getEndRelOffset());
+ }
}
/**
@@ -110,8 +137,7 @@
/** To be overridden by subclasses to adjust the animation surface change. */
void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
// Update the surface position and alpha.
- final Point offset = mChange.getEndRelOffset();
- mTransformation.getMatrix().postTranslate(offset.x, offset.y);
+ mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
@@ -119,8 +145,8 @@
// positionX/Y are in local coordinate, so minus the local offset to get the slide amount.
final int positionX = Math.round(mMatrix[MTRANS_X]);
final int positionY = Math.round(mMatrix[MTRANS_Y]);
- final Rect cropRect = new Rect(mChange.getEndAbsBounds());
- cropRect.offset(positionX - offset.x, positionY - offset.y);
+ final Rect cropRect = new Rect(mContentBounds);
+ cropRect.offset(positionX - mContentRelOffset.x, positionY - mContentRelOffset.y);
// Store the current offset of the surface top left from (0,0) in absolute coordinate.
final int offsetX = cropRect.left;
@@ -133,7 +159,7 @@
} else if (mAnimation.hasExtension()) {
// Allow the surface to be shown in its original bounds in case we want to use edge
// extensions.
- cropRect.union(mChange.getEndAbsBounds());
+ cropRect.union(mContentBounds);
}
// cropRect is in absolute coordinate, so we need to translate it to surface top left.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 921861a..c0a6456 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -304,6 +304,7 @@
// This is because the TaskFragment surface/change won't contain the Activity's before its
// reparent.
Animation changeAnimation = null;
+ Rect parentBounds = new Rect();
for (TransitionInfo.Change change : info.getChanges()) {
if (change.getMode() != TRANSIT_CHANGE
|| change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
@@ -326,10 +327,15 @@
}
}
+ // The TaskFragment may be enter/exit split, so we take the union of both as the parent
+ // size.
+ parentBounds.union(boundsAnimationChange.getStartAbsBounds());
+ parentBounds.union(boundsAnimationChange.getEndAbsBounds());
+
// There are two animations in the array. The first one is for the start leash
// (snapshot), and the second one is for the end leash (TaskFragment).
final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
- boundsAnimationChange.getEndAbsBounds());
+ parentBounds);
// Keep track as we might need to add background color for the animation.
// Although there may be multiple change animation, record one of them is sufficient
// because the background color will be added to the root leash for the whole animation.
@@ -352,6 +358,11 @@
animations[1], boundsAnimationChange));
}
+ if (parentBounds.isEmpty()) {
+ throw new IllegalStateException(
+ "There should be at least one changing window to play the change animation");
+ }
+
// If there is no corresponding open/close window with the change, we should show background
// color to cover the empty part of the screen.
boolean shouldShouldBackgroundColor = true;
@@ -368,10 +379,10 @@
// No-op if it will be covered by the changing parent window.
animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
} else if (Transitions.isClosingType(change.getMode())) {
- animation = mAnimationSpec.createChangeBoundsCloseAnimation(change);
+ animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
shouldShouldBackgroundColor = false;
} else {
- animation = mAnimationSpec.createChangeBoundsOpenAnimation(change);
+ animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds);
shouldShouldBackgroundColor = false;
}
adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 2bb7369..d10a674 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -21,7 +21,6 @@
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
import android.content.Context;
-import android.graphics.Point;
import android.graphics.Rect;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
@@ -80,15 +79,25 @@
/** Animation for window that is opening in a change transition. */
@NonNull
- Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change) {
+ Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change,
+ @NonNull Rect parentBounds) {
+ // Use end bounds for opening.
final Rect bounds = change.getEndAbsBounds();
- final Point offset = change.getEndRelOffset();
- // The window will be animated in from left or right depends on its position.
- final int startLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+ final int startLeft;
+ final int startTop;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated in from left or right depending on its position.
+ startTop = 0;
+ startLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated in from top or bottom depending on its position.
+ startTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ startLeft = 0;
+ }
// The position should be 0-based as we will post translate in
// ActivityEmbeddingAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(startLeft, 0, 0, 0);
+ final Animation animation = new TranslateAnimation(startLeft, 0, startTop, 0);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
@@ -98,15 +107,25 @@
/** Animation for window that is closing in a change transition. */
@NonNull
- Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change) {
- final Rect bounds = change.getEndAbsBounds();
- final Point offset = change.getEndRelOffset();
- // The window will be animated out to left or right depends on its position.
- final int endLeft = offset.x == 0 ? -bounds.width() : bounds.width();
+ Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change,
+ @NonNull Rect parentBounds) {
+ // Use start bounds for closing.
+ final Rect bounds = change.getStartAbsBounds();
+ final int endTop;
+ final int endLeft;
+ if (parentBounds.top == bounds.top && parentBounds.bottom == bounds.bottom) {
+ // The window will be animated out to left or right depending on its position.
+ endTop = 0;
+ endLeft = parentBounds.left == bounds.left ? -bounds.width() : bounds.width();
+ } else {
+ // The window will be animated out to top or bottom depending on its position.
+ endTop = parentBounds.top == bounds.top ? -bounds.height() : bounds.height();
+ endLeft = 0;
+ }
// The position should be 0-based as we will post translate in
// ActivityEmbeddingAnimationAdapter#onAnimationUpdate
- final Animation animation = new TranslateAnimation(0, endLeft, 0, 0);
+ final Animation animation = new TranslateAnimation(0, endLeft, 0, endTop);
animation.setInterpolator(mFastOutExtraSlowInInterpolator);
animation.setDuration(CHANGE_ANIMATION_DURATION);
animation.initialize(bounds.width(), bounds.height(), bounds.width(), bounds.height());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index bec6844..dd8afff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -475,8 +475,13 @@
@VisibleForTesting
public void onStatusBarStateChanged(boolean isShade) {
+ boolean didChange = mIsStatusBarShade != isShade;
+ if (DEBUG_BUBBLE_CONTROLLER) {
+ Log.d(TAG, "onStatusBarStateChanged isShade=" + isShade + " didChange=" + didChange);
+ }
mIsStatusBarShade = isShade;
- if (!mIsStatusBarShade) {
+ if (!mIsStatusBarShade && didChange) {
+ // Only collapse stack on change
collapseStack();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 1474754..214b304 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -76,6 +76,7 @@
private GestureDetector mDoubleTapDetector;
private boolean mInteractive;
private boolean mSetTouchRegion = true;
+ private int mLastDraggingPosition;
/**
* Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
@@ -218,7 +219,11 @@
insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
// Only insets the divider bar with task bar when it's expanded so that the rounded corners
// will be drawn against task bar.
- if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
+ // But there is no need to do it when IME showing because there are no rounded corners at
+ // the bottom. This also avoids the problem of task bar height not changing when IME
+ // floating.
+ if (!insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME)
+ && taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
mTempRect.inset(taskBarInsetsSource.calculateVisibleInsets(mTempRect));
}
@@ -298,6 +303,7 @@
}
if (mMoving) {
final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
+ mLastDraggingPosition = position;
mSplitLayout.updateDivideBounds(position);
}
break;
@@ -372,6 +378,15 @@
"Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
from);
mInteractive = interactive;
+ if (!mInteractive && mMoving) {
+ final int position = mSplitLayout.getDividePosition();
+ mSplitLayout.flingDividePosition(
+ mLastDraggingPosition,
+ position,
+ mSplitLayout.FLING_RESIZE_DURATION,
+ () -> mSplitLayout.setDividePosition(position, true /* applyLayoutChange */));
+ mMoving = false;
+ }
releaseTouching();
mHandle.setVisibility(mInteractive ? View.VISIBLE : View.INVISIBLE);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 5b7ed27..6e116b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -163,7 +163,8 @@
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
- Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY) {
+ Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
+ boolean immediately) {
if (mResizingIconView == null) {
return;
}
@@ -178,8 +179,8 @@
final boolean show =
newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height();
- final boolean animate = show != mShown;
- if (animate && mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ final boolean update = show != mShown;
+ if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
// If we need to animate and animator still running, cancel it before we ensure both
// background and icon surfaces are non null for next animation.
mFadeAnimator.cancel();
@@ -192,7 +193,7 @@
.setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
}
- if (mGapBackgroundLeash == null) {
+ if (mGapBackgroundLeash == null && !immediately) {
final boolean isLandscape = newBounds.height() == sideBounds.height();
final int left = isLandscape ? mBounds.width() : 0;
final int top = isLandscape ? 0 : mBounds.height();
@@ -221,8 +222,13 @@
newBounds.width() / 2 - mIconSize / 2,
newBounds.height() / 2 - mIconSize / 2);
- if (animate) {
- startFadeAnimation(show, null /* finishedConsumer */);
+ if (update) {
+ if (immediately) {
+ t.setVisibility(mBackgroundLeash, show);
+ t.setVisibility(mIconLeash, show);
+ } else {
+ startFadeAnimation(show, null /* finishedConsumer */);
+ }
mShown = show;
}
}
@@ -319,10 +325,12 @@
@Override
public void onAnimationStart(@NonNull Animator animation) {
if (show) {
- animT.show(mBackgroundLeash).show(mIconLeash).show(mGapBackgroundLeash).apply();
- } else {
- animT.hide(mGapBackgroundLeash).apply();
+ animT.show(mBackgroundLeash).show(mIconLeash);
}
+ if (mGapBackgroundLeash != null) {
+ animT.setVisibility(mGapBackgroundLeash, show);
+ }
+ animT.apply();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 839edc8..45b234a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -81,10 +81,10 @@
public static final int PARALLAX_DISMISSING = 1;
public static final int PARALLAX_ALIGN_CENTER = 2;
- private static final int FLING_RESIZE_DURATION = 250;
+ public static final int FLING_RESIZE_DURATION = 250;
private static final int FLING_SWITCH_DURATION = 350;
- private static final int FLING_ENTER_DURATION = 350;
- private static final int FLING_EXIT_DURATION = 350;
+ private static final int FLING_ENTER_DURATION = 450;
+ private static final int FLING_EXIT_DURATION = 450;
private int mDividerWindowWidth;
private int mDividerInsets;
@@ -122,6 +122,7 @@
private int mDensity;
private final boolean mDimNonImeSide;
+ private ValueAnimator mDividerFlingAnimator;
public SplitLayout(String windowName, Context context, Configuration configuration,
SplitLayoutHandler splitLayoutHandler,
@@ -395,6 +396,10 @@
mSplitWindowManager.release(t);
mDisplayImeController.removePositionProcessor(mImePositionProcessor);
mImePositionProcessor.reset();
+ if (mDividerFlingAnimator != null) {
+ mDividerFlingAnimator.cancel();
+ }
+ resetDividerPosition();
}
public void release() {
@@ -577,13 +582,18 @@
CUJ_SPLIT_SCREEN_RESIZE);
return;
}
- ValueAnimator animator = ValueAnimator
+
+ if (mDividerFlingAnimator != null) {
+ mDividerFlingAnimator.cancel();
+ }
+
+ mDividerFlingAnimator = ValueAnimator
.ofInt(from, to)
.setDuration(duration);
- animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- animator.addUpdateListener(
+ mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mDividerFlingAnimator.addUpdateListener(
animation -> updateDivideBounds((int) animation.getAnimatedValue()));
- animator.addListener(new AnimatorListenerAdapter() {
+ mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (flingFinishedCallback != null) {
@@ -591,17 +601,18 @@
}
InteractionJankMonitorUtils.endTracing(
CUJ_SPLIT_SCREEN_RESIZE);
+ mDividerFlingAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
- setDividePosition(to, true /* applyLayoutChange */);
+ mDividerFlingAnimator = null;
}
});
- animator.start();
+ mDividerFlingAnimator.start();
}
- /** Swich both surface position with animation. */
+ /** Switch both surface position with animation. */
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) {
final boolean isLandscape = isLandscape();
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 34ff6d8..abc4024 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
@@ -16,8 +16,11 @@
package com.android.wm.shell.desktopmode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
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_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -151,21 +154,18 @@
int displayId = mContext.getDisplayId();
+ ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId);
+
WindowContainerTransaction wct = new WindowContainerTransaction();
- // Reset freeform windowing mode that is set per task level (tasks should inherit
- // container value)
- wct.merge(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(displayId),
- true /* transfer */);
- int targetWindowingMode;
+ // Reset freeform windowing mode that is set per task level so tasks inherit it
+ clearFreeformForStandardTasks(runningTasks, wct);
if (active) {
- targetWindowingMode = WINDOWING_MODE_FREEFORM;
+ moveHomeBehindVisibleTasks(runningTasks, wct);
+ setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
} else {
- targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
- // Clear any resized bounds
- wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId),
- true /* transfer */);
+ clearBoundsForStandardTasks(runningTasks, wct);
+ setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
}
- prepareWindowingModeChange(wct, displayId, targetWindowingMode);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
} else {
@@ -173,17 +173,69 @@
}
}
- private void prepareWindowingModeChange(WindowContainerTransaction wct,
- int displayId, @WindowConfiguration.WindowingMode int windowingMode) {
- DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer
- .getDisplayAreaInfo(displayId);
+ private WindowContainerTransaction clearBoundsForStandardTasks(
+ ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks");
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
+ taskInfo.token, taskInfo);
+ wct.setBounds(taskInfo.token, null);
+ }
+ }
+ return wct;
+ }
+
+ private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks,
+ WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks");
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE,
+ "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
+ taskInfo);
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ }
+ }
+ }
+
+ private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks,
+ WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks");
+ RunningTaskInfo homeTask = null;
+ ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>();
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+ homeTask = taskInfo;
+ } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+ && taskInfo.isVisible()) {
+ visibleTasks.add(taskInfo);
+ }
+ }
+ if (homeTask == null) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found");
+ } else {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d",
+ visibleTasks.size());
+ wct.reorder(homeTask.getToken(), true /* onTop */);
+ for (RunningTaskInfo task : visibleTasks) {
+ wct.reorder(task.getToken(), true /* onTop */);
+ }
+ }
+ }
+
+ private void setDisplayAreaWindowingMode(int displayId,
+ @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) {
+ DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ displayId);
if (displayAreaInfo == null) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE,
"unable to update windowing mode for display %d display not found", displayId);
return;
}
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE,
"setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
windowingMode);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 90b35a5..44bcdb2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -82,7 +82,7 @@
mTasks.put(taskInfo.taskId, state);
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash, t, t);
+ mWindowDecorationViewModel.onTaskOpening(taskInfo, leash, t, t);
t.apply();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 168f6d7..6e710f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -120,7 +120,7 @@
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- mWindowDecorViewModel.createWindowDecoration(
+ mWindowDecorViewModel.onTaskOpening(
change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@@ -128,31 +128,23 @@
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- mWindowDecorViewModel.setupWindowDecorationForTransition(
- change.getTaskInfo(), startT, finishT);
+ mWindowDecorViewModel.onTaskClosing(change.getTaskInfo(), startT, finishT);
}
private void onChangeTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- mWindowDecorViewModel.setupWindowDecorationForTransition(
- change.getTaskInfo(), startT, finishT);
+ mWindowDecorViewModel.onTaskChanging(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
private void onToFrontTransitionReady(
TransitionInfo.Change change,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
- boolean exists = mWindowDecorViewModel.setupWindowDecorationForTransition(
- change.getTaskInfo(),
- startT,
- finishT);
- if (!exists) {
- // Window caption does not exist, create it
- mWindowDecorViewModel.createWindowDecoration(
- change.getTaskInfo(), change.getLeash(), startT, finishT);
- }
+ mWindowDecorViewModel.onTaskChanging(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@Override
@@ -188,4 +180,4 @@
mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
}
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 75a4091..6623f5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -103,7 +103,7 @@
if (mWindowDecorViewModelOptional.isPresent()) {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
createdWindowDecor = mWindowDecorViewModelOptional.get()
- .createWindowDecoration(taskInfo, leash, t, t);
+ .onTaskOpening(taskInfo, leash, t, t);
t.apply();
}
if (!createdWindowDecor) {
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 f170e77..8ba2583 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
@@ -63,6 +63,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
+import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -179,8 +180,10 @@
// This is necessary in case there was a resize animation ongoing when exit PIP
// started, in which case the first resize will be skipped to let the exit
// operation handle the final resize out of PIP mode. See b/185306679.
- finishResize(tx, destinationBounds, direction, animationType);
- sendOnPipTransitionFinished(direction);
+ finishResizeDelayedIfNeeded(() -> {
+ finishResize(tx, destinationBounds, direction, animationType);
+ sendOnPipTransitionFinished(direction);
+ });
}
}
@@ -196,6 +199,39 @@
}
};
+ /**
+ * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+ *
+ * This is done to avoid a race condition between the last transaction applied in
+ * onPipAnimationUpdate and the finishResize in onPipAnimationEnd. The transaction in
+ * onPipAnimationUpdate is applied directly from WmShell, while onPipAnimationEnd creates a
+ * WindowContainerTransaction in finishResize, which is to be applied by WmCore later. Normally,
+ * the WCT should be the last transaction to finish the animation. However, it may happen that
+ * it gets applied *before* the transaction created by the last onPipAnimationUpdate. This
+ * happens only when the PiP surface transaction has to be synced with the PiP menu due to the
+ * necessity for a delay when syncing the PiP surface animation with the PiP menu surface
+ * animation and redrawing the PiP menu contents. As a result, the PiP surface gets scaled after
+ * the new bounds are applied by WmCore, which makes the PiP surface have unexpected bounds.
+ *
+ * To avoid this, we delay the finishResize operation until
+ * the next frame. This aligns the last onAnimationUpdate transaction with the WCT application.
+ */
+ private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+ if (!shouldSyncPipTransactionWithMenu()) {
+ finishResizeRunnable.run();
+ return;
+ }
+
+ // Delay the finishResize to the next frame
+ Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+ mMainExecutor.execute(finishResizeRunnable);
+ }, null);
+ }
+
+ private boolean shouldSyncPipTransactionWithMenu() {
+ return mPipMenuController.isMenuVisible();
+ }
+
@VisibleForTesting
final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +257,7 @@
@Override
public boolean handlePipTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, Rect destinationBounds) {
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(leash, tx, destinationBounds);
return true;
}
@@ -1223,7 +1259,7 @@
mSurfaceTransactionHelper
.crop(tx, mLeash, toBounds)
.round(tx, mLeash, mPipTransitionState.isInPip());
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
@@ -1265,7 +1301,7 @@
mSurfaceTransactionHelper
.scale(tx, mLeash, startBounds, toBounds, degrees)
.round(tx, mLeash, startBounds, toBounds);
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
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 d28a9f3..efe938f 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
@@ -612,12 +612,21 @@
new DisplayInsetsController.OnInsetsChangedListener() {
@Override
public void insetsChanged(InsetsState insetsState) {
+ DisplayLayout pendingLayout =
+ mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId());
+ if (mIsInFixedRotation
+ || pendingLayout.rotation()
+ != mPipBoundsState.getDisplayLayout().rotation()) {
+ // bail out if there is a pending rotation or fixed rotation change
+ return;
+ }
int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
onDisplayChanged(
mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
false /* saveRestoreSnapFraction */);
int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
if (!mEnablePipKeepClearAlgorithm) {
+ // offset PiP to adjust for bottom inset change
int pipTop = mPipBoundsState.getBounds().top;
int diff = newMaxMovementBound - oldMaxMovementBound;
if (diff < 0 && pipTop > newMaxMovementBound) {
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 eb08d0e..56aa742 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
@@ -86,8 +86,8 @@
/**
* Starts a pair of intent and task in one transition.
*/
- oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent,
- in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio,
+ oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId,
+ in Bundle options2, int sidePosition, float splitRatio,
in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
/**
@@ -95,7 +95,7 @@
*/
oneway void startShortcutAndTask(in ShortcutInfo shortcutInfo, in Bundle options1, int taskId,
in Bundle options2, int splitPosition, float splitRatio,
- in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
/**
* Version of startTasks using legacy transition system.
@@ -108,9 +108,8 @@
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
- in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2,
- int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
- in InstanceId instanceId) = 12;
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
/**
* Starts a pair of shortcut and task using legacy transition system.
@@ -120,6 +119,21 @@
in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
/**
+ * 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;
+
+ /**
+ * Start a pair of intents in one transition.
+ */
+ oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1,
+ in PendingIntent pendingIntent2, in Bundle options2, int splitPosition,
+ float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+
+ /**
* Blocking call that notifies and gets additional split-screen targets when entering
* recents (for example: the dividerBar).
* @param appTargets apps that will be re-parented to display area
@@ -133,4 +147,4 @@
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
}
-// Last id = 17
+// Last id = 19
\ No newline at end of file
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 c6a2b83..a79ac45 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
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -32,6 +33,8 @@
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -55,18 +58,18 @@
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
+import android.widget.Toast;
import android.window.RemoteTransition;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -166,8 +169,11 @@
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
+ private final String[] mMultiInstancesComponents;
- private StageCoordinator mStageCoordinator;
+ @VisibleForTesting
+ StageCoordinator mStageCoordinator;
+
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mGoingToRecentsTasksLayer;
@@ -210,6 +216,51 @@
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
shellInit.addInitCallback(this::onInit, this);
}
+
+ // TODO(255224696): Remove the config once having a way for client apps to opt-in
+ // multi-instances split.
+ mMultiInstancesComponents = mContext.getResources()
+ .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
+ }
+
+ @VisibleForTesting
+ SplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ RecentTasksController recentTasks,
+ ShellExecutor mainExecutor,
+ StageCoordinator stageCoordinator) {
+ mShellCommandHandler = shellCommandHandler;
+ mShellController = shellController;
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mMainExecutor = mainExecutor;
+ mDisplayController = displayController;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mDragAndDropController = dragAndDropController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mIconProvider = iconProvider;
+ mRecentTasksOptional = Optional.of(recentTasks);
+ mStageCoordinator = stageCoordinator;
+ mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
+ shellInit.addInitCallback(this::onInit, this);
+ mMultiInstancesComponents = mContext.getResources()
+ .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
}
public SplitScreen asSplitScreen() {
@@ -471,72 +522,144 @@
startIntent(intent, fillInIntent, position, options);
}
+ private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+ && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
+ }
+
+ private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+ && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId);
+ }
+
+ private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ Intent fillInIntent1 = null;
+ Intent fillInIntent2 = null;
+ if (launchSameComponentAdjacently(pendingIntent1, pendingIntent2)
+ && supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
+ fillInIntent1 = new Intent();
+ fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ fillInIntent2 = new Intent();
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
+ pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
+ instanceId);
+ }
+
@Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
- if (fillInIntent == null) {
- fillInIntent = new Intent();
- }
- // 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.
+ // 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.
+ if (fillInIntent == null) fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
- // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
- // split and there is no reusable background task.
- if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
- final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent()
- ? mRecentTasksOptional.get().findTaskInBackground(
- intent.getIntent().getComponent())
- : null;
- if (taskInfo != null) {
- startTask(taskInfo.taskId, position, options);
+ if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) {
+ final ComponentName launching = intent.getIntent().getComponent();
+ if (supportMultiInstancesSplit(launching)) {
+ // To prevent accumulating large number of instances in the background, reuse task
+ // in the background with priority.
+ final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.findTaskInBackground(launching))
+ .orElse(null);
+ if (taskInfo != null) {
+ startTask(taskInfo.taskId, position, options);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Start task in background");
+ return;
+ }
+
+ // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
+ // the split and there is no reusable background task.
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition("startIntent");
+ return;
+ } else {
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
return;
}
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
}
- if (!ENABLE_SHELL_TRANSITIONS) {
- mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
- return;
- }
mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
/** Returns {@code true} if it's launching the same component on both sides of the split. */
- @VisibleForTesting
- boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) {
- if (startIntent == null) {
- return false;
- }
+ private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent,
+ @SplitPosition int position, int taskId) {
+ if (pendingIntent == null || pendingIntent.getIntent() == null) return false;
- final ComponentName launchingActivity = startIntent.getComponent();
- if (launchingActivity == null) {
- return false;
- }
+ final ComponentName launchingActivity = pendingIntent.getIntent().getComponent();
+ if (launchingActivity == null) return false;
- if (isSplitScreenVisible()) {
- // To prevent users from constantly dropping the same app to the same side resulting in
- // a large number of instances in the background.
- final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position);
- final ComponentName targetActivity = targetTaskInfo != null
- ? targetTaskInfo.baseIntent.getComponent() : null;
- if (Objects.equals(launchingActivity, targetActivity)) {
- return false;
+ if (taskId != INVALID_TASK_ID) {
+ final ActivityManager.RunningTaskInfo taskInfo =
+ mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (taskInfo != null) {
+ return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
}
-
- // Allow users to start a new instance the same to adjacent side.
- final ActivityManager.RunningTaskInfo pairedTaskInfo =
- getTaskInfo(SplitLayout.reversePosition(position));
- final ComponentName pairedActivity = pairedTaskInfo != null
- ? pairedTaskInfo.baseIntent.getComponent() : null;
- return Objects.equals(launchingActivity, pairedActivity);
+ return false;
}
- final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
- if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
- return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ if (!isSplitScreenVisible()) {
+ // Split screen is not yet activated, check if the current top running task is valid to
+ // split together.
+ final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
+ if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
+ return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ }
+ return false;
+ }
+
+ // Compare to the adjacent side of the split to determine if this is launching the same
+ // component adjacently.
+ final ActivityManager.RunningTaskInfo pairedTaskInfo =
+ getTaskInfo(SplitLayout.reversePosition(position));
+ final ComponentName pairedActivity = pairedTaskInfo != null
+ ? pairedTaskInfo.baseIntent.getComponent() : null;
+ return Objects.equals(launchingActivity, pairedActivity);
+ }
+
+ private boolean launchSameComponentAdjacently(PendingIntent pendingIntent1,
+ PendingIntent pendingIntent2) {
+ return Objects.equals(pendingIntent1.getIntent().getComponent(),
+ pendingIntent2.getIntent().getComponent());
+ }
+
+ @VisibleForTesting
+ /** Returns {@code true} if the component supports multi-instances split. */
+ boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
+ if (launching == null) return false;
+
+ final String componentName = launching.flattenToString();
+ for (int i = 0; i < mMultiInstancesComponents.length; i++) {
+ if (mMultiInstancesComponents[i].equals(componentName)) {
+ return true;
+ }
}
return false;
@@ -839,14 +962,13 @@
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
- Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
- int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
- controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
- pendingIntent, fillInIntent, options1, taskId, options2,
- splitPosition, splitRatio, adapter, instanceId));
+ controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter,
+ instanceId));
}
@Override
@@ -872,14 +994,13 @@
}
@Override
- public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
- @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
- @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
- (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent,
- fillInIntent, options1, taskId, options2, splitPosition, splitRatio,
- remoteTransition, instanceId));
+ (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId));
}
@Override
@@ -894,6 +1015,27 @@
}
@Override
+ public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
+ (controller) ->
+ controller.startIntentsWithLegacyTransition(
+ pendingIntent1, options1, pendingIntent2, options2, splitPosition,
+ splitRatio, adapter, instanceId)
+ );
+ }
+
+ @Override
+ public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ // TODO(b/259368992): To be implemented.
+ }
+
+ @Override
public void startShortcut(String packageName, String shortcutId, int position,
@Nullable Bundle options, UserHandle user, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcut",
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 e888c6f..4cb7623 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
@@ -24,7 +24,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -170,6 +169,7 @@
private ValueAnimator mDividerFadeInAnimator;
private boolean mDividerVisible;
private boolean mKeyguardShowing;
+ private boolean mShowDecorImmediately;
private final SyncTransactionQueue mSyncQueue;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -428,6 +428,11 @@
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, position, options);
+ return;
+ }
+
final WindowContainerTransaction wct = new WindowContainerTransaction();
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -441,13 +446,7 @@
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
mSplitTransitions.startEnterTransition(transitType, wct, null, this,
- aborted -> {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePositionAnimated(
- SplitLayout.reversePosition(mSideStagePosition));
- }
- } /* consumedCallback */,
+ null /* consumedCallback */,
(finishWct, finishT) -> {
if (!evictWct.isEmpty()) {
finishWct.merge(evictWct, true);
@@ -457,7 +456,7 @@
/** Launches an activity into split by legacy transition. */
void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options) {
+ @SplitPosition int position, @Nullable Bundle options) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -473,12 +472,6 @@
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
mSplitUnsupportedToast.show();
- } else {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePosition(SplitLayout.reversePosition(
- getSideStagePosition()), null);
- }
}
// Do nothing when the animation was cancelled.
@@ -596,8 +589,7 @@
/** Starts a pair of tasks using legacy transition. */
void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
addActivityOptions(options1, mSideStage);
@@ -607,7 +599,20 @@
instanceId);
}
- /** Starts a pair of intent and task using legacy transition. */
+ /** 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) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+
+ startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
+ splitRatio, adapter, instanceId);
+ }
+
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
@@ -635,18 +640,41 @@
instanceId);
}
+ private void startWithLegacyTransition(WindowContainerTransaction wct,
+ @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
+ 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);
+ }
+
/**
* @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
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) {
// Init divider first to make divider leash for remote animation target.
mSplitLayout.init();
mSplitLayout.setDivideRatio(splitRatio);
+ // Apply surface bounds before animation start.
+ SurfaceControl.Transaction startT = mTransactionPool.acquire();
+ updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */);
+ startT.apply();
+ mTransactionPool.release(startT);
+
// Set false to avoid record new bounds with old task still on top;
mShouldUpdateRecents = false;
mIsDividerRemoteAnimating = true;
@@ -709,14 +737,17 @@
if (mainOptions == null) mainOptions = new Bundle();
addActivityOptions(mainOptions, mMainStage);
updateWindowBounds(mSplitLayout, wct);
- wct.startTask(mainTaskId, mainOptions);
+ if (mainTaskId == INVALID_TASK_ID) {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
+ } else {
+ wct.startTask(mainTaskId, mainOptions);
+ }
wct.reorder(mRootTaskInfo.token, true);
wct.setForceTranslucent(mRootTaskInfo.token, false);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
setDividerVisibility(true, t);
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
});
setEnterInstanceId(instanceId);
@@ -771,9 +802,8 @@
mSideStage.evictInvisibleChildren(wct);
}
- Bundle resolveStartStage(@StageType int stage,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
- @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
switch (stage) {
case STAGE_TYPE_UNDEFINED: {
if (position != SPLIT_POSITION_UNDEFINED) {
@@ -844,9 +874,8 @@
: mMainStage.getTopVisibleChildTaskId();
}
- void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
- if (mSideStagePosition == sideStagePosition) return;
- SurfaceControl.Transaction t = mTransactionPool.acquire();
+ void switchSplitPosition(String reason) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
mTempRect1.setEmpty();
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
@@ -886,6 +915,11 @@
va.start();
});
});
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
}
void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1006,7 +1040,7 @@
mIsDividerRemoteAnimating = false;
mSplitLayout.getInvisibleBounds(mTempRect1);
- if (childrenToTop == null) {
+ if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
@@ -1265,13 +1299,6 @@
}
}
- private void onStageChildTaskEnterPip() {
- // When the exit split-screen is caused by one of the task enters auto pip,
- // we want both tasks to be put to bottom instead of top, otherwise it will end up
- // a fullscreen plus a pinned task instead of pinned only at the end of the transition.
- exitSplitScreen(null, EXIT_REASON_CHILD_TASK_ENTER_PIP);
- }
-
private void updateRecentTasksSplitPair() {
if (!mShouldUpdateRecents) {
return;
@@ -1561,6 +1588,7 @@
if (mLogger.isEnterRequestedByDrag()) {
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
} else {
+ mShowDecorImmediately = true;
mSplitLayout.flingDividerToCenter();
}
});
@@ -1617,10 +1645,7 @@
@Override
public void onDoubleTappedDivider() {
- setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition));
- mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ switchSplitPosition("double tap");
}
@Override
@@ -1639,14 +1664,16 @@
updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
- mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY);
- mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY);
+ mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
+ mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
t.apply();
mTransactionPool.release(t);
}
@Override
public void onLayoutSizeChanged(SplitLayout layout) {
+ // Reset this flag every time onLayoutSizeChanged.
+ mShowDecorImmediately = false;
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
sendOnBoundsChanged();
@@ -2034,7 +2061,6 @@
// Update divider state after animation so that it is still around and positioned
// properly for the animation itself.
mSplitLayout.release();
- mSplitLayout.resetDividerPosition();
mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
}
}
@@ -2311,11 +2337,6 @@
}
@Override
- public void onChildTaskEnterPip() {
- StageCoordinator.this.onStageChildTaskEnterPip();
- }
-
- @Override
public void onRootTaskVanished() {
reset();
StageCoordinator.this.onRootTaskVanished();
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 acad5d9..358f712 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
@@ -18,7 +18,6 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -74,8 +73,6 @@
void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
- void onChildTaskEnterPip();
-
void onRootTaskVanished();
void onNoLongerSupportMultiWindow();
@@ -257,9 +254,6 @@
// Status is managed/synchronized by the transition lifecycle.
return;
}
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
- mCallbacks.onChildTaskEnterPip();
- }
sendStatusChanged();
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
@@ -289,10 +283,10 @@
}
void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
- int offsetY) {
+ int offsetY, boolean immediately) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
- offsetY);
+ offsetY, immediately);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4e1fa29..485b400 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -77,10 +77,10 @@
if (mRemote.asBinder() != null) {
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
+ if (sct != null) {
+ finishTransaction.merge(sct);
+ }
mMainExecutor.execute(() -> {
- if (sct != null) {
- finishTransaction.merge(sct);
- }
finishCallback.onTransitionFinished(wct, null /* wctCB */);
});
}
@@ -90,7 +90,13 @@
if (mRemote.asBinder() != null) {
mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
}
- mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteStartT = RemoteTransitionHandler.copyIfLocal(
+ startTransaction, mRemote.getRemoteTransition());
+ final TransitionInfo remoteInfo =
+ remoteStartT == startTransaction ? info : info.localRemoteCopy();
+ mRemote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
} catch (RemoteException e) {
@@ -124,7 +130,13 @@
}
};
try {
- mRemote.getRemoteTransition().mergeAnimation(transition, info, t, mergeTarget, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteT =
+ RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition());
+ final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ mRemote.getRemoteTransition().mergeAnimation(
+ transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error merging remote transition.", e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 9469529..b4e0584 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -120,10 +121,10 @@
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
unhandleDeath(remote.asBinder(), finishCallback);
+ if (sct != null) {
+ finishTransaction.merge(sct);
+ }
mMainExecutor.execute(() -> {
- if (sct != null) {
- finishTransaction.merge(sct);
- }
mRequestedRemotes.remove(transition);
finishCallback.onTransitionFinished(wct, null /* wctCB */);
});
@@ -131,8 +132,14 @@
};
Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
try {
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteStartT =
+ copyIfLocal(startTransaction, remote.getRemoteTransition());
+ final TransitionInfo remoteInfo =
+ remoteStartT == startTransaction ? info : info.localRemoteCopy();
handleDeath(remote.asBinder(), finishCallback);
- remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+ remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
} catch (RemoteException e) {
@@ -145,6 +152,28 @@
return true;
}
+ static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t,
+ IRemoteTransition remote) {
+ // We care more about parceling than local (though they should be the same); so, use
+ // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
+ if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) {
+ // No local interface, so binder itself will parcel and thus we don't need to.
+ return t;
+ }
+ // Binder won't be parceling; however, the remotes assume they have their own native
+ // objects (and don't know if caller is local or not), so we need to make a COPY here so
+ // that the remote can clean it up without clearing the original transaction.
+ // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
+ final Parcel p = Parcel.obtain();
+ try {
+ t.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ return SurfaceControl.Transaction.CREATOR.createFromParcel(p);
+ } finally {
+ p.recycle();
+ }
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -175,7 +204,11 @@
}
};
try {
- remote.mergeAnimation(transition, info, t, mergeTarget, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
+ final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index b75c552..ab792ee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -321,6 +321,7 @@
.setPixelFormat(PixelFormat.RGBA_8888)
.setChildrenOnly(true)
.setAllowProtected(true)
+ .setCaptureSecureLayers(true)
.build();
final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer =
SurfaceControl.captureLayers(captureArgs);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 857decf..b714d2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -500,6 +500,7 @@
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
onAbort(transitionToken);
+ releaseSurfaces(info);
return;
}
@@ -604,6 +605,15 @@
onFinish(transition, wct, wctCB, false /* abort */);
}
+ /**
+ * Releases an info's animation-surfaces. These don't need to persist and we need to release
+ * them asap so that SF can free memory sooner.
+ */
+ private void releaseSurfaces(@Nullable TransitionInfo info) {
+ if (info == null) return;
+ info.releaseAnimSurfaces();
+ }
+
private void onFinish(IBinder transition,
@Nullable WindowContainerTransaction wct,
@Nullable WindowContainerTransactionCallback wctCB,
@@ -642,6 +652,11 @@
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Transition animation finished (abort=%b), notifying core %s", abort, transition);
+ if (active.mStartT != null) {
+ // Applied by now, so close immediately. Do not set to null yet, though, since nullness
+ // is used later to disambiguate malformed transitions.
+ active.mStartT.close();
+ }
// Merge all relevant transactions together
SurfaceControl.Transaction fullFinish = active.mFinishT;
for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
@@ -661,12 +676,14 @@
fullFinish.apply();
}
// Now perform all the finishes.
+ releaseSurfaces(active.mInfo);
mActiveTransitions.remove(activeIdx);
mOrganizer.finishTransition(transition, wct, wctCB);
while (activeIdx < mActiveTransitions.size()) {
if (!mActiveTransitions.get(activeIdx).mMerged) break;
ActiveTransition merged = mActiveTransitions.remove(activeIdx);
mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+ releaseSurfaces(merged.mInfo);
}
// sift through aborted transitions
while (mActiveTransitions.size() > activeIdx
@@ -679,8 +696,9 @@
}
mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionFinished(active.mToken, true);
+ mObservers.get(i).onTransitionFinished(aborted.mToken, true);
}
+ releaseSurfaces(aborted.mInfo);
}
if (mActiveTransitions.size() <= activeIdx) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index ca15f00..e40db4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -44,6 +44,8 @@
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -53,6 +55,8 @@
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;
+import java.util.function.Supplier;
+
/**
* View model for the window decoration with a caption and shadows. Works with
* {@link CaptionWindowDecoration}.
@@ -60,6 +64,8 @@
public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private static final String TAG = "CaptionViewModel";
+ private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
+ private final Supplier<InputManager> mInputManagerSupplier;
private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -71,9 +77,11 @@
private DesktopModeController mDesktopModeController;
private EventReceiver mEventReceiver;
private InputMonitor mInputMonitor;
+ private boolean mTransitionDragActive;
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
+ private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
public CaptionWindowDecorViewModel(
Context context,
@@ -83,6 +91,29 @@
DisplayController displayController,
SyncTransactionQueue syncQueue,
DesktopModeController desktopModeController) {
+ this(
+ context,
+ mainHandler,
+ mainChoreographer,
+ taskOrganizer,
+ displayController,
+ syncQueue,
+ desktopModeController,
+ new CaptionWindowDecoration.Factory(),
+ InputManager::getInstance);
+ }
+
+ public CaptionWindowDecorViewModel(
+ Context context,
+ Handler mainHandler,
+ Choreographer mainChoreographer,
+ ShellTaskOrganizer taskOrganizer,
+ DisplayController displayController,
+ SyncTransactionQueue syncQueue,
+ DesktopModeController desktopModeController,
+ CaptionWindowDecoration.Factory captionWindowDecorFactory,
+ Supplier<InputManager> inputManagerSupplier) {
+
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
@@ -91,6 +122,13 @@
mDisplayController = displayController;
mSyncQueue = syncQueue;
mDesktopModeController = desktopModeController;
+
+ mCaptionWindowDecorFactory = captionWindowDecorFactory;
+ mInputManagerSupplier = inputManagerSupplier;
+ }
+
+ void setEventReceiverFactory(EventReceiverFactory eventReceiverFactory) {
+ mEventReceiverFactory = eventReceiverFactory;
}
@Override
@@ -99,41 +137,13 @@
}
@Override
- public boolean createWindowDecoration(
+ public boolean onTaskOpening(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
if (!shouldShowWindowDecor(taskInfo)) return false;
- CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
- if (oldDecoration != null) {
- // close the old decoration if it exists to avoid two window decorations being added
- oldDecoration.close();
- }
- final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
- mContext,
- mDisplayController,
- mTaskOrganizer,
- taskInfo,
- taskSurface,
- mMainHandler,
- mMainChoreographer,
- mSyncQueue);
- mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
-
- TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration,
- mDragStartListener);
- CaptionTouchEventListener touchEventListener =
- new CaptionTouchEventListener(taskInfo, taskPositioner);
- windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
- windowDecoration.setDragResizeCallback(taskPositioner);
- setupWindowDecorationForTransition(taskInfo, startT, finishT);
- if (mInputMonitor == null) {
- mInputMonitor = InputManager.getInstance().monitorGestureInput(
- "caption-touch", mContext.getDisplayId());
- mEventReceiver = new EventReceiver(
- mInputMonitor.getInputChannel(), Looper.myLooper());
- }
+ createWindowDecoration(taskInfo, taskSurface, startT, finishT);
return true;
}
@@ -146,25 +156,45 @@
}
@Override
- public boolean setupWindowDecorationForTransition(
+ public void onTaskChanging(
+ RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
+ if (!shouldShowWindowDecor(taskInfo)) {
+ if (decoration != null) {
+ destroyWindowDecoration(taskInfo);
+ }
+ return;
+ }
+
+ if (decoration == null) {
+ createWindowDecoration(taskInfo, taskSurface, startT, finishT);
+ } else {
+ decoration.relayout(taskInfo, startT, finishT);
+ }
+ }
+
+ @Override
+ public void onTaskClosing(
RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
- if (decoration == null) return false;
+ if (decoration == null) return;
decoration.relayout(taskInfo, startT, finishT);
- return true;
}
@Override
- public boolean destroyWindowDecoration(RunningTaskInfo taskInfo) {
+ public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
final CaptionWindowDecoration decoration =
mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
- if (decoration == null) return false;
+ if (decoration == null) return;
decoration.close();
- return true;
}
private class CaptionTouchEventListener implements
@@ -173,16 +203,18 @@
private final int mTaskId;
private final WindowContainerToken mTaskToken;
private final DragResizeCallback mDragResizeCallback;
+ private final DragDetector mDragDetector;
private int mDragPointerId = -1;
- private boolean mDragActive = false;
private CaptionTouchEventListener(
RunningTaskInfo taskInfo,
- DragResizeCallback dragResizeCallback) {
+ DragResizeCallback dragResizeCallback,
+ DragDetector dragDetector) {
mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token;
mDragResizeCallback = dragResizeCallback;
+ mDragDetector = dragDetector;
}
@Override
@@ -210,6 +242,7 @@
decoration.setButtonVisibility();
}
}
+
private void injectBackKey() {
sendBackEvent(KeyEvent.ACTION_DOWN);
sendBackEvent(KeyEvent.ACTION_UP);
@@ -231,19 +264,21 @@
@Override
public boolean onTouch(View v, MotionEvent e) {
+ boolean isDrag = false;
int id = v.getId();
if (id != R.id.caption_handle && id != R.id.caption) {
return false;
}
- if (id == R.id.caption_handle || mDragActive) {
+ if (id == R.id.caption_handle) {
+ isDrag = mDragDetector.detectDragEvent(e);
handleEventForMove(e);
}
if (e.getAction() != MotionEvent.ACTION_DOWN) {
- return false;
+ return isDrag;
}
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (taskInfo.isFocused) {
- return false;
+ return isDrag;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mTaskToken, true /* onTop */);
@@ -251,20 +286,24 @@
return true;
}
+ /**
+ * @param e {@link MotionEvent} to process
+ * @return {@code true} if a drag is happening; or {@code false} if it is not
+ */
private void handleEventForMove(MotionEvent e) {
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- int windowingMode = mDesktopModeController
+ int windowingMode = mDesktopModeController
.getDisplayAreaWindowingMode(taskInfo.displayId);
if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
return;
}
switch (e.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mDragActive = true;
- mDragPointerId = e.getPointerId(0);
+ case MotionEvent.ACTION_DOWN: {
+ mDragPointerId = e.getPointerId(0);
mDragResizeCallback.onDragResizeStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
break;
+ }
case MotionEvent.ACTION_MOVE: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragResizeCallback.onDragResizeMove(
@@ -273,14 +312,13 @@
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- mDragActive = false;
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
.stableInsets().top;
mDragResizeCallback.onDragResizeEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
if (e.getRawY(dragPointerIdx) <= statusBarHeight
- && windowingMode == WINDOWING_MODE_FREEFORM) {
+ && DesktopModeStatus.isActive(mContext)) {
mDesktopModeController.setDesktopModeActive(false);
}
break;
@@ -290,7 +328,7 @@
}
// InputEventReceiver to listen for touch input outside of caption bounds
- private class EventReceiver extends InputEventReceiver {
+ class EventReceiver extends InputEventReceiver {
EventReceiver(InputChannel channel, Looper looper) {
super(channel, looper);
}
@@ -298,26 +336,103 @@
@Override
public void onInputEvent(InputEvent event) {
boolean handled = false;
- if (event instanceof MotionEvent
- && ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
+ if (event instanceof MotionEvent) {
handled = true;
- CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event);
+ CaptionWindowDecorViewModel.this.handleReceivedMotionEvent((MotionEvent) event);
}
finishInputEvent(event, handled);
}
}
- // If any input received is outside of caption bounds, turn off handle menu
- private void handleMotionEvent(MotionEvent ev) {
- int size = mWindowDecorByTaskId.size();
- for (int i = 0; i < size; i++) {
- CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
- if (decoration != null) {
- decoration.closeHandleMenuIfNeeded(ev);
+ class EventReceiverFactory {
+ EventReceiver create(InputChannel channel, Looper looper) {
+ return new EventReceiver(channel, looper);
+ }
+ }
+
+ /**
+ * Handle MotionEvents relevant to focused task's caption that don't directly touch it
+ *
+ * @param ev the {@link MotionEvent} received by {@link EventReceiver}
+ */
+ private void handleReceivedMotionEvent(MotionEvent ev) {
+ if (!DesktopModeStatus.isActive(mContext)) {
+ handleCaptionThroughStatusBar(ev);
+ }
+ handleEventOutsideFocusedCaption(ev);
+ // Prevent status bar from reacting to a caption drag.
+ if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
+ mInputMonitor.pilferPointers();
+ }
+ }
+
+ // If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
+ private void handleEventOutsideFocusedCaption(MotionEvent ev) {
+ int action = ev.getActionMasked();
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ CaptionWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor == null) {
+ return;
+ }
+
+ if (!mTransitionDragActive) {
+ focusedDecor.closeHandleMenuIfNeeded(ev);
}
}
}
+ /**
+ * Perform caption actions if not able to through normal means.
+ * Turn on desktop mode if handle is dragged below status bar.
+ */
+ private void handleCaptionThroughStatusBar(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ // Begin drag through status bar if applicable.
+ CaptionWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor != null && !DesktopModeStatus.isActive(mContext)
+ && focusedDecor.checkTouchEventInHandle(ev)) {
+ mTransitionDragActive = true;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ CaptionWindowDecoration focusedDecor = getFocusedDecor();
+ if (focusedDecor == null) {
+ mTransitionDragActive = false;
+ return;
+ }
+ if (mTransitionDragActive) {
+ mTransitionDragActive = false;
+ int statusBarHeight = mDisplayController
+ .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
+ if (ev.getY() > statusBarHeight) {
+ mDesktopModeController.setDesktopModeActive(true);
+ return;
+ }
+ }
+ focusedDecor.checkClickEvent(ev);
+ break;
+ }
+ case MotionEvent.ACTION_CANCEL: {
+ mTransitionDragActive = false;
+ }
+ }
+ }
+
+ @Nullable
+ private CaptionWindowDecoration getFocusedDecor() {
+ int size = mWindowDecorByTaskId.size();
+ CaptionWindowDecoration focusedDecor = null;
+ for (int i = 0; i < size; i++) {
+ CaptionWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
+ if (decor != null && decor.isFocused()) {
+ focusedDecor = decor;
+ break;
+ }
+ }
+ return focusedDecor;
+ }
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
@@ -327,7 +442,47 @@
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
- private class DragStartListenerImpl implements TaskPositioner.DragStartListener{
+ private void createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (oldDecoration != null) {
+ // close the old decoration if it exists to avoid two window decorations being added
+ oldDecoration.close();
+ }
+ final CaptionWindowDecoration windowDecoration =
+ mCaptionWindowDecorFactory.create(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ taskSurface,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+ mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
+
+ TaskPositioner taskPositioner =
+ new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
+ CaptionTouchEventListener touchEventListener =
+ new CaptionTouchEventListener(
+ taskInfo, taskPositioner, windowDecoration.getDragDetector());
+ windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
+ windowDecoration.setDragResizeCallback(taskPositioner);
+ windowDecoration.relayout(taskInfo, startT, finishT);
+ if (mInputMonitor == null) {
+ InputManager inputManager = mInputManagerSupplier.get();
+ mInputMonitor =
+ inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
+ mEventReceiver =
+ mEventReceiverFactory.create(
+ mInputMonitor.getInputChannel(), Looper.myLooper());
+ }
+ }
+
+ private class DragStartListenerImpl implements TaskPositioner.DragStartListener {
@Override
public void onDragStart(int taskId) {
mWindowDecorByTaskId.get(taskId).closeHandleMenu();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 03cad04..037ca20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -23,6 +23,7 @@
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
@@ -41,7 +42,8 @@
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
- * {@link CaptionWindowDecorViewModel}. The caption bar contains a handle, back button, and close button.
+ * {@link CaptionWindowDecorViewModel}. The caption bar contains a handle, back button, and close
+ * button.
*
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
*/
@@ -62,6 +64,8 @@
private boolean mDesktopActive;
+ private DragDetector mDragDetector;
+
private AdditionalWindow mHandleMenu;
CaptionWindowDecoration(
@@ -79,6 +83,7 @@
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mDesktopActive = DesktopModeStatus.isActive(mContext);
+ mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
}
void setCaptionListeners(
@@ -92,6 +97,10 @@
mDragResizeCallback = dragResizeCallback;
}
+ DragDetector getDragDetector() {
+ return mDragDetector;
+ }
+
@Override
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -173,15 +182,17 @@
if (oldDecorationSurface != mDecorationContainerSurface || mDragResizeListener == null) {
closeDragResizeListener();
mDragResizeListener = new DragResizeInputListener(
- mContext,
- mHandler,
- mChoreographer,
- mDisplay.getDisplayId(),
- mDecorationContainerSurface,
- mDragResizeCallback);
+ mContext,
+ mHandler,
+ mChoreographer,
+ mDisplay.getDisplayId(),
+ mDecorationContainerSurface,
+ mDragResizeCallback);
}
int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext()).getScaledTouchSlop();
+ mDragDetector.setTouchSlop(touchSlop);
+
int resize_handle = mResult.mRootView.getResources()
.getDimensionPixelSize(R.dimen.freeform_resize_handle);
int resize_corner = mResult.mRootView.getResources()
@@ -232,9 +243,8 @@
/**
* Sets the visibility of buttons and color of caption based on desktop mode status
- *
*/
- public void setButtonVisibility() {
+ void setButtonVisibility() {
mDesktopActive = DesktopModeStatus.isActive(mContext);
int v = mDesktopActive ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption);
@@ -253,7 +263,7 @@
caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
}
- public boolean isHandleMenuActive() {
+ boolean isHandleMenuActive() {
return mHandleMenu != null;
}
@@ -268,7 +278,7 @@
/**
* Create and display handle menu window
*/
- public void createHandleMenu() {
+ void createHandleMenu() {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
final Resources resources = mDecorWindowContext.getResources();
int x = mRelayoutParams.mCaptionX;
@@ -289,7 +299,7 @@
/**
* Close the handle menu window
*/
- public void closeHandleMenu() {
+ void closeHandleMenu() {
if (!isHandleMenuActive()) return;
mHandleMenu.releaseView();
mHandleMenu = null;
@@ -303,29 +313,117 @@
/**
* Close an open handle menu if input is outside of menu coordinates
+ *
* @param ev the tapped point to compare against
- * @return
*/
- public void closeHandleMenuIfNeeded(MotionEvent ev) {
- if (mHandleMenu != null) {
- Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
- .positionInParent;
- final Resources resources = mDecorWindowContext.getResources();
- ev.offsetLocation(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
- ev.offsetLocation(-positionInParent.x, -positionInParent.y);
- int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
- int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
- if (!(ev.getX() >= 0 && ev.getY() >= 0
- && ev.getX() <= width && ev.getY() <= height)) {
+ void closeHandleMenuIfNeeded(MotionEvent ev) {
+ if (isHandleMenuActive()) {
+ if (!checkEventInCaptionView(ev, R.id.caption)) {
closeHandleMenu();
}
}
}
+ boolean isFocused() {
+ return mTaskInfo.isFocused;
+ }
+
+ /**
+ * Offset the coordinates of a {@link MotionEvent} to be in the same coordinate space as caption
+ *
+ * @param ev the {@link MotionEvent} to offset
+ * @return the point of the input in local space
+ */
+ private PointF offsetCaptionLocation(MotionEvent ev) {
+ PointF result = new PointF(ev.getX(), ev.getY());
+ Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+ .positionInParent;
+ result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
+ result.offset(-positionInParent.x, -positionInParent.y);
+ return result;
+ }
+
+ /**
+ * Determine if a passed MotionEvent is in a view in caption
+ *
+ * @param ev the {@link MotionEvent} to check
+ * @param layoutId the id of the view
+ * @return {@code true} if event is inside the specified view, {@code false} if not
+ */
+ private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
+ if (mResult.mRootView == null) return false;
+ PointF inputPoint = offsetCaptionLocation(ev);
+ View view = mResult.mRootView.findViewById(layoutId);
+ return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0);
+ }
+
+ boolean checkTouchEventInHandle(MotionEvent ev) {
+ if (isHandleMenuActive()) return false;
+ return checkEventInCaptionView(ev, R.id.caption_handle);
+ }
+
+ /**
+ * Check a passed MotionEvent if a click has occurred on any button on this caption
+ * Note this should only be called when a regular onClick is not possible
+ * (i.e. the button was clicked through status bar layer)
+ *
+ * @param ev the MotionEvent to compare
+ */
+ void checkClickEvent(MotionEvent ev) {
+ if (mResult.mRootView == null) return;
+ View caption = mResult.mRootView.findViewById(R.id.caption);
+ PointF inputPoint = offsetCaptionLocation(ev);
+ if (!isHandleMenuActive()) {
+ View handle = caption.findViewById(R.id.caption_handle);
+ clickIfPointInView(inputPoint, handle);
+ } else {
+ View menu = mHandleMenu.mWindowViewHost.getView();
+ View fullscreen = menu.findViewById(R.id.fullscreen_button);
+ if (clickIfPointInView(inputPoint, fullscreen)) return;
+ View desktop = menu.findViewById(R.id.desktop_button);
+ if (clickIfPointInView(inputPoint, desktop)) return;
+ View split = menu.findViewById(R.id.split_screen_button);
+ if (clickIfPointInView(inputPoint, split)) return;
+ View more = menu.findViewById(R.id.more_button);
+ clickIfPointInView(inputPoint, more);
+ }
+ }
+
+ private boolean clickIfPointInView(PointF inputPoint, View v) {
+ if (v.pointInView(inputPoint.x - v.getLeft(), inputPoint.y, 0)) {
+ mOnCaptionButtonClickListener.onClick(v);
+ return true;
+ }
+ return false;
+ }
+
@Override
public void close() {
closeDragResizeListener();
closeHandleMenu();
super.close();
}
+
+ static class Factory {
+
+ CaptionWindowDecoration create(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Handler handler,
+ Choreographer choreographer,
+ SyncTransactionQueue syncQueue) {
+ return new CaptionWindowDecoration(
+ context,
+ displayController,
+ taskOrganizer,
+ taskInfo,
+ taskSurface,
+ handler,
+ choreographer,
+ syncQueue);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
new file mode 100644
index 0000000..0abe8ab
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+/**
+ * A detector for touch inputs that differentiates between drag and click inputs.
+ * All touch events must be passed through this class to track a drag event.
+ */
+public class DragDetector {
+ private int mTouchSlop;
+ private PointF mInputDownPoint;
+ private boolean mIsDragEvent;
+ private int mDragPointerId;
+ public DragDetector(int touchSlop) {
+ mTouchSlop = touchSlop;
+ mInputDownPoint = new PointF();
+ mIsDragEvent = false;
+ mDragPointerId = -1;
+ }
+
+ /**
+ * Determine if {@link MotionEvent} is part of a drag event.
+ * @return {@code true} if this is a drag event, {@code false} if not
+ */
+ public boolean detectDragEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case ACTION_DOWN: {
+ mDragPointerId = ev.getPointerId(0);
+ float rawX = ev.getRawX(0);
+ float rawY = ev.getRawY(0);
+ mInputDownPoint.set(rawX, rawY);
+ return false;
+ }
+ case ACTION_MOVE: {
+ if (!mIsDragEvent) {
+ int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
+ float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
+ float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
+ if (Math.hypot(dx, dy) > mTouchSlop) {
+ mIsDragEvent = true;
+ }
+ }
+ return mIsDragEvent;
+ }
+ case ACTION_UP: {
+ boolean result = mIsDragEvent;
+ mIsDragEvent = false;
+ mInputDownPoint.set(0, 0);
+ mDragPointerId = -1;
+ return result;
+ }
+ case ACTION_CANCEL: {
+ mIsDragEvent = false;
+ mInputDownPoint.set(0, 0);
+ mDragPointerId = -1;
+ return false;
+ }
+ }
+ return mIsDragEvent;
+ }
+
+ public void setTouchSlop(int touchSlop) {
+ mTouchSlop = touchSlop;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index b9f16b6..d3f1332 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -22,7 +22,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.content.Context;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
@@ -38,6 +37,7 @@
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.SurfaceControl;
+import android.view.ViewConfiguration;
import android.view.WindowManagerGlobal;
import com.android.internal.view.BaseIWindow;
@@ -76,7 +76,7 @@
private Rect mRightBottomCornerBounds;
private int mDragPointerId = -1;
- private int mTouchSlop;
+ private DragDetector mDragDetector;
DragResizeInputListener(
Context context,
@@ -115,6 +115,7 @@
mInputEventReceiver = new TaskResizeInputEventReceiver(
mInputChannel, mHandler, mChoreographer);
mCallback = callback;
+ mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
}
/**
@@ -146,7 +147,7 @@
mHeight = height;
mResizeHandleThickness = resizeHandleThickness;
mCornerSize = cornerSize;
- mTouchSlop = touchSlop;
+ mDragDetector.setTouchSlop(touchSlop);
Region touchRegion = new Region();
final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
@@ -228,7 +229,6 @@
private boolean mConsumeBatchEventScheduled;
private boolean mShouldHandleEvents;
private boolean mDragging;
- private final PointF mActionDownPoint = new PointF();
private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -276,7 +276,9 @@
// Check if this is a touch event vs mouse event.
// Touch events are tracked in four corners. Other events are tracked in resize edges.
boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
-
+ if (isTouch) {
+ mDragging = mDragDetector.detectDragEvent(e);
+ }
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
float x = e.getX(0);
@@ -290,7 +292,6 @@
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
- mActionDownPoint.set(rawX, rawY);
int ctrlType = calculateCtrlType(isTouch, x, y);
mCallback.onDragResizeStart(ctrlType, rawX, rawY);
result = true;
@@ -304,14 +305,7 @@
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
- if (isTouch) {
- // Check for touch slop for touch events
- float dx = rawX - mActionDownPoint.x;
- float dy = rawY - mActionDownPoint.y;
- if (!mDragging && Math.hypot(dx, dy) > mTouchSlop) {
- mDragging = true;
- }
- } else {
+ if (!isTouch) {
// For all other types allow immediate dragging.
mDragging = true;
}
@@ -323,14 +317,13 @@
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- if (mDragging) {
+ if (mShouldHandleEvents && mDragging) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
mCallback.onDragResizeEnd(
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
}
mDragging = false;
mShouldHandleEvents = false;
- mActionDownPoint.set(0, 0);
mDragPointerId = -1;
result = true;
break;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index f0f2db7..a49a300 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -40,6 +40,9 @@
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mResizeStartPoint = new PointF();
private final Rect mResizeTaskBounds = new Rect();
+ // Whether the |dragResizing| hint should be sent with the next bounds change WCT.
+ // Used to optimized fluid resizing of freeform tasks.
+ private boolean mPendingDragResizeHint = false;
private int mCtrlType;
private DragStartListener mDragStartListener;
@@ -53,6 +56,12 @@
@Override
public void onDragResizeStart(int ctrlType, float x, float y) {
+ if (ctrlType != CTRL_TYPE_UNDEFINED) {
+ // The task is being resized, send the |dragResizing| hint to core with the first
+ // bounds-change wct.
+ mPendingDragResizeHint = true;
+ }
+
mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType;
@@ -63,19 +72,31 @@
@Override
public void onDragResizeMove(float x, float y) {
- changeBounds(x, y);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (changeBounds(wct, x, y)) {
+ if (mPendingDragResizeHint) {
+ // This is the first bounds change since drag resize operation started.
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
+ mPendingDragResizeHint = false;
+ }
+ mTaskOrganizer.applyTransaction(wct);
+ }
}
@Override
public void onDragResizeEnd(float x, float y) {
- changeBounds(x, y);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
+ changeBounds(wct, x, y);
+ mTaskOrganizer.applyTransaction(wct);
mCtrlType = 0;
mTaskBoundsAtDragStart.setEmpty();
mResizeStartPoint.set(0, 0);
+ mPendingDragResizeHint = false;
}
- private void changeBounds(float x, float y) {
+ private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
float deltaX = x - mResizeStartPoint.x;
mResizeTaskBounds.set(mTaskBoundsAtDragStart);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
@@ -96,10 +117,10 @@
}
if (!mResizeTaskBounds.isEmpty()) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
- mTaskOrganizer.applyTransaction(wct);
+ return true;
}
+ return false;
}
interface DragStartListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index 2ce4d04..907977c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -28,7 +28,6 @@
* servers.
*/
public interface WindowDecorViewModel {
-
/**
* Sets the transition starter that starts freeform task transitions.
*
@@ -37,16 +36,16 @@
void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter);
/**
- * Creates a window decoration for the given task.
- * Can be {@code null} for Fullscreen tasks but not Freeform ones.
+ * Creates a window decoration for the given task. Can be {@code null} for Fullscreen tasks but
+ * not Freeform ones.
*
- * @param taskInfo the initial task info of the task
+ * @param taskInfo the initial task info of the task
* @param taskSurface the surface of the task
- * @param startT the start transaction to be applied before the transition
- * @param finishT the finish transaction to restore states after the transition
+ * @param startT the start transaction to be applied before the transition
+ * @param finishT the finish transaction to restore states after the transition
* @return {@code true} if window decoration was created, {@code false} otherwise
*/
- boolean createWindowDecoration(
+ boolean onTaskOpening(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
SurfaceControl.Transaction startT,
@@ -54,7 +53,7 @@
/**
* Notifies a task info update on the given task, with the window decoration created previously
- * for this task by {@link #createWindowDecoration}.
+ * for this task by {@link #onTaskOpening}.
*
* @param taskInfo the new task info of the task
*/
@@ -62,13 +61,29 @@
/**
* Notifies a transition is about to start about the given task to give the window decoration a
- * chance to prepare for this transition.
+ * chance to prepare for this transition. Unlike {@link #onTaskInfoChanged}, this method creates
+ * a window decoration if one does not exist but is required.
*
- * @param startT the start transaction to be applied before the transition
- * @param finishT the finish transaction to restore states after the transition
- * @return {@code true} if window decoration exists, {@code false} otherwise
+ * @param taskInfo the initial task info of the task
+ * @param taskSurface the surface of the task
+ * @param startT the start transaction to be applied before the transition
+ * @param finishT the finish transaction to restore states after the transition
*/
- boolean setupWindowDecorationForTransition(
+ void onTaskChanging(
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT);
+
+ /**
+ * Notifies that the given task is about to close to give the window decoration a chance to
+ * prepare for this transition.
+ *
+ * @param taskInfo the initial task info of the task
+ * @param startT the start transaction to be applied before the transition
+ * @param finishT the finish transaction to restore states after the transition
+ */
+ void onTaskClosing(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT);
@@ -77,7 +92,6 @@
* Destroys the window decoration of the give task.
*
* @param taskInfo the info of the task
- * @return {@code true} if window decoration was destroyed, {@code false} otherwise
*/
- boolean destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
-}
+ void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7cbace5..081c8ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,13 +16,9 @@
package com.android.wm.shell;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
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.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,8 +30,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -44,11 +38,9 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
-import android.app.WindowConfiguration;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -61,8 +53,6 @@
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransaction.Change;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -638,130 +628,10 @@
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
- @Test
- public void testPrepareClearBoundsForStandardTasks() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
- mOrganizer.onTaskAppeared(task2, null);
-
- MockToken otherDisplayToken = new MockToken();
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED,
- otherDisplayToken);
- otherDisplayTask.displayId = 2;
- mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
- assertEquals(wct.getChanges().size(), 2);
- Change boundsChange1 = wct.getChanges().get(token1.binder());
- assertNotNull(boundsChange1);
- assertNotEquals(
- (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
- assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
-
- Change boundsChange2 = wct.getChanges().get(token2.binder());
- assertNotNull(boundsChange2);
- assertNotEquals(
- (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
- assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
- }
-
- @Test
- public void testPrepareClearBoundsForStandardTasks_onlyClearActivityTypeStandard() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
- task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- mOrganizer.onTaskAppeared(task2, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
- // Only clear bounds for task1
- assertEquals(1, wct.getChanges().size());
- assertNotNull(wct.getChanges().get(token1.binder()));
- }
-
- @Test
- public void testPrepareClearFreeformForStandardTasks() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW, token2);
- mOrganizer.onTaskAppeared(task2, null);
-
- MockToken otherDisplayToken = new MockToken();
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM,
- otherDisplayToken);
- otherDisplayTask.displayId = 2;
- mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
- // Only task with freeform windowing mode and the right display should be updated
- assertEquals(wct.getChanges().size(), 1);
- Change wmModeChange1 = wct.getChanges().get(token1.binder());
- assertNotNull(wmModeChange1);
- assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
- }
-
- @Test
- public void testPrepareClearFreeformForStandardTasks_onlyClearActivityTypeStandard() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_FREEFORM, token2);
- task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- mOrganizer.onTaskAppeared(task2, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
- // Only clear freeform for task1
- assertEquals(1, wct.getChanges().size());
- assertNotNull(wct.getChanges().get(token1.binder()));
- }
-
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
return taskInfo;
}
-
- private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, MockToken token) {
- RunningTaskInfo taskInfo = createTaskInfo(taskId, windowingMode);
- taskInfo.displayId = 1;
- taskInfo.token = token.token();
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
- return taskInfo;
- }
-
- private static class MockToken {
- private final WindowContainerToken mToken;
- private final IBinder mBinder;
-
- MockToken() {
- mToken = mock(WindowContainerToken.class);
- mBinder = mock(IBinder.class);
- when(mToken.asBinder()).thenReturn(mBinder);
- }
-
- WindowContainerToken token() {
- return mToken;
- }
-
- IBinder binder() {
- return mBinder;
- }
- }
}
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 79b520c..89bafcb 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
@@ -16,10 +16,13 @@
package com.android.wm.shell.desktopmode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
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;
@@ -30,13 +33,14 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -68,6 +72,9 @@
import org.mockito.Mock;
import org.mockito.Mockito;
+import java.util.ArrayList;
+import java.util.Arrays;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DesktopModeControllerTest extends ShellTestCase {
@@ -83,9 +90,7 @@
@Mock
private Handler mMockHandler;
@Mock
- private Transitions mMockTransitions;
- private TestShellExecutor mExecutor;
-
+ private Transitions mTransitions;
private DesktopModeController mController;
private DesktopModeTaskRepository mDesktopModeTaskRepository;
private ShellInit mShellInit;
@@ -97,20 +102,19 @@
when(DesktopModeStatus.isActive(any())).thenReturn(true);
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
- mExecutor = new TestShellExecutor();
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
mController = new DesktopModeController(mContext, mShellInit, mShellController,
- mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
- mDesktopModeTaskRepository, mMockHandler, mExecutor);
+ mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
+ mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
- new WindowContainerTransaction());
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
mShellInit.init();
clearInvocations(mShellTaskOrganizer);
clearInvocations(mRootTaskDisplayAreaOrganizer);
+ clearInvocations(mTransitions);
}
@After
@@ -124,113 +128,133 @@
}
@Test
- public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
- // Create a fake WCT to simulate setting task windowing mode to undefined
- WindowContainerTransaction taskWct = new WindowContainerTransaction();
- MockToken taskMockToken = new MockToken();
- taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskWct);
+ public void testDesktopModeEnabled_rootTdaSetToFreeform() {
+ DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
- // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
- MockToken displayMockToken = new MockToken();
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
-
- // The test
mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
-
- // WCT should have 2 changes - clear task wm mode and set display wm mode
- WindowContainerTransaction wct = arg.getValue();
- assertThat(wct.getChanges()).hasSize(2);
-
- // Verify executed WCT has a change for setting task windowing mode to undefined
- Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
- assertThat(taskWmModeChange).isNotNull();
- assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
-
- // Verify executed WCT has a change for setting display windowing mode to freeform
- Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(displayWmModeChange).isNotNull();
- assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+ // 1 change: Root TDA windowing mode
+ assertThat(wct.getChanges().size()).isEqualTo(1);
+ // Verify WCT has a change for setting windowing mode to freeform
+ Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
}
@Test
- public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
- // Create a fake WCT to simulate setting task windowing mode to undefined
- WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
- MockToken taskWmMockToken = new MockToken();
- taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskWmWct);
+ public void testDesktopModeDisabled_rootTdaSetToFullscreen() {
+ DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
- // Create a fake WCT to simulate clearing task bounds
- WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
- MockToken taskBoundsMockToken = new MockToken();
- taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
- when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskBoundsWct);
-
- // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
- MockToken displayMockToken = new MockToken();
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
-
- // The test
mController.updateDesktopModeActive(false);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+ // 1 change: Root TDA windowing mode
+ assertThat(wct.getChanges().size()).isEqualTo(1);
+ // Verify WCT has a change for setting windowing mode to fullscreen
+ Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ }
- // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
- WindowContainerTransaction wct = arg.getValue();
- assertThat(wct.getChanges()).hasSize(3);
+ @Test
+ public void testDesktopModeEnabled_windowingModeCleared() {
+ createMockDisplayArea();
+ RunningTaskInfo freeformTask = createFreeformTask();
+ RunningTaskInfo fullscreenTask = createFullscreenTask();
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(freeformTask, fullscreenTask, homeTask)));
- // Verify executed WCT has a change for setting task windowing mode to undefined
- Change taskWmMode = wct.getChanges().get(taskWmMockToken.binder());
- assertThat(taskWmMode).isNotNull();
- assertThat(taskWmMode.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+ mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
- // Verify executed WCT has a change for clearing task bounds
- Change bounds = wct.getChanges().get(taskBoundsMockToken.binder());
- assertThat(bounds).isNotNull();
- assertThat(bounds.getWindowSetMask() & WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
- assertThat(bounds.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
+ // 2 changes: Root TDA windowing mode and 1 task
+ assertThat(wct.getChanges().size()).isEqualTo(2);
+ // No changes for tasks that are not standard or freeform
+ assertThat(wct.getChanges().get(fullscreenTask.token.asBinder())).isNull();
+ assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+ // Standard freeform task has windowing mode cleared
+ Change change = wct.getChanges().get(freeformTask.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+ }
- // Verify executed WCT has a change for setting display windowing mode to fullscreen
- Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(displayWmModeChange).isNotNull();
- assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ @Test
+ public void testDesktopModeDisabled_windowingModeAndBoundsCleared() {
+ createMockDisplayArea();
+ RunningTaskInfo freeformTask = createFreeformTask();
+ RunningTaskInfo fullscreenTask = createFullscreenTask();
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(freeformTask, fullscreenTask, homeTask)));
+
+ mController.updateDesktopModeActive(false);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // 3 changes: Root TDA windowing mode and 2 tasks
+ assertThat(wct.getChanges().size()).isEqualTo(3);
+ // No changes to home task
+ assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+ // Standard tasks have bounds cleared
+ assertThatBoundsCleared(wct.getChanges().get(freeformTask.token.asBinder()));
+ assertThatBoundsCleared(wct.getChanges().get(fullscreenTask.token.asBinder()));
+ // Freeform standard tasks have windowing mode cleared
+ assertThat(wct.getChanges().get(
+ freeformTask.token.asBinder()).getWindowingMode()).isEqualTo(
+ WINDOWING_MODE_UNDEFINED);
+ }
+
+ @Test
+ public void testDesktopModeEnabled_homeTaskBehindVisibleTask() {
+ createMockDisplayArea();
+ RunningTaskInfo fullscreenTask1 = createFullscreenTask();
+ fullscreenTask1.isVisible = true;
+ RunningTaskInfo fullscreenTask2 = createFullscreenTask();
+ fullscreenTask2.isVisible = false;
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(fullscreenTask1, fullscreenTask2, homeTask)));
+
+ mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // Check that there are hierarchy changes for home task and visible task
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ // First show home task
+ WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
+
+ // Then visible task on top of it
+ WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
}
@Test
public void testShowDesktopApps() {
// Set up two active tasks on desktop
- mDesktopModeTaskRepository.addActiveTask(1);
- mDesktopModeTaskRepository.addActiveTask(2);
- MockToken token1 = new MockToken();
- MockToken token2 = new MockToken();
- ActivityManager.RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder().setToken(
- token1.token()).setLastActiveTime(100).build();
- ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder().setToken(
- token2.token()).setLastActiveTime(200).build();
- when(mShellTaskOrganizer.getRunningTaskInfo(1)).thenReturn(taskInfo1);
- when(mShellTaskOrganizer.getRunningTaskInfo(2)).thenReturn(taskInfo2);
+ RunningTaskInfo freeformTask1 = createFreeformTask();
+ freeformTask1.lastActiveTime = 100;
+ RunningTaskInfo freeformTask2 = createFreeformTask();
+ freeformTask2.lastActiveTime = 200;
+ mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+ mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+ when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
+ freeformTask1);
+ when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
+ freeformTask2);
// Run show desktop apps logic
mController.showDesktopApps();
ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
WindowContainerTransaction.class);
- verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), wctCaptor.capture(), any());
+ } else {
+ verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+ }
WindowContainerTransaction wct = wctCaptor.getValue();
// Check wct has reorder calls
@@ -239,12 +263,12 @@
// Task 2 has activity later, must be first
WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(token2.binder());
+ assertThat(op1.getContainer()).isEqualTo(freeformTask2.token.asBinder());
// Task 1 should be second
- WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(0);
+ WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(token2.binder());
+ assertThat(op2.getContainer()).isEqualTo(freeformTask1.token.asBinder());
}
@Test
@@ -266,7 +290,7 @@
@Test
public void testHandleTransitionRequest_notFreeform_returnsNull() {
- ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+ RunningTaskInfo trigger = new RunningTaskInfo();
trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
WindowContainerTransaction wct = mController.handleRequest(
new Binder(),
@@ -276,7 +300,7 @@
@Test
public void testHandleTransitionRequest_returnsWct() {
- ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+ RunningTaskInfo trigger = new RunningTaskInfo();
trigger.token = new MockToken().mToken;
trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
WindowContainerTransaction wct = mController.handleRequest(
@@ -285,6 +309,57 @@
assertThat(wct).isNotNull();
}
+ private DisplayAreaInfo createMockDisplayArea() {
+ DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().mToken,
+ mContext.getDisplayId(), 0);
+ when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
+ .thenReturn(displayAreaInfo);
+ return displayAreaInfo;
+ }
+
+ private RunningTaskInfo createFreeformTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private RunningTaskInfo createFullscreenTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private RunningTaskInfo createHomeTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_HOME)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private WindowContainerTransaction getDesktopModeSwitchTransaction() {
+ ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_CHANGE), arg.capture(), any());
+ } else {
+ verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+ }
+ return arg.getValue();
+ }
+
+ private void assertThatBoundsCleared(Change change) {
+ assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
+ assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
+ }
+
private static class MockToken {
private final WindowContainerToken mToken;
private final IBinder mBinder;
@@ -298,9 +373,5 @@
WindowContainerToken token() {
return mToken;
}
-
- IBinder binder() {
- return mBinder;
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 7068a84..48415d4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -103,7 +103,7 @@
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mWindowDecorViewModel).createWindowDecoration(
+ verify(mWindowDecorViewModel).onTaskOpening(
change.getTaskInfo(), change.getLeash(), startT, finishT);
}
@@ -120,7 +120,7 @@
mTransitionObserver.onTransitionReady(transition, info, startT, finishT);
mTransitionObserver.onTransitionStarting(transition);
- verify(mWindowDecorViewModel).setupWindowDecorationForTransition(
+ verify(mWindowDecorViewModel).onTaskClosing(
change.getTaskInfo(), startT, finishT);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index d01f3d3..38b75f8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -16,18 +16,24 @@
package com.android.wm.shell.splitscreen;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -35,6 +41,8 @@
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -65,11 +73,11 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
/**
* Tests for {@link SplitScreenController}
*/
@@ -91,18 +99,21 @@
@Mock Transitions mTransitions;
@Mock TransactionPool mTransactionPool;
@Mock IconProvider mIconProvider;
- @Mock Optional<RecentTasksController> mRecentTasks;
+ @Mock StageCoordinator mStageCoordinator;
+ @Mock RecentTasksController mRecentTasks;
+ @Captor ArgumentCaptor<Intent> mIntentCaptor;
private SplitScreenController mSplitScreenController;
@Before
public void setup() {
+ assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
- mIconProvider, mRecentTasks, mMainExecutor));
+ mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
}
@Test
@@ -148,58 +159,100 @@
}
@Test
- public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
- doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
- doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
-
- // Verify launching the same activity returns true.
+ public void testStartIntent_appendsNoUserActionFlag() {
Intent startIntent = createStartIntent("startActivity");
- ActivityManager.RunningTaskInfo focusTaskInfo =
- createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- // Verify launching different activity returns false.
- Intent diffIntent = createStartIntent("diffActivity");
- focusTaskInfo =
- createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
}
@Test
- public void testShouldAddMultipleTaskFlag_inSplitScreen() {
- doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+ public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into focus task
+ ActivityManager.RunningTaskInfo focusTaskInfo =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into focus task
+ ActivityManager.RunningTaskInfo focusTaskInfo =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+ // Put the same component into a task in the background
+ ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
+ doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
+ doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
ActivityManager.RunningTaskInfo sameTaskInfo =
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
- Intent diffIntent = createStartIntent("diffActivity");
- ActivityManager.RunningTaskInfo differentTaskInfo =
- createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ // Put the same component into a task in the background
+ doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
+ .findTaskInBackground(any());
- // Verify launching the same activity return false.
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
- // Verify launching the same activity as adjacent returns true.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
+ }
- // Verify launching different activity from adjacent returns false.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ @Test
+ public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
+ doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
+ doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+ ActivityManager.RunningTaskInfo sameTaskInfo =
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).switchSplitPosition(anyString());
}
private Intent createStartIntent(String activityName) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
new file mode 100644
index 0000000..8b134ed
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+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.app.ActivityManager;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.InputChannel;
+import android.view.InputMonitor;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.GrantPermissionRule;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopModeController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/** Tests of {@link CaptionWindowDecorViewModel} */
+@SmallTest
+public class CaptionWindowDecorViewModelTests extends ShellTestCase {
+ @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
+
+ @Mock private CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
+
+ @Mock private Handler mMainHandler;
+
+ @Mock private Choreographer mMainChoreographer;
+
+ @Mock private ShellTaskOrganizer mTaskOrganizer;
+
+ @Mock private DisplayController mDisplayController;
+
+ @Mock private SyncTransactionQueue mSyncQueue;
+
+ @Mock private DesktopModeController mDesktopModeController;
+
+ @Mock private InputMonitor mInputMonitor;
+
+ @Mock private InputChannel mInputChannel;
+
+ @Mock private CaptionWindowDecorViewModel.EventReceiverFactory mEventReceiverFactory;
+
+ @Mock private CaptionWindowDecorViewModel.EventReceiver mEventReceiver;
+
+ @Mock private InputManager mInputManager;
+
+ private final List<InputManager> mMockInputManagers = new ArrayList<>();
+
+ private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel;
+
+ @Before
+ public void setUp() {
+ mMockInputManagers.add(mInputManager);
+
+ mCaptionWindowDecorViewModel =
+ new CaptionWindowDecorViewModel(
+ mContext,
+ mMainHandler,
+ mMainChoreographer,
+ mTaskOrganizer,
+ mDisplayController,
+ mSyncQueue,
+ mDesktopModeController,
+ mCaptionWindowDecorFactory,
+ new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
+ mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
+
+ doReturn(mCaptionWindowDecoration)
+ .when(mCaptionWindowDecorFactory)
+ .create(any(), any(), any(), any(), any(), any(), any(), any());
+
+ when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
+ when(mEventReceiverFactory.create(any(), any())).thenReturn(mEventReceiver);
+ when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
+ }
+
+ @Test
+ public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
+ Looper.prepare();
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ GrantPermissionRule.grant(android.Manifest.permission.MONITOR_INPUT);
+
+ mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
+ verify(mCaptionWindowDecorFactory)
+ .create(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+ verify(mCaptionWindowDecoration).close();
+ }
+
+ @Test
+ public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
+ final int taskId = 1;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, WINDOWING_MODE_UNDEFINED);
+ SurfaceControl surfaceControl = mock(SurfaceControl.class);
+ final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
+
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+
+ verify(mCaptionWindowDecorFactory, never())
+ .create(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
+
+ mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
+
+ verify(mCaptionWindowDecorFactory)
+ .create(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ surfaceControl,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+ }
+
+ private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+ ActivityManager.RunningTaskInfo taskInfo =
+ new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setVisible(true)
+ .build();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ return taskInfo;
+ }
+
+ private static class MockObjectSupplier<T> implements Supplier<T> {
+ private final List<T> mObjects;
+ private final Supplier<T> mDefaultSupplier;
+ private int mNumOfCalls = 0;
+
+ private MockObjectSupplier(List<T> objects, Supplier<T> defaultSupplier) {
+ mObjects = objects;
+ mDefaultSupplier = defaultSupplier;
+ }
+
+ @Override
+ public T get() {
+ final T mock =
+ mNumOfCalls < mObjects.size() ? mObjects.get(mNumOfCalls)
+ : mDefaultSupplier.get();
+ ++mNumOfCalls;
+ return mock;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
new file mode 100644
index 0000000..ac10ddb
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -0,0 +1,130 @@
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [TaskPositioner].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:TaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TaskPositionerTest : ShellTestCase() {
+
+ @Mock
+ private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+ @Mock
+ private lateinit var mockWindowDecoration: WindowDecoration<*>
+ @Mock
+ private lateinit var mockDragStartListener: TaskPositioner.DragStartListener
+
+ @Mock
+ private lateinit var taskToken: WindowContainerToken
+ @Mock
+ private lateinit var taskBinder: IBinder
+
+ private lateinit var taskPositioner: TaskPositioner
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ taskPositioner = TaskPositioner(
+ mockShellTaskOrganizer,
+ mockWindowDecoration,
+ mockDragStartListener
+ )
+ `when`(taskToken.asBinder()).thenReturn(taskBinder)
+ mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+ taskId = TASK_ID
+ token = taskToken
+ configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ }
+ }
+
+ @Test
+ fun testDragResize_move_skipsDragResizingFlag() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_UNDEFINED, // Move
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Move the task 10px to the right.
+ val newX = STARTING_BOUNDS.left.toFloat() + 10
+ val newY = STARTING_BOUNDS.top.toFloat()
+ taskPositioner.onDragResizeMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragResizeEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setsDragResizingFlag() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_RIGHT, // Resize right
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize the task by 10px to the right.
+ val newX = STARTING_BOUNDS.right.toFloat() + 10
+ val newY = STARTING_BOUNDS.top.toFloat()
+ taskPositioner.onDragResizeMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragResizeEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }
+ })
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ !change.dragResizing
+ }
+ })
+ }
+
+ companion object {
+ private const val TASK_ID = 5
+ private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ }
+}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 3d08959..552a423 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -624,7 +624,6 @@
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES, TYPE_BLUETOOTH_A2DP);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER, TYPE_BLUETOOTH_A2DP);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HDMI, TYPE_HDMI);
- INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, TYPE_DOCK);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, TYPE_DOCK);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_USB_ACCESSORY, TYPE_USB_ACCESSORY);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_USB_DEVICE, TYPE_USB_DEVICE);
@@ -652,7 +651,6 @@
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_HDMI, TYPE_HDMI);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_TELEPHONY_RX, TYPE_TELEPHONY);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BACK_MIC, TYPE_BUILTIN_MIC);
- INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET, TYPE_DOCK);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_DGTL_DOCK_HEADSET, TYPE_DOCK);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_USB_ACCESSORY, TYPE_USB_ACCESSORY);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_USB_DEVICE, TYPE_USB_DEVICE);
@@ -687,7 +685,7 @@
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_USB_DEVICE, AudioSystem.DEVICE_OUT_USB_DEVICE);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_USB_HEADSET, AudioSystem.DEVICE_OUT_USB_HEADSET);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_USB_ACCESSORY, AudioSystem.DEVICE_OUT_USB_ACCESSORY);
- EXT_TO_INT_DEVICE_MAPPING.put(TYPE_DOCK, AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET);
+ EXT_TO_INT_DEVICE_MAPPING.put(TYPE_DOCK, AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_FM, AudioSystem.DEVICE_OUT_FM);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_TELEPHONY, AudioSystem.DEVICE_OUT_TELEPHONY_TX);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_AUX_LINE, AudioSystem.DEVICE_OUT_AUX_LINE);
@@ -710,7 +708,7 @@
TYPE_WIRED_HEADSET, AudioSystem.DEVICE_IN_WIRED_HEADSET);
EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_HDMI, AudioSystem.DEVICE_IN_HDMI);
EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_TELEPHONY, AudioSystem.DEVICE_IN_TELEPHONY_RX);
- EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_DOCK, AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET);
+ EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_DOCK, AudioSystem.DEVICE_IN_DGTL_DOCK_HEADSET);
EXT_TO_INT_INPUT_DEVICE_MAPPING.put(
TYPE_USB_ACCESSORY, AudioSystem.DEVICE_IN_USB_ACCESSORY);
EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_USB_DEVICE, AudioSystem.DEVICE_IN_USB_DEVICE);
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java
index c708876..4aee9eb 100644
--- a/media/java/android/media/AudioDeviceVolumeManager.java
+++ b/media/java/android/media/AudioDeviceVolumeManager.java
@@ -41,8 +41,7 @@
*/
public class AudioDeviceVolumeManager {
- // define when using Log.*
- //private static final String TAG = "AudioDeviceVolumeManager";
+ private static final String TAG = "AudioDeviceVolumeManager";
/** Indicates no special treatment in the handling of the volume adjustment */
public static final int ADJUST_MODE_NORMAL = 0;
@@ -62,11 +61,15 @@
private static IAudioService sService;
private final @NonNull String mPackageName;
- private final @Nullable String mAttributionTag;
- public AudioDeviceVolumeManager(Context context) {
+ /**
+ * @hide
+ * Constructor
+ * @param context the Context for the device volume operations
+ */
+ public AudioDeviceVolumeManager(@NonNull Context context) {
+ Objects.requireNonNull(context);
mPackageName = context.getApplicationContext().getOpPackageName();
- mAttributionTag = context.getApplicationContext().getAttributionTag();
}
/**
@@ -308,13 +311,36 @@
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) {
try {
- getService().setDeviceVolume(vi, ada, mPackageName, mAttributionTag);
+ getService().setDeviceVolume(vi, ada, mPackageName);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/**
+ * @hide
+ * Returns the volume on the given audio device for the given volume information.
+ * For instance if using a {@link VolumeInfo} configured for {@link AudioManager#STREAM_ALARM},
+ * it will return the alarm volume. When no volume index has ever been set for the given
+ * device, the default volume will be returned (the volume setting that would have been
+ * applied if playback for that use case had started).
+ * @param vi the volume information, only stream-based volumes are supported. Information
+ * other than the stream type is ignored.
+ * @param ada the device for which volume is to be retrieved
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
+ @NonNull AudioDeviceAttributes ada) {
+ try {
+ return getService().getDeviceVolume(vi, ada, mPackageName);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return VolumeInfo.getDefaultVolumeInfo();
+ }
+
+ /**
+ * @hide
* Return human-readable name for volume behavior
* @param behavior one of the volume behaviors defined in AudioManager
* @return a string for the given behavior
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e7eda3e..798688e 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1212,7 +1212,13 @@
}
}
- private static boolean isPublicStreamType(int streamType) {
+ /**
+ * @hide
+ * Checks whether a stream type is part of the public SDK
+ * @param streamType
+ * @return true if the stream type is available in SDK
+ */
+ public static boolean isPublicStreamType(int streamType) {
switch (streamType) {
case STREAM_VOICE_CALL:
case STREAM_SYSTEM:
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 90eb9e6..ad933e0 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -99,7 +99,10 @@
in String callingPackage, in String attributionTag);
void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
- in String callingPackage, in String attributionTag);
+ in String callingPackage);
+
+ VolumeInfo getDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
+ in String callingPackage);
oneway void handleVolumeKey(in KeyEvent event, boolean isOnTv,
String callingPackage, String caller);
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 8a03afb..d6fe6825 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -86,8 +86,10 @@
*
* <p>
* The format is one of the values from
- * {@link android.graphics.ImageFormat ImageFormat}. The mapping between the
- * formats and the planes is as follows:
+ * {@link android.graphics.ImageFormat ImageFormat},
+ * {@link android.graphics.PixelFormat PixelFormat}, or
+ * {@link android.hardware.HardwareBuffer HardwareBuffer}. The mapping between the
+ * formats and the planes is as follows (any formats not listed will have 1 plane):
* </p>
*
* <table>
@@ -171,15 +173,18 @@
* </tr>
* <tr>
* <td>{@link android.graphics.ImageFormat#YCBCR_P010 YCBCR_P010}</td>
- * <td>1</td>
+ * <td>3</td>
* <td>P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
- * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
- * little-endian value, with the lower 6 bits set to zero.
+ * followed by a Wx(H/2) Cb and Cr planes. Each sample is represented by a 16-bit
+ * little-endian value, with the lower 6 bits set to zero. Since this is guaranteed to be
+ * a semi-planar format, the Cb plane can also be treated as an interleaved Cb/Cr plane.
* </td>
* </tr>
* </table>
*
* @see android.graphics.ImageFormat
+ * @see android.graphics.PixelFormat
+ * @see android.hardware.HardwareBuffer
*/
public abstract int getFormat();
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 82c3139..b0917c7 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -89,6 +89,7 @@
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
+ private boolean mPreferBuiltinDevice;
// playback properties, use synchronized with mPlaybackSettingsLock
private boolean mIsLooping = false;
private float mVolume = 1.0f;
@@ -157,7 +158,39 @@
}
/**
+ * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is
+ * the one on which outgoing audio for SIM calls is played.
+ *
+ * @param audioManager the audio manage.
+ * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if
+ * none can be found.
+ */
+ private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) {
+ AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo device : deviceList) {
+ if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ return device;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the preferred device of the ringtong playback to the built-in device.
+ *
+ * @hide
+ */
+ public boolean preferBuiltinDevice(boolean enable) {
+ mPreferBuiltinDevice = enable;
+ if (mLocalPlayer == null) {
+ return true;
+ }
+ return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager));
+ }
+
+ /**
* Creates a local media player for the ringtone using currently set attributes.
+ *
* @hide
*/
public void createLocalMediaPlayer() {
@@ -172,6 +205,8 @@
try {
mLocalPlayer.setDataSource(mContext, mUri);
mLocalPlayer.setAudioAttributes(mAudioAttributes);
+ mLocalPlayer.setPreferredDevice(
+ mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null);
synchronized (mPlaybackSettingsLock) {
applyPlaybackProperties_sync();
}
diff --git a/media/java/android/media/VolumeInfo.java b/media/java/android/media/VolumeInfo.java
index c61b0e5..bc91a09 100644
--- a/media/java/android/media/VolumeInfo.java
+++ b/media/java/android/media/VolumeInfo.java
@@ -27,7 +27,6 @@
import android.os.ServiceManager;
import android.util.Log;
-import java.util.List;
import java.util.Objects;
/**
@@ -35,8 +34,9 @@
* A class to represent type of volume information.
* Can be used to represent volume associated with a stream type or {@link AudioVolumeGroup}.
* Volume index is optional when used to represent a category of volume.
- * Index ranges are supported too, making the representation of volume changes agnostic to the
- * range (e.g. can be used to map BT A2DP absolute volume range to internal range).
+ * Volume ranges are supported too, making the representation of volume changes agnostic
+ * regarding the range of values that are supported (e.g. can be used to map BT A2DP absolute
+ * volume range to internal range).
*
* Note: this class is not yet part of the SystemApi but is intended to be gradually introduced
* particularly in parts of the audio framework that suffer from code ambiguity when
@@ -46,25 +46,27 @@
private static final String TAG = "VolumeInfo";
private final boolean mUsesStreamType; // false implies AudioVolumeGroup is used
+ private final boolean mHasMuteCommand;
private final boolean mIsMuted;
private final int mVolIndex;
private final int mMinVolIndex;
private final int mMaxVolIndex;
- private final int mVolGroupId;
- private final int mStreamType;
+ private final @Nullable AudioVolumeGroup mVolGroup;
+ private final @AudioManager.PublicStreamTypes int mStreamType;
private static IAudioService sService;
private static VolumeInfo sDefaultVolumeInfo;
- private VolumeInfo(boolean usesStreamType, boolean isMuted, int volIndex,
- int minVolIndex, int maxVolIndex,
- int volGroupId, int streamType) {
+ private VolumeInfo(boolean usesStreamType, boolean hasMuteCommand, boolean isMuted,
+ int volIndex, int minVolIndex, int maxVolIndex,
+ AudioVolumeGroup volGroup, int streamType) {
mUsesStreamType = usesStreamType;
+ mHasMuteCommand = hasMuteCommand;
mIsMuted = isMuted;
mVolIndex = volIndex;
mMinVolIndex = minVolIndex;
mMaxVolIndex = maxVolIndex;
- mVolGroupId = volGroupId;
+ mVolGroup = volGroup;
mStreamType = streamType;
}
@@ -81,8 +83,10 @@
/**
* Returns the associated stream type, or will throw if {@link #hasStreamType()} returned false.
* @return a stream type value, see AudioManager.STREAM_*
+ * @throws IllegalStateException when called on a VolumeInfo not configured for
+ * stream types.
*/
- public int getStreamType() {
+ public @AudioManager.PublicStreamTypes int getStreamType() {
if (!mUsesStreamType) {
throw new IllegalStateException("VolumeInfo doesn't use stream types");
}
@@ -101,24 +105,28 @@
/**
* Returns the associated volume group, or will throw if {@link #hasVolumeGroup()} returned
* false.
- * @return the volume group corresponding to this VolumeInfo, or null if an error occurred
- * in the volume group management
+ * @return the volume group corresponding to this VolumeInfo
+ * @throws IllegalStateException when called on a VolumeInfo not configured for
+ * volume groups.
*/
- public @Nullable AudioVolumeGroup getVolumeGroup() {
+ public @NonNull AudioVolumeGroup getVolumeGroup() {
if (mUsesStreamType) {
throw new IllegalStateException("VolumeInfo doesn't use AudioVolumeGroup");
}
- List<AudioVolumeGroup> volGroups = AudioVolumeGroup.getAudioVolumeGroups();
- for (AudioVolumeGroup group : volGroups) {
- if (group.getId() == mVolGroupId) {
- return group;
- }
- }
- return null;
+ return mVolGroup;
}
/**
- * Returns whether this instance is conveying a mute state.
+ * Return whether this instance is conveying a mute state
+ * @return true if the muted state was explicitly set for this instance
+ */
+ public boolean hasMuteCommand() {
+ return mHasMuteCommand;
+ }
+
+ /**
+ * Returns whether this instance is conveying a mute state that was explicitly set
+ * by {@link Builder#setMuted(boolean)}, false otherwise
* @return true if the volume state is muted
*/
public boolean isMuted() {
@@ -185,18 +193,21 @@
*/
public static final class Builder {
private boolean mUsesStreamType = true; // false implies AudioVolumeGroup is used
- private int mStreamType = AudioManager.STREAM_MUSIC;
+ private @AudioManager.PublicStreamTypes int mStreamType = AudioManager.STREAM_MUSIC;
+ private boolean mHasMuteCommand = false;
private boolean mIsMuted = false;
private int mVolIndex = INDEX_NOT_SET;
private int mMinVolIndex = INDEX_NOT_SET;
private int mMaxVolIndex = INDEX_NOT_SET;
- private int mVolGroupId = -Integer.MIN_VALUE;
+ private @Nullable AudioVolumeGroup mVolGroup;
/**
* Builder constructor for stream type-based VolumeInfo
*/
- public Builder(int streamType) {
- // TODO validate stream type
+ public Builder(@AudioManager.PublicStreamTypes int streamType) {
+ if (!AudioManager.isPublicStreamType(streamType)) {
+ throw new IllegalArgumentException("Not a valid public stream type " + streamType);
+ }
mUsesStreamType = true;
mStreamType = streamType;
}
@@ -208,7 +219,7 @@
Objects.requireNonNull(volGroup);
mUsesStreamType = false;
mStreamType = -Integer.MIN_VALUE;
- mVolGroupId = volGroup.getId();
+ mVolGroup = volGroup;
}
/**
@@ -219,11 +230,12 @@
Objects.requireNonNull(info);
mUsesStreamType = info.mUsesStreamType;
mStreamType = info.mStreamType;
+ mHasMuteCommand = info.mHasMuteCommand;
mIsMuted = info.mIsMuted;
mVolIndex = info.mVolIndex;
mMinVolIndex = info.mMinVolIndex;
mMaxVolIndex = info.mMaxVolIndex;
- mVolGroupId = info.mVolGroupId;
+ mVolGroup = info.mVolGroup;
}
/**
@@ -232,6 +244,7 @@
* @return the same builder instance
*/
public @NonNull Builder setMuted(boolean isMuted) {
+ mHasMuteCommand = true;
mIsMuted = isMuted;
return this;
}
@@ -241,7 +254,6 @@
* @param volIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown
* @return the same builder instance
*/
- // TODO should we allow muted true + volume index set? (useful when toggling mute on/off?)
public @NonNull Builder setVolumeIndex(int volIndex) {
if (volIndex != INDEX_NOT_SET && volIndex < 0) {
throw new IllegalArgumentException("Volume index cannot be negative");
@@ -296,9 +308,9 @@
throw new IllegalArgumentException("Min volume index:" + mMinVolIndex
+ " greater than max index:" + mMaxVolIndex);
}
- return new VolumeInfo(mUsesStreamType, mIsMuted,
+ return new VolumeInfo(mUsesStreamType, mHasMuteCommand, mIsMuted,
mVolIndex, mMinVolIndex, mMaxVolIndex,
- mVolGroupId, mStreamType);
+ mVolGroup, mStreamType);
}
}
@@ -306,8 +318,8 @@
// Parcelable
@Override
public int hashCode() {
- return Objects.hash(mUsesStreamType, mStreamType, mIsMuted,
- mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroupId);
+ return Objects.hash(mUsesStreamType, mHasMuteCommand, mStreamType, mIsMuted,
+ mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroup);
}
@Override
@@ -318,19 +330,20 @@
VolumeInfo that = (VolumeInfo) o;
return ((mUsesStreamType == that.mUsesStreamType)
&& (mStreamType == that.mStreamType)
- && (mIsMuted == that.mIsMuted)
- && (mVolIndex == that.mVolIndex)
- && (mMinVolIndex == that.mMinVolIndex)
- && (mMaxVolIndex == that.mMaxVolIndex)
- && (mVolGroupId == that.mVolGroupId));
+ && (mHasMuteCommand == that.mHasMuteCommand)
+ && (mIsMuted == that.mIsMuted)
+ && (mVolIndex == that.mVolIndex)
+ && (mMinVolIndex == that.mMinVolIndex)
+ && (mMaxVolIndex == that.mMaxVolIndex)
+ && Objects.equals(mVolGroup, that.mVolGroup));
}
@Override
public String toString() {
return new String("VolumeInfo:"
+ (mUsesStreamType ? (" streamType:" + mStreamType)
- : (" volGroupId" + mVolGroupId))
- + " muted:" + mIsMuted
+ : (" volGroup:" + mVolGroup))
+ + (mHasMuteCommand ? (" muted:" + mIsMuted) : ("[no mute cmd]"))
+ ((mVolIndex != INDEX_NOT_SET) ? (" volIndex:" + mVolIndex) : "")
+ ((mMinVolIndex != INDEX_NOT_SET) ? (" min:" + mMinVolIndex) : "")
+ ((mMaxVolIndex != INDEX_NOT_SET) ? (" max:" + mMaxVolIndex) : ""));
@@ -345,21 +358,29 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeBoolean(mUsesStreamType);
dest.writeInt(mStreamType);
+ dest.writeBoolean(mHasMuteCommand);
dest.writeBoolean(mIsMuted);
dest.writeInt(mVolIndex);
dest.writeInt(mMinVolIndex);
dest.writeInt(mMaxVolIndex);
- dest.writeInt(mVolGroupId);
+ if (!mUsesStreamType) {
+ mVolGroup.writeToParcel(dest, 0 /*ignored*/);
+ }
}
private VolumeInfo(@NonNull Parcel in) {
mUsesStreamType = in.readBoolean();
mStreamType = in.readInt();
+ mHasMuteCommand = in.readBoolean();
mIsMuted = in.readBoolean();
mVolIndex = in.readInt();
mMinVolIndex = in.readInt();
mMaxVolIndex = in.readInt();
- mVolGroupId = in.readInt();
+ if (!mUsesStreamType) {
+ mVolGroup = AudioVolumeGroup.CREATOR.createFromParcel(in);
+ } else {
+ mVolGroup = null;
+ }
}
public static final @NonNull Parcelable.Creator<VolumeInfo> CREATOR =
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 95599bd..1183ca3 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -174,6 +174,12 @@
jfieldID typeId;
} gDescriptorInfo;
+static struct {
+ jclass clazz;
+ jmethodID ctorId;
+ jmethodID setId;
+} gBufferInfo;
+
struct fields_t {
jmethodID postEventFromNativeID;
jmethodID lockAndGetContextID;
@@ -460,11 +466,7 @@
return err;
}
- ScopedLocalRef<jclass> clazz(
- env, env->FindClass("android/media/MediaCodec$BufferInfo"));
-
- jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
- env->CallVoidMethod(bufferInfo, method, (jint)offset, (jint)size, timeUs, flags);
+ env->CallVoidMethod(bufferInfo, gBufferInfo.setId, (jint)offset, (jint)size, timeUs, flags);
return OK;
}
@@ -1091,13 +1093,7 @@
CHECK(msg->findInt64("timeUs", &timeUs));
CHECK(msg->findInt32("flags", (int32_t *)&flags));
- ScopedLocalRef<jclass> clazz(
- env, env->FindClass("android/media/MediaCodec$BufferInfo"));
- jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "()V");
- jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
-
- obj = env->NewObject(clazz.get(), ctor);
-
+ obj = env->NewObject(gBufferInfo.clazz, gBufferInfo.ctorId);
if (obj == NULL) {
if (env->ExceptionCheck()) {
ALOGE("Could not create MediaCodec.BufferInfo.");
@@ -1107,7 +1103,7 @@
return;
}
- env->CallVoidMethod(obj, method, (jint)offset, (jint)size, timeUs, flags);
+ env->CallVoidMethod(obj, gBufferInfo.setId, (jint)offset, (jint)size, timeUs, flags);
break;
}
@@ -3235,6 +3231,16 @@
gDescriptorInfo.typeId = env->GetFieldID(clazz.get(), "mType", "I");
CHECK(gDescriptorInfo.typeId != NULL);
+
+ clazz.reset(env->FindClass("android/media/MediaCodec$BufferInfo"));
+ CHECK(clazz.get() != NULL);
+ gBufferInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+ gBufferInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+ CHECK(gBufferInfo.ctorId != NULL);
+
+ gBufferInfo.setId = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
+ CHECK(gBufferInfo.setId != NULL);
}
static void android_media_MediaCodec_native_setup(
diff --git a/packages/BackupRestoreConfirmation/res/values-en-rCA/strings.xml b/packages/BackupRestoreConfirmation/res/values-en-rCA/strings.xml
index d096d98..d5019d5 100644
--- a/packages/BackupRestoreConfirmation/res/values-en-rCA/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-en-rCA/strings.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="backup_confirm_title" msgid="827563724209303345">"Full backup"</string>
- <string name="restore_confirm_title" msgid="5469365809567486602">"Full restoration"</string>
+ <string name="restore_confirm_title" msgid="5469365809567486602">"Full restore"</string>
<string name="backup_confirm_text" msgid="1878021282758896593">"A full backup of all data to a connected desktop computer has been requested. Do you want to allow this to happen?\n\nIf you did not request the backup yourself, do not allow the operation to proceed."</string>
<string name="allow_backup_button_label" msgid="4217228747769644068">"Back up my data"</string>
<string name="deny_backup_button_label" msgid="6009119115581097708">"Do not back up"</string>
@@ -32,7 +32,7 @@
<string name="restore_enc_password_text" msgid="6140898525580710823">"If the restore data is encrypted, please enter the password below:"</string>
<string name="toast_backup_started" msgid="550354281452756121">"Backup starting..."</string>
<string name="toast_backup_ended" msgid="3818080769548726424">"Backup finished"</string>
- <string name="toast_restore_started" msgid="7881679218971277385">"Restoration starting..."</string>
- <string name="toast_restore_ended" msgid="1764041639199696132">"Restoration ended"</string>
+ <string name="toast_restore_started" msgid="7881679218971277385">"Restore starting..."</string>
+ <string name="toast_restore_ended" msgid="1764041639199696132">"Restore ended"</string>
<string name="toast_timeout" msgid="5276598587087626877">"Operation timed out"</string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index 9821014..76f8e4a 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -20,7 +20,7 @@
<string name="confirmation_title" msgid="3785000297483688997">"Allow <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to access your <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
<string name="chooser_title" msgid="2262294130493605839">"Choose a <xliff:g id="PROFILE_NAME">%1$s</xliff:g> to be managed by <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
- <string name="summary_watch" msgid="3002344206574997652">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, calendar, call logs and Nearby devices permissions."</string>
+ <string name="summary_watch" msgid="3002344206574997652">"This app is needed to manage your <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions."</string>
<string name="permission_apps" msgid="6142133265286656158">"Apps"</string>
<string name="permission_apps_summary" msgid="798718816711515431">"Stream your phone’s apps"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Allow <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to access this information from your phone"</string>
@@ -31,18 +31,18 @@
<string name="title_computer" msgid="4693714143506569253">"Allow <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> to access this information from your phone"</string>
<string name="summary_computer" msgid="3798467601598297062"></string>
<string name="permission_notification" msgid="693762568127741203">"Notifications"</string>
- <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages and photos"</string>
+ <string name="permission_notification_summary" msgid="884075314530071011">"Can read all notifications, including information like contacts, messages, and photos"</string>
<string name="permission_storage" msgid="6831099350839392343">"Photos and media"</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">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media and notifications"</string>
+ <string name="helper_summary_computer" msgid="9050724687678157852">"<xliff:g id="APP_NAME">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> to access your phone’s photos, media, and notifications"</string>
<string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
<string name="summary_generic" msgid="2346762210105903720"></string>
<string name="consent_yes" msgid="8344487259618762872">"Allow"</string>
- <string name="consent_no" msgid="2640796915611404382">"Don\'t allow"</string>
+ <string name="consent_no" msgid="2640796915611404382">"Don’t allow"</string>
<string name="consent_back" msgid="2560683030046918882">"Back"</string>
<string name="permission_sync_confirmation_title" msgid="667074294393493186">"Transfer app permissions to your watch"</string>
<string name="permission_sync_summary" msgid="8873391306499120778">"To make it easier to set up your watch, apps installed on your watch during setup will use the same permissions as your phone.\n\n These permissions may include access to your watch’s microphone and location."</string>
- <string name="vendor_icon_description" msgid="4445875290032225965">"App icon"</string>
- <string name="vendor_header_button_description" msgid="6566660389500630608">"More information button"</string>
+ <string name="vendor_icon_description" msgid="4445875290032225965">"App Icon"</string>
+ <string name="vendor_header_button_description" msgid="6566660389500630608">"More Information Button"</string>
</resources>
diff --git a/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml b/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
index 62dba98..0867293 100644
--- a/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
@@ -3,8 +3,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="keyguard_description" msgid="8582605799129954556">"Please enter your password and continue to Dynamic System Updates"</string>
<string name="notification_install_completed" msgid="6252047868415172643">"Dynamic system is ready. To start using it, restart your device."</string>
- <string name="notification_install_inprogress" msgid="7383334330065065017">"Installation in progress"</string>
- <string name="notification_install_failed" msgid="4066039210317521404">"Installation failed"</string>
+ <string name="notification_install_inprogress" msgid="7383334330065065017">"Install in progress"</string>
+ <string name="notification_install_failed" msgid="4066039210317521404">"Install failed"</string>
<string name="notification_image_validation_failed" msgid="2720357826403917016">"Image validation failed. Abort installation."</string>
<string name="notification_dynsystem_in_use" msgid="1053194595682188396">"Currently running a dynamic system. Restart to use the original Android version."</string>
<string name="notification_action_cancel" msgid="5929299408545961077">"Cancel"</string>
diff --git a/packages/InputDevices/res/values-en-rCA/strings.xml b/packages/InputDevices/res/values-en-rCA/strings.xml
index ab48729..1161783 100644
--- a/packages/InputDevices/res/values-en-rCA/strings.xml
+++ b/packages/InputDevices/res/values-en-rCA/strings.xml
@@ -19,7 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Swiss German"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgian"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarian"</string>
- <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarian, phonetic"</string>
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarian, Phonetic"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italian"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danish"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegian"</string>
diff --git a/packages/PackageInstaller/res/values-en-rCA/strings.xml b/packages/PackageInstaller/res/values-en-rCA/strings.xml
index f09f7bb..c67351c 100644
--- a/packages/PackageInstaller/res/values-en-rCA/strings.xml
+++ b/packages/PackageInstaller/res/values-en-rCA/strings.xml
@@ -60,13 +60,13 @@
<string name="uninstall_update_text" msgid="863648314632448705">"Replace this app with the factory version? All data will be removed."</string>
<string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"Replace this app with the factory version? All data will be removed. This affects all users of this device, including those with work profiles."</string>
<string name="uninstall_keep_data" msgid="7002379587465487550">"Keep <xliff:g id="SIZE">%1$s</xliff:g> of app data."</string>
- <string name="uninstalling_notification_channel" msgid="840153394325714653">"Running uninstallations"</string>
- <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Failed uninstallations"</string>
+ <string name="uninstalling_notification_channel" msgid="840153394325714653">"Running uninstalls"</string>
+ <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Failed uninstalls"</string>
<string name="uninstalling" msgid="8709566347688966845">"Uninstalling…"</string>
<string name="uninstalling_app" msgid="8866082646836981397">"Uninstalling <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>…"</string>
- <string name="uninstall_done" msgid="439354138387969269">"Uninstallation finished."</string>
+ <string name="uninstall_done" msgid="439354138387969269">"Uninstall finished."</string>
<string name="uninstall_done_app" msgid="4588850984473605768">"Uninstalled <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string>
- <string name="uninstall_failed" msgid="1847750968168364332">"Uninstallation unsuccessful."</string>
+ <string name="uninstall_failed" msgid="1847750968168364332">"Uninstall unsuccessful."</string>
<string name="uninstall_failed_app" msgid="5506028705017601412">"Uninstalling <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> unsuccessful."</string>
<string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"Can\'t uninstall active device admin app"</string>
<string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"Can\'t uninstall active device admin app for <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
@@ -76,9 +76,9 @@
<string name="manage_device_administrators" msgid="3092696419363842816">"Manage device admin apps"</string>
<string name="manage_users" msgid="1243995386982560813">"Manage users"</string>
<string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> couldn\'t be uninstalled."</string>
- <string name="Parse_error_dlg_text" msgid="1661404001063076789">"There was a problem while parsing the package."</string>
+ <string name="Parse_error_dlg_text" msgid="1661404001063076789">"There was a problem parsing the package."</string>
<string name="wear_not_allowed_dlg_title" msgid="8664785993465117517">"Android Wear"</string>
- <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"Install/uninstall actions not supported on Wear."</string>
+ <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"Install/Uninstall actions not supported on Wear."</string>
<string name="message_staging" msgid="8032722385658438567">"Staging app…"</string>
<string name="app_name_unknown" msgid="6881210203354323926">"Unknown"</string>
<string name="untrusted_external_source_warning" product="tablet" msgid="7067510047443133095">"For your security, your tablet currently isn’t allowed to install unknown apps from this source. You can change this in Settings."</string>
@@ -89,8 +89,8 @@
<string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"Your TV and personal data are more vulnerable to attack by unknown apps. By installing this app, you agree that you are responsible for any damage to your TV or loss of data that may result from its use."</string>
<string name="anonymous_source_continue" msgid="4375745439457209366">"Continue"</string>
<string name="external_sources_settings" msgid="4046964413071713807">"Settings"</string>
- <string name="wear_app_channel" msgid="1960809674709107850">"Installing/uninstalling Wear apps"</string>
+ <string name="wear_app_channel" msgid="1960809674709107850">"Installing/uninstalling wear apps"</string>
<string name="app_installed_notification_channel_description" msgid="2695385797601574123">"App installed notification"</string>
<string name="notification_installation_success_message" msgid="6450467996056038442">"Successfully installed"</string>
- <string name="notification_installation_success_status" msgid="3172502643504323321">"Successfully installed \'<xliff:g id="APPNAME">%1$s</xliff:g>\'"</string>
+ <string name="notification_installation_success_status" msgid="3172502643504323321">"Successfully installed “<xliff:g id="APPNAME">%1$s</xliff:g>”"</string>
</resources>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index af05078..73bb5eb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -34,6 +34,8 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import java.util.Arrays;
@@ -96,6 +98,22 @@
mAbortInstall = true;
}
}
+
+ final String installerPackageNameFromIntent = getIntent().getStringExtra(
+ Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ if (installerPackageNameFromIntent != null) {
+ final String callingPkgName = getLaunchedFromPackage();
+ if (!TextUtils.equals(installerPackageNameFromIntent, callingPkgName)
+ && mPackageManager.checkPermission(Manifest.permission.INSTALL_PACKAGES,
+ callingPkgName) != PackageManager.PERMISSION_GRANTED) {
+ Log.e(LOG_TAG, "The given installer package name " + installerPackageNameFromIntent
+ + " is invalid. Remove it.");
+ EventLog.writeEvent(0x534e4554, "236687884", getLaunchedFromUid(),
+ "Invalid EXTRA_INSTALLER_PACKAGE_NAME");
+ getIntent().removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ }
+ }
+
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
diff --git a/packages/PrintSpooler/res/values-en-rCA/strings.xml b/packages/PrintSpooler/res/values-en-rCA/strings.xml
index 7fbfeb3..606cc3e 100644
--- a/packages/PrintSpooler/res/values-en-rCA/strings.xml
+++ b/packages/PrintSpooler/res/values-en-rCA/strings.xml
@@ -23,21 +23,21 @@
<string name="label_copies_summary" msgid="3861966063536529540">"Copies:"</string>
<string name="label_paper_size" msgid="908654383827777759">"Paper size"</string>
<string name="label_paper_size_summary" msgid="5668204981332138168">"Paper size:"</string>
- <string name="label_color" msgid="1108690305218188969">"Colour"</string>
+ <string name="label_color" msgid="1108690305218188969">"Color"</string>
<string name="label_duplex" msgid="5370037254347072243">"Two-sided"</string>
<string name="label_orientation" msgid="2853142581990496477">"Orientation"</string>
<string name="label_pages" msgid="7768589729282182230">"Pages"</string>
<string name="destination_default_text" msgid="5422708056807065710">"Select a printer"</string>
<string name="template_all_pages" msgid="3322235982020148762">"All <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
<string name="template_page_range" msgid="428638530038286328">"Range of <xliff:g id="PAGE_COUNT">%1$s</xliff:g>"</string>
- <string name="pages_range_example" msgid="8558694453556945172">"e.g. 1–5,8,11–13"</string>
+ <string name="pages_range_example" msgid="8558694453556945172">"e.g. 1—5,8,11—13"</string>
<string name="print_preview" msgid="8010217796057763343">"Print preview"</string>
<string name="install_for_print_preview" msgid="6366303997385509332">"Install PDF viewer for preview"</string>
<string name="printing_app_crashed" msgid="854477616686566398">"Printing app crashed"</string>
<string name="generating_print_job" msgid="3119608742651698916">"Generating print job"</string>
<string name="save_as_pdf" msgid="5718454119847596853">"Save as PDF"</string>
<string name="all_printers" msgid="5018829726861876202">"All printers…"</string>
- <string name="print_dialog" msgid="32628687461331979">"Print dialogue"</string>
+ <string name="print_dialog" msgid="32628687461331979">"Print dialog"</string>
<string name="current_page_template" msgid="5145005201131935302">"<xliff:g id="CURRENT_PAGE">%1$d</xliff:g>/<xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string>
<string name="page_description_template" msgid="6831239682256197161">"Page <xliff:g id="CURRENT_PAGE">%1$d</xliff:g> of <xliff:g id="PAGE_COUNT">%2$d</xliff:g>"</string>
<string name="summary_template" msgid="8899734908625669193">"Summary, copies <xliff:g id="COPIES">%1$s</xliff:g>, paper size <xliff:g id="PAPER_SIZE">%2$s</xliff:g>"</string>
@@ -91,7 +91,7 @@
<string name="print_service_security_warning_summary" msgid="1427434625361692006">"Your document may pass through one or more servers on its way to the printer."</string>
<string-array name="color_mode_labels">
<item msgid="7602948745415174937">"Black & White"</item>
- <item msgid="2762241247228983754">"Colour"</item>
+ <item msgid="2762241247228983754">"Color"</item>
</string-array>
<string-array name="duplex_mode_labels">
<item msgid="3882302912790928315">"None"</item>
diff --git a/packages/SettingsLib/res/values-en-rCA/arrays.xml b/packages/SettingsLib/res/values-en-rCA/arrays.xml
index 327e4e9..8a57232 100644
--- a/packages/SettingsLib/res/values-en-rCA/arrays.xml
+++ b/packages/SettingsLib/res/values-en-rCA/arrays.xml
@@ -86,7 +86,7 @@
<item msgid="8147982633566548515">"map14"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_titles">
- <item msgid="2494959071796102843">"Use system selection (default)"</item>
+ <item msgid="2494959071796102843">"Use System Selection (Default)"</item>
<item msgid="4055460186095649420">"SBC"</item>
<item msgid="720249083677397051">"AAC"</item>
<item msgid="1049450003868150455">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
@@ -96,7 +96,7 @@
<item msgid="506175145534048710">"Opus"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_summaries">
- <item msgid="8868109554557331312">"Use system selection (default)"</item>
+ <item msgid="8868109554557331312">"Use System Selection (Default)"</item>
<item msgid="9024885861221697796">"SBC"</item>
<item msgid="4688890470703790013">"AAC"</item>
<item msgid="8627333814413492563">"<xliff:g id="QUALCOMM">Qualcomm®</xliff:g> <xliff:g id="APTX">aptX™</xliff:g> audio"</item>
@@ -106,52 +106,52 @@
<item msgid="7940970833006181407">"Opus"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_titles">
- <item msgid="926809261293414607">"Use system selection (default)"</item>
+ <item msgid="926809261293414607">"Use System Selection (Default)"</item>
<item msgid="8003118270854840095">"44.1 kHz"</item>
<item msgid="3208896645474529394">"48.0 kHz"</item>
<item msgid="8420261949134022577">"88.2 kHz"</item>
<item msgid="8887519571067543785">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_sample_rate_summaries">
- <item msgid="2284090879080331090">"Use system selection (default)"</item>
+ <item msgid="2284090879080331090">"Use System Selection (Default)"</item>
<item msgid="1872276250541651186">"44.1 kHz"</item>
<item msgid="8736780630001704004">"48.0 kHz"</item>
<item msgid="7698585706868856888">"88.2 kHz"</item>
<item msgid="8946330945963372966">"96.0 kHz"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_titles">
- <item msgid="2574107108483219051">"Use system selection (default)"</item>
+ <item msgid="2574107108483219051">"Use System Selection (Default)"</item>
<item msgid="4671992321419011165">"16 bits/sample"</item>
<item msgid="1933898806184763940">"24 bits/sample"</item>
<item msgid="1212577207279552119">"32 bits/sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_bits_per_sample_summaries">
- <item msgid="9196208128729063711">"Use system selection (default)"</item>
+ <item msgid="9196208128729063711">"Use System Selection (Default)"</item>
<item msgid="1084497364516370912">"16 bits/sample"</item>
<item msgid="2077889391457961734">"24 bits/sample"</item>
<item msgid="3836844909491316925">"32 bits/sample"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_titles">
- <item msgid="3014194562841654656">"Use system selection (default)"</item>
+ <item msgid="3014194562841654656">"Use System Selection (Default)"</item>
<item msgid="5982952342181788248">"Mono"</item>
<item msgid="927546067692441494">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_channel_mode_summaries">
- <item msgid="1997302811102880485">"Use system selection (default)"</item>
+ <item msgid="1997302811102880485">"Use System Selection (Default)"</item>
<item msgid="8005696114958453588">"Mono"</item>
<item msgid="1333279807604675720">"Stereo"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_titles">
- <item msgid="1241278021345116816">"Optimised for Audio Quality (990kbps/909kbps)"</item>
- <item msgid="3523665555859696539">"Balanced Audio And Connection Quality (660 kbps/606 kbps)"</item>
- <item msgid="886408010459747589">"Optimised for Connection Quality (330kbps/303kbps)"</item>
+ <item msgid="1241278021345116816">"Optimized for Audio Quality (990kbps/909kbps)"</item>
+ <item msgid="3523665555859696539">"Balanced Audio And Connection Quality (660kbps/606kbps)"</item>
+ <item msgid="886408010459747589">"Optimized for Connection Quality (330kbps/303kbps)"</item>
<item msgid="3808414041654351577">"Best Effort (Adaptive Bit Rate)"</item>
</string-array>
<string-array name="bluetooth_a2dp_codec_ldac_playback_quality_summaries">
- <item msgid="804499336721569838">"Optimised for Audio Quality"</item>
- <item msgid="7451422070435297462">"Balanced Audio and Connection Quality"</item>
- <item msgid="6173114545795428901">"Optimised for Connection Quality"</item>
- <item msgid="4349908264188040530">"Best effort (adaptive bit rate)"</item>
+ <item msgid="804499336721569838">"Optimized for Audio Quality"</item>
+ <item msgid="7451422070435297462">"Balanced Audio And Connection Quality"</item>
+ <item msgid="6173114545795428901">"Optimized for Connection Quality"</item>
+ <item msgid="4349908264188040530">"Best Effort (Adaptive Bit Rate)"</item>
</string-array>
<string-array name="bluetooth_audio_active_device_summaries">
<item msgid="8019740759207729126"></item>
@@ -161,25 +161,25 @@
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="1191094707770726722">"Off"</item>
- <item msgid="7839165897132179888">"64 K"</item>
- <item msgid="2715700596495505626">"256 K"</item>
- <item msgid="7099386891713159947">"1 M"</item>
- <item msgid="6069075827077845520">"4 M"</item>
- <item msgid="6078203297886482480">"8 M"</item>
+ <item msgid="7839165897132179888">"64K"</item>
+ <item msgid="2715700596495505626">"256K"</item>
+ <item msgid="7099386891713159947">"1M"</item>
+ <item msgid="6069075827077845520">"4M"</item>
+ <item msgid="6078203297886482480">"8M"</item>
</string-array>
<string-array name="select_logd_size_lowram_titles">
<item msgid="1145807928339101085">"Off"</item>
- <item msgid="4064786181089783077">"64 K"</item>
- <item msgid="3052710745383602630">"256 K"</item>
- <item msgid="3691785423374588514">"1 M"</item>
+ <item msgid="4064786181089783077">"64K"</item>
+ <item msgid="3052710745383602630">"256K"</item>
+ <item msgid="3691785423374588514">"1M"</item>
</string-array>
<string-array name="select_logd_size_summaries">
<item msgid="409235464399258501">"Off"</item>
- <item msgid="4195153527464162486">"64 K per log buffer"</item>
- <item msgid="7464037639415220106">"256 K per log buffer"</item>
- <item msgid="8539423820514360724">"1 M per log buffer"</item>
- <item msgid="1984761927103140651">"4 M per log buffer"</item>
- <item msgid="2983219471251787208">"8 M per log buffer"</item>
+ <item msgid="4195153527464162486">"64K per log buffer"</item>
+ <item msgid="7464037639415220106">"256K per log buffer"</item>
+ <item msgid="8539423820514360724">"1M per log buffer"</item>
+ <item msgid="1984761927103140651">"4M per log buffer"</item>
+ <item msgid="2983219471251787208">"8M per log buffer"</item>
</string-array>
<string-array name="select_logpersist_titles">
<item msgid="704720725704372366">"Off"</item>
@@ -222,7 +222,7 @@
</string-array>
<string-array name="overlay_display_devices_entries">
<item msgid="4497393944195787240">"None"</item>
- <item msgid="8461943978957133391">"480 p"</item>
+ <item msgid="8461943978957133391">"480p"</item>
<item msgid="6923083594932909205">"480p (secure)"</item>
<item msgid="1226941831391497335">"720p"</item>
<item msgid="7051983425968643928">"720p (secure)"</item>
@@ -258,10 +258,10 @@
<string-array name="app_process_limit_entries">
<item msgid="794656271086646068">"Standard limit"</item>
<item msgid="8628438298170567201">"No background processes"</item>
- <item msgid="915752993383950932">"At most, 1 process"</item>
- <item msgid="8554877790859095133">"At most, 2 processes"</item>
- <item msgid="9060830517215174315">"At most, 3 processes"</item>
- <item msgid="6506681373060736204">"At most, 4 processes"</item>
+ <item msgid="915752993383950932">"At most 1 process"</item>
+ <item msgid="8554877790859095133">"At most 2 processes"</item>
+ <item msgid="9060830517215174315">"At most 3 processes"</item>
+ <item msgid="6506681373060736204">"At most 4 processes"</item>
</string-array>
<string-array name="usb_configuration_titles">
<item msgid="3358668781763928157">"Charging"</item>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index d991312..9b0c6b2a 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -46,22 +46,22 @@
<string name="wifi_security_passpoint" msgid="2209078477216565387">"Passpoint"</string>
<string name="wifi_security_sae" msgid="3644520541721422843">"WPA3-Personal"</string>
<string name="wifi_security_psk_sae" msgid="8135104122179904684">"WPA2/WPA3-Personal"</string>
- <string name="wifi_security_none_owe" msgid="5241745828327404101">"None/Enhanced open"</string>
- <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced open"</string>
+ <string name="wifi_security_none_owe" msgid="5241745828327404101">"None/Enhanced Open"</string>
+ <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced Open"</string>
<string name="wifi_security_eap_suiteb" msgid="415842785991698142">"WPA3-Enterprise 192-bit"</string>
<string name="wifi_remembered" msgid="3266709779723179188">"Saved"</string>
<string name="wifi_disconnected" msgid="7054450256284661757">"Disconnected"</string>
<string name="wifi_disabled_generic" msgid="2651916945380294607">"Disabled"</string>
<string name="wifi_disabled_network_failure" msgid="2660396183242399585">"IP Configuration Failure"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="1302938248432705534">"Not connected due to low quality network"</string>
- <string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"Wi-Fi Connection Failure"</string>
+ <string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"WiFi Connection Failure"</string>
<string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Authentication problem"</string>
<string name="wifi_cant_connect" msgid="5718417542623056783">"Can\'t connect"</string>
<string name="wifi_cant_connect_to_ap" msgid="3099667989279700135">"Can\'t connect to \'<xliff:g id="AP_NAME">%1$s</xliff:g>\'"</string>
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"Check password and try again"</string>
<string name="wifi_not_in_range" msgid="1541760821805777772">"Not in range"</string>
<string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Won\'t automatically connect"</string>
- <string name="wifi_no_internet" msgid="1774198889176926299">"No Internet access"</string>
+ <string name="wifi_no_internet" msgid="1774198889176926299">"No internet access"</string>
<string name="saved_network" msgid="7143698034077223645">"Saved by <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_to_metered_access_point" msgid="9179693207918156341">"Connected to metered network"</string>
<string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatically connected via %1$s"</string>
@@ -70,27 +70,27 @@
<string name="connected_via_app" msgid="3532267661404276584">"Connected via <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="available_via_passpoint" msgid="1716000261192603682">"Available via %1$s"</string>
<string name="tap_to_sign_up" msgid="5356397741063740395">"Tap to sign up"</string>
- <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No Internet"</string>
+ <string name="wifi_connected_no_internet" msgid="5087420713443350646">"No internet"</string>
<string name="private_dns_broken" msgid="1984159464346556931">"Private DNS server cannot be accessed"</string>
<string name="wifi_limited_connection" msgid="1184778285475204682">"Limited connection"</string>
- <string name="wifi_status_no_internet" msgid="3799933875988829048">"No Internet"</string>
- <string name="wifi_status_sign_in_required" msgid="2236267500459526855">"Sign-in required"</string>
+ <string name="wifi_status_no_internet" msgid="3799933875988829048">"No internet"</string>
+ <string name="wifi_status_sign_in_required" msgid="2236267500459526855">"Sign in required"</string>
<string name="wifi_ap_unable_to_handle_new_sta" msgid="5885145407184194503">"Access point temporarily full"</string>
<string name="connected_via_carrier" msgid="1968057009076191514">"Connected via %1$s"</string>
<string name="available_via_carrier" msgid="465598683092718294">"Available via %1$s"</string>
<string name="osu_opening_provider" msgid="4318105381295178285">"Opening <xliff:g id="PASSPOINTPROVIDER">%1$s</xliff:g>"</string>
<string name="osu_connect_failed" msgid="9107873364807159193">"Couldn’t connect"</string>
<string name="osu_completing_sign_up" msgid="8412636665040390901">"Completing sign-up…"</string>
- <string name="osu_sign_up_failed" msgid="5605453599586001793">"Couldn’t complete sign-up. Tap to try again"</string>
+ <string name="osu_sign_up_failed" msgid="5605453599586001793">"Couldn’t complete sign-up. Tap to try again."</string>
<string name="osu_sign_up_complete" msgid="7640183358878916847">"Sign-up complete. Connecting…"</string>
- <string name="speed_label_very_slow" msgid="8526005255731597666">"Very slow"</string>
+ <string name="speed_label_very_slow" msgid="8526005255731597666">"Very Slow"</string>
<string name="speed_label_slow" msgid="6069917670665664161">"Slow"</string>
<string name="speed_label_okay" msgid="1253594383880810424">"OK"</string>
<string name="speed_label_medium" msgid="9078405312828606976">"Medium"</string>
<string name="speed_label_fast" msgid="2677719134596044051">"Fast"</string>
- <string name="speed_label_very_fast" msgid="8215718029533182439">"Very fast"</string>
+ <string name="speed_label_very_fast" msgid="8215718029533182439">"Very Fast"</string>
<string name="wifi_passpoint_expired" msgid="6540867261754427561">"Expired"</string>
- <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
+ <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="7739366554710388701">"Disconnected"</string>
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Disconnecting…"</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"Connecting…"</string>
@@ -120,8 +120,8 @@
<string name="bluetooth_profile_pbap" msgid="4262303387989406171">"Contacts and call history sharing"</string>
<string name="bluetooth_profile_pbap_summary" msgid="6466456791354759132">"Use for contacts and call history sharing"</string>
<string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Internet connection sharing"</string>
- <string name="bluetooth_profile_map" msgid="8907204701162107271">"Text messages"</string>
- <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM access"</string>
+ <string name="bluetooth_profile_map" msgid="8907204701162107271">"Text Messages"</string>
+ <string name="bluetooth_profile_sap" msgid="8304170950447934386">"SIM Access"</string>
<string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
<string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"HD audio"</string>
<string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"Hearing Aids"</string>
@@ -130,14 +130,14 @@
<string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"Connected to LE audio"</string>
<string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"Connected to media audio"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"Connected to phone audio"</string>
- <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file-transfer server"</string>
+ <string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"Connected to file transfer server"</string>
<string name="bluetooth_map_profile_summary_connected" msgid="4141725591784669181">"Connected to map"</string>
<string name="bluetooth_sap_profile_summary_connected" msgid="1280297388033001037">"Connected to SAP"</string>
- <string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"Not connected to file-transfer server"</string>
+ <string name="bluetooth_opp_profile_summary_not_connected" msgid="3959741824627764954">"Not connected to file transfer server"</string>
<string name="bluetooth_hid_profile_summary_connected" msgid="3923653977051684833">"Connected to input device"</string>
- <string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"Connected to device for Internet access"</string>
- <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Sharing local Internet connection with device"</string>
- <string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"Use for Internet access"</string>
+ <string name="bluetooth_pan_user_profile_summary_connected" msgid="380469653827505727">"Connected to device for internet access"</string>
+ <string name="bluetooth_pan_nap_profile_summary_connected" msgid="3744773111299503493">"Sharing local internet connection with device"</string>
+ <string name="bluetooth_pan_profile_summary_use_for" msgid="7422039765025340313">"Use for internet access"</string>
<string name="bluetooth_map_profile_summary_use_for" msgid="4453622103977592583">"Use for map"</string>
<string name="bluetooth_sap_profile_summary_use_for" msgid="6204902866176714046">"Use for SIM access"</string>
<string name="bluetooth_a2dp_profile_summary_use_for" msgid="7324694226276491807">"Use for media audio"</string>
@@ -156,17 +156,17 @@
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Pairing rejected by <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Computer"</string>
<string name="bluetooth_talkback_headset" msgid="3406852564400882682">"Headset"</string>
- <string name="bluetooth_talkback_phone" msgid="868393783858123880">"Telephone"</string>
+ <string name="bluetooth_talkback_phone" msgid="868393783858123880">"Phone"</string>
<string name="bluetooth_talkback_imaging" msgid="8781682986822514331">"Imaging"</string>
<string name="bluetooth_talkback_headphone" msgid="8613073829180337091">"Headphone"</string>
<string name="bluetooth_talkback_input_peripheral" msgid="5133944817800149942">"Input Peripheral"</string>
<string name="bluetooth_talkback_bluetooth" msgid="1143241359781999989">"Bluetooth"</string>
- <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wi-Fi off."</string>
- <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wi-Fi disconnected."</string>
- <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wi-Fi one bar."</string>
- <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wi-Fi two bars."</string>
- <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wi-Fi three bars."</string>
- <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wi-Fi signal full."</string>
+ <string name="accessibility_wifi_off" msgid="1195445715254137155">"Wifi off."</string>
+ <string name="accessibility_no_wifi" msgid="5297119459491085771">"Wifi disconnected."</string>
+ <string name="accessibility_wifi_one_bar" msgid="6025652717281815212">"Wifi one bar."</string>
+ <string name="accessibility_wifi_two_bars" msgid="687800024970972270">"Wifi two bars."</string>
+ <string name="accessibility_wifi_three_bars" msgid="779895671061950234">"Wifi three bars."</string>
+ <string name="accessibility_wifi_signal_full" msgid="7165262794551355617">"Wifi signal full."</string>
<string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Open network"</string>
<string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Secure network"</string>
<string name="process_kernel_label" msgid="950292573930336765">"Android OS"</string>
@@ -188,7 +188,7 @@
<string name="tts_default_rate_title" msgid="3964187817364304022">"Speech rate"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"Speed at which the text is spoken"</string>
<string name="tts_default_pitch_title" msgid="6988592215554485479">"Pitch"</string>
- <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Affects the tone of the synthesised speech"</string>
+ <string name="tts_default_pitch_summary" msgid="9132719475281551884">"Affects the tone of the synthesized speech"</string>
<string name="tts_default_lang_title" msgid="4698933575028098940">"Language"</string>
<string name="tts_lang_use_system" msgid="6312945299804012406">"Use system language"</string>
<string name="tts_lang_not_selected" msgid="7927823081096056147">"Language not selected"</string>
@@ -198,7 +198,7 @@
<string name="tts_install_data_title" msgid="1829942496472751703">"Install voice data"</string>
<string name="tts_install_data_summary" msgid="3608874324992243851">"Install the voice data required for speech synthesis"</string>
<string name="tts_engine_security_warning" msgid="3372432853837988146">"This speech synthesis engine may be able to collect all the text that will be spoken, including personal data like passwords and credit card numbers. It comes from the <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> engine. Enable the use of this speech synthesis engine?"</string>
- <string name="tts_engine_network_required" msgid="8722087649733906851">"This language requires a working network connection for Text-to-Speech output."</string>
+ <string name="tts_engine_network_required" msgid="8722087649733906851">"This language requires a working network connection for text-to-speech output."</string>
<string name="tts_default_sample_string" msgid="6388016028292967973">"This is an example of speech synthesis"</string>
<string name="tts_status_title" msgid="8190784181389278640">"Default language status"</string>
<string name="tts_status_ok" msgid="8583076006537547379">"<xliff:g id="LOCALE">%1$s</xliff:g> is fully supported"</string>
@@ -234,7 +234,7 @@
<string name="apn_settings_not_available" msgid="1147111671403342300">"Access Point Name settings are not available for this user"</string>
<string name="enable_adb" msgid="8072776357237289039">"USB debugging"</string>
<string name="enable_adb_summary" msgid="3711526030096574316">"Debug mode when USB is connected"</string>
- <string name="clear_adb_keys" msgid="3010148733140369917">"Revoke USB debugging authorisations"</string>
+ <string name="clear_adb_keys" msgid="3010148733140369917">"Revoke USB debugging authorizations"</string>
<string name="enable_adb_wireless" msgid="6973226350963971018">"Wireless debugging"</string>
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Debug mode when Wi‑Fi is connected"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Error"</string>
@@ -243,22 +243,22 @@
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Pair device with QR code"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Pair new devices using QR code scanner"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Pair device with pairing code"</string>
- <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Pair new devices using six-digit code"</string>
+ <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Pair new devices using six digit code"</string>
<string name="adb_paired_devices_title" msgid="5268997341526217362">"Paired devices"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Currently connected"</string>
<string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Device details"</string>
<string name="adb_device_forget" msgid="193072400783068417">"Forget"</string>
<string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Device fingerprint: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
<string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Connection unsuccessful"</string>
- <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Make sure that <xliff:g id="DEVICE_NAME">%1$s</xliff:g> is connected to the correct network"</string>
+ <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Make sure <xliff:g id="DEVICE_NAME">%1$s</xliff:g> is connected to the correct network"</string>
<string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Pair with device"</string>
<string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Wi‑Fi pairing code"</string>
<string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Pairing unsuccessful"</string>
- <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Make sure that the device is connected to the same network."</string>
+ <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Make sure the device is connected to the same network."</string>
<string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Pairing device…"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Failed to pair the device. Either the QR code was incorrect, or the device is not connected to the same network."</string>
- <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address and port"</string>
+ <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"IP address & Port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Scan QR code"</string>
<string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Pair device over Wi‑Fi by scanning a QR code"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Please connect to a Wi‑Fi network"</string>
@@ -278,28 +278,28 @@
<string name="mock_location_app_set" msgid="4706722469342913843">"Mock location app: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="debug_networking_category" msgid="6829757985772659599">"Networking"</string>
<string name="wifi_display_certification" msgid="1805579519992520381">"Wireless display certification"</string>
- <string name="wifi_verbose_logging" msgid="1785910450009679371">"Enable Wi‑Fi verbose logging"</string>
+ <string name="wifi_verbose_logging" msgid="1785910450009679371">"Enable Wi‑Fi Verbose Logging"</string>
<string name="wifi_scan_throttling" msgid="2985624788509913617">"Wi‑Fi scan throttling"</string>
- <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wi‑Fi non‑persistent MAC randomisation"</string>
+ <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wi‑Fi non‑persistent MAC randomization"</string>
<string name="mobile_data_always_on" msgid="8275958101875563572">"Mobile data always active"</string>
<string name="tethering_hardware_offload" msgid="4116053719006939161">"Tethering hardware acceleration"</string>
<string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Show Bluetooth devices without names"</string>
<string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Disable absolute volume"</string>
<string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Enable Gabeldorsche"</string>
- <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP version"</string>
+ <string name="bluetooth_select_avrcp_version_string" msgid="1710571610177659127">"Bluetooth AVRCP Version"</string>
<string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Select Bluetooth AVRCP Version"</string>
- <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP version"</string>
- <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP version"</string>
- <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth audio codec"</string>
+ <string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Bluetooth MAP Version"</string>
+ <string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Select Bluetooth MAP Version"</string>
+ <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bluetooth Audio Codec"</string>
<string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Trigger Bluetooth Audio Codec\nSelection"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth audio sample rate"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Bluetooth Audio Sample Rate"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Trigger Bluetooth Audio Codec\nSelection: Sample Rate"</string>
- <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"Grey-out means not supported by phone or headset"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Bluetooth audio bits per sample"</string>
+ <string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"Gray-out means not supported by phone or headset"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"Bluetooth Audio Bits Per Sample"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"Trigger Bluetooth Audio Codec\nSelection: Bits Per Sample"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"Bluetooth audio channel mode"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"Bluetooth Audio Channel Mode"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"Trigger Bluetooth Audio Codec\nSelection: Channel Mode"</string>
- <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Bluetooth audio LDAC codec: Playback quality"</string>
+ <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"Trigger Bluetooth Audio LDAC\nCodec Selection: Playback Quality"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
<string name="select_private_dns_configuration_title" msgid="7887550926056143018">"Private DNS"</string>
@@ -311,14 +311,14 @@
<string name="private_dns_mode_provider_failure" msgid="8356259467861515108">"Couldn\'t connect"</string>
<string name="wifi_display_certification_summary" msgid="8111151348106907513">"Show options for wireless display certification"</string>
<string name="wifi_verbose_logging_summary" msgid="4993823188807767892">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
- <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduces battery drain and improves network performance"</string>
- <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"When this mode is enabled, this device’s MAC address may change each time that it connects to a network that has MAC randomisation enabled."</string>
+ <string name="wifi_scan_throttling_summary" msgid="2577105472017362814">"Reduces battery drain & improves network performance"</string>
+ <string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"When this mode is enabled, this device’s MAC address may change each time it connects to a network that has MAC randomization enabled."</string>
<string name="wifi_metered_label" msgid="8737187690304098638">"Metered"</string>
<string name="wifi_unmetered_label" msgid="6174142840934095093">"Unmetered"</string>
<string name="select_logd_size_title" msgid="1604578195914595173">"Logger buffer sizes"</string>
<string name="select_logd_size_dialog_title" msgid="2105401994681013578">"Select Logger sizes per log buffer"</string>
<string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"Clear logger persistent storage?"</string>
- <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"When we are no longer monitoring with the persistent logger, we are required to erase the logger data resident on your device."</string>
+ <string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"When we no longer are monitoring with the persistent logger, we are required to erase the logger data resident on your device."</string>
<string name="select_logpersist_title" msgid="447071974007104196">"Store logger data persistently on device"</string>
<string name="select_logpersist_dialog_title" msgid="7745193591195485594">"Select log buffers to store persistently on device"</string>
<string name="select_usb_configuration_title" msgid="6339801314922294586">"Select USB Configuration"</string>
@@ -329,22 +329,22 @@
<string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Always keep mobile data active, even when Wi‑Fi is active (for fast network switching)."</string>
<string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Use tethering hardware acceleration if available"</string>
<string name="adb_warning_title" msgid="7708653449506485728">"Allow USB debugging?"</string>
- <string name="adb_warning_message" msgid="8145270656419669221">"USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification and read log data."</string>
+ <string name="adb_warning_message" msgid="8145270656419669221">"USB debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data."</string>
<string name="adbwifi_warning_title" msgid="727104571653031865">"Allow wireless debugging?"</string>
<string name="adbwifi_warning_message" msgid="8005936574322702388">"Wireless debugging is intended for development purposes only. Use it to copy data between your computer and your device, install apps on your device without notification, and read log data."</string>
- <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoke access to USB debugging from all computers you\'ve previously authorised?"</string>
+ <string name="adb_keys_warning_message" msgid="2968555274488101220">"Revoke access to USB debugging from all computers you’ve previously authorized?"</string>
<string name="dev_settings_warning_title" msgid="8251234890169074553">"Allow development settings?"</string>
<string name="dev_settings_warning_message" msgid="37741686486073668">"These settings are intended for development use only. They can cause your device and the applications on it to break or misbehave."</string>
<string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Verify apps over USB"</string>
- <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behaviour."</string>
+ <string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"Check apps installed via ADB/ADT for harmful behavior."</string>
<string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"Bluetooth devices without names (MAC addresses only) will be displayed"</string>
<string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control."</string>
<string name="bluetooth_enable_gabeldorsche_summary" msgid="2054730331770712629">"Enables the Bluetooth Gabeldorsche feature stack."</string>
- <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Enables the enhanced connectivity feature."</string>
+ <string name="enhanced_connectivity_summary" msgid="1576414159820676330">"Enables the Enhanced Connectivity feature."</string>
<string name="enable_terminal_title" msgid="3834790541986303654">"Local terminal"</string>
<string name="enable_terminal_summary" msgid="2481074834856064500">"Enable terminal app that offers local shell access"</string>
<string name="hdcp_checking_title" msgid="3155692785074095986">"HDCP checking"</string>
- <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Set HDCP checking behaviour"</string>
+ <string name="hdcp_checking_dialog_title" msgid="7691060297616217781">"Set HDCP checking behavior"</string>
<string name="debug_debugging_category" msgid="535341063709248842">"Debugging"</string>
<string name="debug_app" msgid="8903350241392391766">"Select debug app"</string>
<string name="debug_app_not_set" msgid="1934083001283807188">"No debug application set"</string>
@@ -373,7 +373,7 @@
<string name="debug_hw_overdraw" msgid="8944851091008756796">"Debug GPU overdraw"</string>
<string name="disable_overlays" msgid="4206590799671557143">"Disable HW overlays"</string>
<string name="disable_overlays_summary" msgid="1954852414363338166">"Always use GPU for screen compositing"</string>
- <string name="simulate_color_space" msgid="1206503300335835151">"Simulate colour space"</string>
+ <string name="simulate_color_space" msgid="1206503300335835151">"Simulate color space"</string>
<string name="enable_opengl_traces_title" msgid="4638773318659125196">"Enable OpenGL traces"</string>
<string name="usb_audio_disable_routing" msgid="3367656923544254975">"Disable USB audio routing"</string>
<string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Disable automatic routing to USB audio peripherals"</string>
@@ -389,31 +389,31 @@
<string name="enable_gpu_debug_layers" msgid="4986675516188740397">"Enable GPU debug layers"</string>
<string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"Allow loading GPU debug layers for debug apps"</string>
<string name="enable_verbose_vendor_logging" msgid="1196698788267682072">"Enable verbose vendor logging"</string>
- <string name="enable_verbose_vendor_logging_summary" msgid="5426292185780393708">"Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery and/or use more storage."</string>
+ <string name="enable_verbose_vendor_logging_summary" msgid="5426292185780393708">"Include additional device-specific vendor logs in bug reports, which may contain private information, use more battery, and/or use more storage."</string>
<string name="window_animation_scale_title" msgid="5236381298376812508">"Window animation scale"</string>
<string name="transition_animation_scale_title" msgid="1278477690695439337">"Transition animation scale"</string>
<string name="animator_duration_scale_title" msgid="7082913931326085176">"Animator duration scale"</string>
<string name="overlay_display_devices_title" msgid="5411894622334469607">"Simulate secondary displays"</string>
<string name="debug_applications_category" msgid="5394089406638954196">"Apps"</string>
- <string name="immediately_destroy_activities" msgid="1826287490705167403">"Don\'t keep activities"</string>
+ <string name="immediately_destroy_activities" msgid="1826287490705167403">"Don’t keep activities"</string>
<string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Destroy every activity as soon as the user leaves it"</string>
<string name="app_process_limit_title" msgid="8361367869453043007">"Background process limit"</string>
<string name="show_all_anrs" msgid="9160563836616468726">"Show background ANRs"</string>
- <string name="show_all_anrs_summary" msgid="8562788834431971392">"Display App Not Responding dialogue for background apps"</string>
+ <string name="show_all_anrs_summary" msgid="8562788834431971392">"Display App Not Responding dialog for background apps"</string>
<string name="show_notification_channel_warnings" msgid="3448282400127597331">"Show notification channel warnings"</string>
<string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Displays on-screen warning when an app posts a notification without a valid channel"</string>
<string name="force_allow_on_external" msgid="9187902444231637880">"Force allow apps on external"</string>
<string name="force_allow_on_external_summary" msgid="8525425782530728238">"Makes any app eligible to be written to external storage, regardless of manifest values"</string>
- <string name="force_resizable_activities" msgid="7143612144399959606">"Force activities to be resizeable"</string>
- <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizeable for multi-window, regardless of manifest values."</string>
+ <string name="force_resizable_activities" msgid="7143612144399959606">"Force activities to be resizable"</string>
+ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Make all activities resizable for multi-window, regardless of manifest values."</string>
<string name="enable_freeform_support" msgid="7599125687603914253">"Enable freeform windows"</string>
<string name="enable_freeform_support_summary" msgid="1822862728719276331">"Enable support for experimental freeform windows."</string>
<string name="desktop_mode" msgid="2389067840550544462">"Desktop mode"</string>
<string name="local_backup_password_title" msgid="4631017948933578709">"Desktop backup password"</string>
- <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren\'t currently protected"</string>
+ <string name="local_backup_password_summary_none" msgid="7646898032616361714">"Desktop full backups aren’t currently protected"</string>
<string name="local_backup_password_summary_change" msgid="1707357670383995567">"Tap to change or remove the password for desktop full backups"</string>
<string name="local_backup_password_toast_success" msgid="4891666204428091604">"New backup password set"</string>
- <string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"New password and confirmation don\'t match"</string>
+ <string name="local_backup_password_toast_confirmation_mismatch" msgid="2994718182129097733">"New password and confirmation don’t match"</string>
<string name="local_backup_password_toast_validation_failure" msgid="714669442363647122">"Failure setting backup password"</string>
<string name="loading_injected_setting_summary" msgid="8394446285689070348">"Loading…"</string>
<string-array name="color_mode_names">
@@ -422,9 +422,9 @@
<item msgid="6564241960833766170">"Standard"</item>
</string-array>
<string-array name="color_mode_descriptions">
- <item msgid="6828141153199944847">"Enhanced colours"</item>
- <item msgid="4548987861791236754">"Natural colours as seen by the eye"</item>
- <item msgid="1282170165150762976">"Colours optimised for digital content"</item>
+ <item msgid="6828141153199944847">"Enhanced colors"</item>
+ <item msgid="4548987861791236754">"Natural colors as seen by the eye"</item>
+ <item msgid="1282170165150762976">"Colors optimized for digital content"</item>
</string-array>
<string name="inactive_apps_title" msgid="5372523625297212320">"Standby apps"</string>
<string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Inactive. Tap to toggle."</string>
@@ -441,15 +441,15 @@
<string name="select_webview_provider_title" msgid="3917815648099445503">"WebView implementation"</string>
<string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Set WebView implementation"</string>
<string name="select_webview_provider_toast_text" msgid="8512254949169359848">"This choice is no longer valid. Try again."</string>
- <string name="picture_color_mode" msgid="1013807330552931903">"Picture colour mode"</string>
+ <string name="picture_color_mode" msgid="1013807330552931903">"Picture color mode"</string>
<string name="picture_color_mode_desc" msgid="151780973768136200">"Use sRGB"</string>
<string name="daltonizer_mode_disabled" msgid="403424372812399228">"Disabled"</string>
<string name="daltonizer_mode_monochromacy" msgid="362060873835885014">"Monochromacy"</string>
<string name="daltonizer_mode_deuteranomaly" msgid="3507284319584683963">"Deuteranomaly (red-green)"</string>
<string name="daltonizer_mode_protanomaly" msgid="7805583306666608440">"Protanomaly (red-green)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="7135266249220732267">"Tritanomaly (blue-yellow)"</string>
- <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Colour correction"</string>
- <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Colour correction can be helpful when you want to:<br/> <ol> <li>&nbsp;See colours more accurately</li> <li>&nbsp;Remove colours to help you focus</li> </ol>"</string>
+ <string name="accessibility_display_daltonizer_preference_title" msgid="1810693571332381974">"Color correction"</string>
+ <string name="accessibility_display_daltonizer_preference_subtitle" msgid="1522101114585266455">"Color correction can be helpful when you want to:<br/> <ol> <li>&nbsp;See colors more accurately</li> <li>&nbsp;Remove colors to help you focus</li> </ol>"</string>
<string name="daltonizer_type_overridden" msgid="4509604753672535721">"Overridden by <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="power_remaining_settings_home_page" msgid="4885165789445462557">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> - <xliff:g id="TIME_STRING">%2$s</xliff:g>"</string>
<string name="power_remaining_duration_only" msgid="8264199158671531431">"About <xliff:g id="TIME_REMAINING">%1$s</xliff:g> left"</string>
@@ -476,7 +476,7 @@
<string name="power_remaining_duration_shutdown_imminent" product="device" msgid="4374784375644214578">"Device may shut down soon (<xliff:g id="LEVEL">%1$s</xliff:g>)"</string>
<string name="power_charging" msgid="6727132649743436802">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATE">%2$s</xliff:g>"</string>
<string name="power_remaining_charging_duration_only" msgid="8085099012811384899">"<xliff:g id="TIME">%1$s</xliff:g> left until full"</string>
- <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> – <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
+ <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> left until full"</string>
<string name="power_charging_limited" msgid="6971664137170239141">"<xliff:g id="LEVEL">%1$s</xliff:g> - Charging is paused"</string>
<string name="battery_info_status_unknown" msgid="268625384868401114">"Unknown"</string>
<string name="battery_info_status_charging" msgid="4279958015430387405">"Charging"</string>
@@ -487,9 +487,9 @@
<string name="battery_info_status_discharging" msgid="6962689305413556485">"Not charging"</string>
<string name="battery_info_status_not_charging" msgid="3371084153747234837">"Connected, not charging"</string>
<string name="battery_info_status_full" msgid="1339002294876531312">"Charged"</string>
- <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully charged"</string>
+ <string name="battery_info_status_full_charged" msgid="3536054261505567948">"Fully Charged"</string>
<string name="disabled_by_admin_summary_text" msgid="5343911767402923057">"Controlled by admin"</string>
- <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by restricted setting"</string>
+ <string name="disabled_by_app_ops_text" msgid="8373595926549098012">"Controlled by Restricted Setting"</string>
<string name="disabled" msgid="8017887509554714950">"Disabled"</string>
<string name="external_source_trusted" msgid="1146522036773132905">"Allowed"</string>
<string name="external_source_untrusted" msgid="5037891688911672227">"Not allowed"</string>
@@ -515,13 +515,13 @@
<string name="active_input_method_subtypes" msgid="4232680535471633046">"Active input methods"</string>
<string name="use_system_language_to_select_input_method_subtypes" msgid="4865195835541387040">"Use system languages"</string>
<string name="failed_to_open_app_settings_toast" msgid="764897252657692092">"Failed to open settings for <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
- <string name="ime_security_warning" msgid="6547562217880551450">"This input method may be able to collect all the text that you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
+ <string name="ime_security_warning" msgid="6547562217880551450">"This input method may be able to collect all the text you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
<string name="direct_boot_unaware_dialog_message" msgid="7845398276735021548">"Note: After a reboot, this app can\'t start until you unlock your phone"</string>
<string name="ims_reg_title" msgid="8197592958123671062">"IMS registration state"</string>
<string name="ims_reg_status_registered" msgid="884916398194885457">"Registered"</string>
<string name="ims_reg_status_not_registered" msgid="2989287366045704694">"Not registered"</string>
<string name="status_unavailable" msgid="5279036186589861608">"Unavailable"</string>
- <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomised"</string>
+ <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC is randomized"</string>
<string name="wifi_tether_connected_summary" msgid="5282919920463340158">"{count,plural, =0{0 device connected}=1{1 device connected}other{# devices connected}}"</string>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"More time."</string>
<string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"Less time."</string>
@@ -530,7 +530,7 @@
<string name="done" msgid="381184316122520313">"Done"</string>
<string name="alarms_and_reminders_label" msgid="6918395649731424294">"Alarms and reminders"</string>
<string name="alarms_and_reminders_switch_title" msgid="4939393911531826222">"Allow setting alarms and reminders"</string>
- <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarms and reminders"</string>
+ <string name="alarms_and_reminders_title" msgid="8819933264635406032">"Alarms & reminders"</string>
<string name="alarms_and_reminders_footer_title" msgid="6302587438389079695">"Allow this app to set alarms and schedule time-sensitive actions. This lets the app run in the background, which may use more battery.\n\nIf this permission is off, existing alarms and time-based events scheduled by this app won’t work."</string>
<string name="keywords_alarms_and_reminders" msgid="6633360095891110611">"schedule, alarm, reminder, clock"</string>
<string name="zen_mode_enable_dialog_turn_on" msgid="6418297231575050426">"Turn on"</string>
@@ -549,7 +549,7 @@
<string name="media_transfer_this_device_name" product="default" msgid="2357329267148436433">"This phone"</string>
<string name="media_transfer_this_device_name" product="tablet" msgid="3714653244000242800">"This tablet"</string>
<string name="media_transfer_this_phone" msgid="7194341457812151531">"This phone"</string>
- <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off and back on"</string>
+ <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Problem connecting. Turn device off & back on"</string>
<string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Wired audio device"</string>
<string name="help_label" msgid="3528360748637781274">"Help and feedback"</string>
<string name="storage_category" msgid="2287342585424631813">"Storage"</string>
@@ -558,23 +558,23 @@
<string name="shared_data_no_blobs_text" msgid="3108114670341737434">"There is no shared data for this user."</string>
<string name="shared_data_query_failure_text" msgid="3489828881998773687">"There was an error fetching shared data. Try again."</string>
<string name="blob_id_text" msgid="8680078988996308061">"Shared data ID: <xliff:g id="BLOB_ID">%d</xliff:g>"</string>
- <string name="blob_expires_text" msgid="7882727111491739331">"Expires on <xliff:g id="DATE">%s</xliff:g>"</string>
+ <string name="blob_expires_text" msgid="7882727111491739331">"Expires at <xliff:g id="DATE">%s</xliff:g>"</string>
<string name="shared_data_delete_failure_text" msgid="3842701391009628947">"There was an error deleting the shared data."</string>
<string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"There are no leases acquired for this shared data. Would you like to delete it?"</string>
<string name="accessor_info_title" msgid="8289823651512477787">"Apps sharing data"</string>
<string name="accessor_no_description_text" msgid="7510967452505591456">"No description provided by the app."</string>
- <string name="accessor_expires_text" msgid="4625619273236786252">"Lease expires on <xliff:g id="DATE">%s</xliff:g>"</string>
+ <string name="accessor_expires_text" msgid="4625619273236786252">"Lease expires at <xliff:g id="DATE">%s</xliff:g>"</string>
<string name="delete_blob_text" msgid="2819192607255625697">"Delete shared data"</string>
- <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Are you sure that you want to delete this shared data?"</string>
+ <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Are you sure you want to delete this shared data?"</string>
<string name="user_add_user_item_summary" msgid="5748424612724703400">"Users have their own apps and content"</string>
<string name="user_add_profile_item_summary" msgid="5418602404308968028">"You can restrict access to apps and content from your account"</string>
<string name="user_add_user_item_title" msgid="2394272381086965029">"User"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Restricted profile"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Add new user?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"You can share this device with other people by creating additional users. Each user has their own space, which they can customise with apps, wallpaper and so on. Users can also adjust device settings such as Wi‑Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user."</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"You can share this device with other people by creating additional users. Each user has their own space, which they can customize with apps, wallpaper, and so on. Users can also adjust device settings like Wi‑Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Set up user now?"</string>
- <string name="user_setup_dialog_message" msgid="269931619868102841">"Make sure that the person is available to take the device and set up their space."</string>
+ <string name="user_setup_dialog_message" msgid="269931619868102841">"Make sure the person is available to take the device and set up their space"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Set up profile now?"</string>
<string name="user_setup_button_setup_now" msgid="1708269547187760639">"Set up now"</string>
<string name="user_setup_button_setup_later" msgid="8712980133555493516">"Not now"</string>
@@ -583,7 +583,7 @@
<string name="user_new_profile_name" msgid="2405500423304678841">"New profile"</string>
<string name="user_info_settings_title" msgid="6351390762733279907">"User info"</string>
<string name="profile_info_settings_title" msgid="105699672534365099">"Profile info"</string>
- <string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you\'ll need to set up a screen lock to protect your apps and personal data."</string>
+ <string name="user_need_lock_message" msgid="4311424336209509301">"Before you can create a restricted profile, you’ll need to set up a screen lock to protect your apps and personal data."</string>
<string name="user_set_lock_button" msgid="1427128184982594856">"Set lock"</string>
<string name="user_switch_to_user" msgid="6975428297154968543">"Switch to <xliff:g id="USER_NAME">%s</xliff:g>"</string>
<string name="creating_new_user_dialog_message" msgid="7232880257538970375">"Creating new user…"</string>
@@ -626,7 +626,7 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Disabled"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
- <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphones"</string>
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"On"</string>
<string name="wifi_hotspot_switch_off_text" msgid="7245567251496959764">"Off"</string>
<string name="carrier_network_change_mode" msgid="4257621815706644026">"Carrier network changing"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 0731b6b..216fd61 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -242,7 +242,7 @@
<string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver y usar los dispositivos disponibles, activa la depuración inalámbrica"</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Vincular dispositivo mediante código QR"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Vincular dispositivos nuevos mediante escáner de código QR"</string>
- <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Vincular dispositivo con código de sincronización"</string>
+ <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Vincular dispositivo con un código de vinculación"</string>
<string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Vincular dispositivos nuevos mediante código de seis dígitos"</string>
<string name="adb_paired_devices_title" msgid="5268997341526217362">"Dispositivos vinculados"</string>
<string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Conectado actualmente"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 4ef4ee6..20e8db6 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -184,7 +184,7 @@
<string name="launch_defaults_some" msgid="3631650616557252926">"कुछ डिफ़ॉल्ट सेट हैं"</string>
<string name="launch_defaults_none" msgid="8049374306261262709">"कोई डिफ़ॉल्ट सेट नहीं है"</string>
<string name="tts_settings" msgid="8130616705989351312">"लेख से बोली सेटिंग"</string>
- <string name="tts_settings_title" msgid="7602210956640483039">"लिखाई को बोली में बदलना"</string>
+ <string name="tts_settings_title" msgid="7602210956640483039">"लिखाई को बोली में बदलने की सुविधा"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"बोली दर"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"बोलने की गति तय करें"</string>
<string name="tts_default_pitch_title" msgid="6988592215554485479">"पिच"</string>
diff --git a/packages/SettingsLib/res/values-nb/arrays.xml b/packages/SettingsLib/res/values-nb/arrays.xml
index 7e65fa0..928ebc3 100644
--- a/packages/SettingsLib/res/values-nb/arrays.xml
+++ b/packages/SettingsLib/res/values-nb/arrays.xml
@@ -49,9 +49,9 @@
<item msgid="1999413958589971747">"Unngår dårlig tilkobling midlertidig"</item>
</string-array>
<string-array name="hdcp_checking_titles">
- <item msgid="2377230797542526134">"Kontrollér aldri"</item>
- <item msgid="3919638466823112484">"Kontrollér kun DRM-innhold"</item>
- <item msgid="9048424957228926377">"Kontrollér alltid"</item>
+ <item msgid="2377230797542526134">"Kontroller aldri"</item>
+ <item msgid="3919638466823112484">"Kontroller kun DRM-innhold"</item>
+ <item msgid="9048424957228926377">"Kontroller alltid"</item>
</string-array>
<string-array name="hdcp_checking_summaries">
<item msgid="4045840870658484038">"Bruk aldri HDCP-kontroll"</item>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 16ee8b8..4e3be22 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -437,7 +437,7 @@
<string name="transcode_notification" msgid="5560515979793436168">"Vis omkodingsvarsler"</string>
<string name="transcode_disable_cache" msgid="3160069309377467045">"Slå av omkodingsbuffer"</string>
<string name="runningservices_settings_title" msgid="6460099290493086515">"Aktive tjenester"</string>
- <string name="runningservices_settings_summary" msgid="1046080643262665743">"Se og kontrollér tjenester som kjører"</string>
+ <string name="runningservices_settings_summary" msgid="1046080643262665743">"Se og kontroller tjenester som kjører"</string>
<string name="select_webview_provider_title" msgid="3917815648099445503">"WebView-implementering"</string>
<string name="select_webview_provider_dialog_title" msgid="2444261109877277714">"Angi WebView-implementering"</string>
<string name="select_webview_provider_toast_text" msgid="8512254949169359848">"Dette valget er ikke gyldig lenger. Prøv på nytt."</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index f868c69..e66e3a1 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -278,7 +278,7 @@
<string name="mock_location_app_set" msgid="4706722469342913843">"Ứng dụng vị trí mô phỏng: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="debug_networking_category" msgid="6829757985772659599">"Mạng"</string>
<string name="wifi_display_certification" msgid="1805579519992520381">"Chứng nhận hiển thị không dây"</string>
- <string name="wifi_verbose_logging" msgid="1785910450009679371">"Bật ghi nhật ký chi tiết Wi‑Fi"</string>
+ <string name="wifi_verbose_logging" msgid="1785910450009679371">"Bật tính năng ghi nhật ký chi tiết Wi‑Fi"</string>
<string name="wifi_scan_throttling" msgid="2985624788509913617">"Hạn chế quét tìm Wi-Fi"</string>
<string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Tạo địa chỉ MAC ngẫu nhiên, không cố định mỗi khi kết nối Wi-Fi"</string>
<string name="mobile_data_always_on" msgid="8275958101875563572">"Dữ liệu di động luôn hoạt động"</string>
@@ -290,7 +290,7 @@
<string name="bluetooth_select_avrcp_version_dialog_title" msgid="7846922290083709633">"Chọn phiên bản Bluetooth AVRCP"</string>
<string name="bluetooth_select_map_version_string" msgid="526308145174175327">"Phiên bản Bluetooth MAP"</string>
<string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"Chọn phiên bản Bluetooth MAP"</string>
- <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Codec âm thanh Bluetooth"</string>
+ <string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"Bộ mã hoá và giải mã âm thanh qua Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"Kích hoạt chế độ chọn codec\nâm thanh Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"Tốc độ lấy mẫu âm thanh Bluetooth"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"Kích hoạt chế độ chọn codec\nâm thanh Bluetooth: Tần số lấy mẫu"</string>
@@ -384,7 +384,7 @@
<string name="window_blurs" msgid="6831008984828425106">"Cho phép làm mờ cửa sổ"</string>
<string name="force_msaa" msgid="4081288296137775550">"Bắt buộc 4x MSAA"</string>
<string name="force_msaa_summary" msgid="9070437493586769500">"Bật 4x MSAA trong ứng dụng OpenGL ES 2.0"</string>
- <string name="show_non_rect_clip" msgid="7499758654867881817">"Gỡ lỗi hoạt động của clip không phải là hình chữ nhật"</string>
+ <string name="show_non_rect_clip" msgid="7499758654867881817">"Gỡ lỗi hoạt động của đoạn không phải hình chữ nhật"</string>
<string name="track_frame_time" msgid="522674651937771106">"Kết xuất HWUI cấu hình"</string>
<string name="enable_gpu_debug_layers" msgid="4986675516188740397">"Bật lớp gỡ lỗi GPU"</string>
<string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"Cho phép tải lớp gỡ lỗi GPU cho ứng dụng gỡ lỗi"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 949bbfb..d1ac7d0 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1125,7 +1125,9 @@
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
<string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string>
<!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
- <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging is paused</string>
+ <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging paused</string>
+ <!-- [CHAR_LIMIT=80] Label for battery charging future pause -->
+ <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging to <xliff:g id="dock_defender_threshold">%2$s</xliff:g></string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 950ee21..9583a59 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -668,6 +668,12 @@
* @param bluetoothProfile the Bluetooth profile
*/
public void onActiveDeviceChanged(boolean isActive, int bluetoothProfile) {
+ if (BluetoothUtils.D) {
+ Log.d(TAG, "onActiveDeviceChanged: "
+ + "profile " + BluetoothProfile.getProfileName(bluetoothProfile)
+ + ", device " + mDevice.getAnonymizedAddress()
+ + ", isActive " + isActive);
+ }
boolean changed = false;
switch (bluetoothProfile) {
case BluetoothProfile.A2DP:
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index 5fa04f9..faea5b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -412,14 +412,13 @@
}
companion object {
- private const val TAG = "ThemedBatteryDrawable"
- private const val WIDTH = 12f
- private const val HEIGHT = 20f
+ const val WIDTH = 12f
+ const val HEIGHT = 20f
private const val CRITICAL_LEVEL = 15
// On a 12x20 grid, how wide to make the fill protection stroke.
// Scales when our size changes
private const val PROTECTION_STROKE_WIDTH = 3f
// Arbitrarily chosen for visibility at small sizes
- private const val PROTECTION_MIN_STROKE_WIDTH = 6f
+ const val PROTECTION_MIN_STROKE_WIDTH = 6f
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 8946516..92a938c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -53,6 +53,7 @@
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN,
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
+ Settings.Secure.CONTRAST_LEVEL,
Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET,
Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED,
Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index fbbdd32..b152209 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -175,6 +175,7 @@
VALIDATORS.put(Global.ADVANCED_BATTERY_USAGE_AMOUNT, PERCENTAGE_INTEGER_VALIDATOR);
VALIDATORS.put(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
+ VALIDATORS.put(Global.STYLUS_EVER_USED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.HAS_PAY_TOKENS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index cbf7953..eabf4cc 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -86,6 +86,7 @@
VALIDATORS.put(Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.CONTRAST_LEVEL, new InclusiveFloatRangeValidator(-1f, 1f));
VALIDATORS.put(
Secure.ACCESSIBILITY_CAPTIONING_PRESET,
new DiscreteValueValidator(new String[] {"-1", "0", "1", "2", "3", "4"}));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index aa3a983..a78faaf 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1756,6 +1756,9 @@
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
SecureSettingsProto.Accessibility.HIGH_TEXT_CONTRAST_ENABLED);
dumpSetting(s, p,
+ Settings.Secure.CONTRAST_LEVEL,
+ SecureSettingsProto.Accessibility.CONTRAST_LEVEL);
+ dumpSetting(s, p,
Settings.Secure.FONT_WEIGHT_ADJUSTMENT,
SecureSettingsProto.FONT_WEIGHT_ADJUSTMENT);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 8b9d118..fd0fa3a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -276,6 +276,7 @@
Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS,
Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS,
Settings.Global.STYLUS_HANDWRITING_ENABLED,
+ Settings.Global.STYLUS_EVER_USED,
Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT,
Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS,
Settings.Global.ENHANCED_4G_MODE_ENABLED,
diff --git a/packages/Shell/res/values-en-rCA/strings.xml b/packages/Shell/res/values-en-rCA/strings.xml
index 5462813..65ab725 100644
--- a/packages/Shell/res/values-en-rCA/strings.xml
+++ b/packages/Shell/res/values-en-rCA/strings.xml
@@ -28,7 +28,7 @@
<string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Select to share your bug report without a screenshot or wait for the screenshot to finish"</string>
<string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Tap to share your bug report without a screenshot or wait for the screenshot to finish"</string>
<string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Tap to share your bug report without a screenshot or wait for the screenshot to finish"</string>
- <string name="bugreport_confirm" msgid="5917407234515812495">"Bug reports contain data from the system\'s various log files, which may include data that you consider sensitive (such as app-usage and location data). Only share bug reports with people and apps that you trust."</string>
+ <string name="bugreport_confirm" msgid="5917407234515812495">"Bug reports contain data from the system\'s various log files, which may include data you consider sensitive (such as app-usage and location data). Only share bug reports with people and apps you trust."</string>
<string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Don\'t show again"</string>
<string name="bugreport_storage_title" msgid="5332488144740527109">"Bug reports"</string>
<string name="bugreport_unreadable_text" msgid="586517851044535486">"Bug report file could not be read"</string>
diff --git a/packages/SimAppDialog/res/values-en-rCA/strings.xml b/packages/SimAppDialog/res/values-en-rCA/strings.xml
index 1ddbaf9..7983c04 100644
--- a/packages/SimAppDialog/res/values-en-rCA/strings.xml
+++ b/packages/SimAppDialog/res/values-en-rCA/strings.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="app_name" msgid="8898068901680117589">"Sim app dialogue"</string>
+ <string name="app_name" msgid="8898068901680117589">"Sim App Dialog"</string>
<string name="install_carrier_app_title" msgid="334729104862562585">"Activate mobile service"</string>
<string name="install_carrier_app_description" msgid="4014303558674923797">"To get your new SIM working properly, you\'ll need to install the <xliff:g id="ID_1">%1$s</xliff:g> app"</string>
<string name="install_carrier_app_description_default" msgid="7356830245205847840">"To get your new SIM working properly, you\'ll need to install the carrier app"</string>
diff --git a/packages/SoundPicker/res/values-en-rCA/strings.xml b/packages/SoundPicker/res/values-en-rCA/strings.xml
index 4c237b9..b0708356 100644
--- a/packages/SoundPicker/res/values-en-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-en-rCA/strings.xml
@@ -23,7 +23,7 @@
<string name="add_alarm_text" msgid="3545497316166999225">"Add alarm"</string>
<string name="add_notification_text" msgid="4431129543300614788">"Add notification"</string>
<string name="delete_ringtone_text" msgid="201443984070732499">"Delete"</string>
- <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
- <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
+ <string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
+ <string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
</resources>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9d7a9e7..e624441 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -91,6 +91,7 @@
"SystemUIAnimationLib",
"SystemUIPluginLib",
"SystemUISharedLib",
+ "SystemUICustomizationLib",
"SystemUI-statsd",
"SettingsLib",
"androidx.core_core-ktx",
@@ -166,30 +167,20 @@
}
android_library {
- name: "SystemUI-tests",
+ name: "SystemUI-tests-base",
manifest: "tests/AndroidManifest-base.xml",
- additional_manifests: ["tests/AndroidManifest.xml"],
-
resource_dirs: [
"tests/res",
"res-product",
"res-keyguard",
"res",
],
- srcs: [
- "tests/src/**/*.kt",
- "tests/src/**/*.java",
- "src/**/*.kt",
- "src/**/*.java",
- "src/**/I*.aidl",
- ":ReleaseJavaFiles",
- ":SystemUI-tests-utils",
- ],
static_libs: [
"WifiTrackerLib",
"SystemUIAnimationLib",
"SystemUIPluginLib",
"SystemUISharedLib",
+ "SystemUICustomizationLib",
"SystemUI-statsd",
"SettingsLib",
"androidx.viewpager2_viewpager2",
@@ -222,9 +213,6 @@
"metrics-helper-lib",
"hamcrest-library",
"androidx.test.rules",
- "androidx.test.uiautomator",
- "mockito-target-extended-minus-junit4",
- "androidx.test.ext.junit",
"testables",
"truth-prebuilt",
"monet",
@@ -234,6 +222,27 @@
"LowLightDreamLib",
"motion_tool_lib",
],
+}
+
+android_library {
+ name: "SystemUI-tests",
+ manifest: "tests/AndroidManifest-base.xml",
+ additional_manifests: ["tests/AndroidManifest.xml"],
+ srcs: [
+ "tests/src/**/*.kt",
+ "tests/src/**/*.java",
+ "src/**/*.kt",
+ "src/**/*.java",
+ "src/**/I*.aidl",
+ ":ReleaseJavaFiles",
+ ":SystemUI-tests-utils",
+ ],
+ static_libs: [
+ "SystemUI-tests-base",
+ "androidx.test.uiautomator",
+ "mockito-target-extended-minus-junit4",
+ "androidx.test.ext.junit",
+ ],
libs: [
"android.test.runner",
"android.test.base",
@@ -247,6 +256,45 @@
plugins: ["dagger2-compiler"],
}
+android_app {
+ name: "SystemUIRobo-stub",
+ defaults: [
+ "platform_app_defaults",
+ "SystemUI_app_defaults",
+ ],
+ manifest: "tests/AndroidManifest-base.xml",
+ static_libs: [
+ "SystemUI-tests-base",
+ ],
+ aaptflags: [
+ "--extra-packages",
+ "com.android.systemui",
+ ],
+ dont_merge_manifests: true,
+ platform_apis: true,
+ system_ext_specific: true,
+ certificate: "platform",
+ privileged: true,
+ resource_dirs: [],
+}
+
+android_robolectric_test {
+ name: "SystemUiRoboTests",
+ srcs: [
+ "tests/robolectric/src/**/*.kt",
+ "tests/robolectric/src/**/*.java",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "truth-prebuilt",
+ ],
+ kotlincflags: ["-Xjvm-default=enable"],
+ instrumentation_for: "SystemUIRobo-stub",
+ java_resource_dirs: ["tests/robolectric/config"],
+}
+
// Opt-out config for optimizing the SystemUI target using R8.
// Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
// `SYSTEMUI_OPTIMIZE_JAVA := false`.
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4267ba2..844e88a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -126,6 +126,9 @@
<uses-permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES" />
<uses-permission android:name="android.permission.INPUT_CONSUMER" />
+ <!-- DeviceStateManager -->
+ <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE" />
+
<!-- DreamManager -->
<uses-permission android:name="android.permission.READ_DREAM_STATE" />
<uses-permission android:name="android.permission.WRITE_DREAM_STATE" />
@@ -195,6 +198,9 @@
<permission android:name="com.android.systemui.permission.FLAGS"
android:protectionLevel="signature" />
+ <permission android:name="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
+ android:protectionLevel="signature|privileged" />
+
<!-- Adding Quick Settings tiles -->
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
@@ -976,5 +982,12 @@
<action android:name="com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG" />
</intent-filter>
</receiver>
+
+ <provider
+ android:authorities="com.android.systemui.keyguard.quickaffordance"
+ android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
+ android:exported="true"
+ android:permission="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
+ />
</application>
</manifest>
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index f7bcf1f..e6ac48f 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -34,10 +34,28 @@
"res",
],
- static_libs: [
- "PluginCoreLib",
- ],
+ static_libs: ["androidx.core_core-animation-nodeps"],
manifest: "AndroidManifest.xml",
kotlincflags: ["-Xjvm-default=all"],
}
+
+android_test {
+ name: "SystemUIAnimationLibTests",
+
+ static_libs: [
+ "SystemUIAnimationLib",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "testables",
+ ],
+ libs: [
+ "android.test.base",
+ ],
+ srcs: [
+ "**/*.java",
+ "**/*.kt",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+ test_suites: ["general-tests"],
+}
diff --git a/packages/SystemUI/animation/TEST_MAPPING b/packages/SystemUI/animation/TEST_MAPPING
new file mode 100644
index 0000000..3dc8510
--- /dev/null
+++ b/packages/SystemUI/animation/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "SystemUIAnimationLibTests"
+ }
+ ]
+}
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 fdfad2b..54aa351 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -75,7 +75,7 @@
*/
interface Controller {
/** The [ViewRootImpl] of this controller. */
- val viewRoot: ViewRootImpl
+ val viewRoot: ViewRootImpl?
/**
* The identity object of the source animated by this controller. This animator will ensure
@@ -807,15 +807,17 @@
* inversely, removed from the overlay when the source is moved back to its original position).
*/
private fun synchronizeNextDraw(then: () -> Unit) {
- if (forceDisableSynchronization) {
- // Don't synchronize when inside an automated test.
+ val controllerRootView = controller.viewRoot?.view
+ if (forceDisableSynchronization || controllerRootView == null) {
+ // Don't synchronize when inside an automated test or if the controller root view is
+ // detached.
then()
return
}
- ViewRootSync.synchronizeNextDraw(controller.viewRoot.view, decorView, then)
+ ViewRootSync.synchronizeNextDraw(controllerRootView, decorView, then)
decorView.invalidate()
- controller.viewRoot.view.invalidate()
+ controllerRootView.invalidate()
}
private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
index 8063483..9dbb920 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Interpolators.java
@@ -27,7 +27,10 @@
import android.view.animation.PathInterpolator;
/**
- * Utility class to receive interpolators from
+ * Utility class to receive interpolators from.
+ *
+ * Make sure that changes made to this class are also reflected in {@link InterpolatorsAndroidX}.
+ * Please consider using the androidx dependencies featuring better testability altogether.
*/
public class Interpolators {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
new file mode 100644
index 0000000..8da87feb
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/InterpolatorsAndroidX.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation;
+
+import android.graphics.Path;
+import android.util.MathUtils;
+
+import androidx.core.animation.AccelerateDecelerateInterpolator;
+import androidx.core.animation.AccelerateInterpolator;
+import androidx.core.animation.BounceInterpolator;
+import androidx.core.animation.DecelerateInterpolator;
+import androidx.core.animation.Interpolator;
+import androidx.core.animation.LinearInterpolator;
+import androidx.core.animation.PathInterpolator;
+
+/**
+ * Utility class to receive interpolators from. (androidx compatible version)
+ *
+ * This is the androidx compatible version of {@link Interpolators}. Make sure that changes made to
+ * this class are also reflected in {@link Interpolators}.
+ *
+ * Using the androidx versions of {@link androidx.core.animation.ValueAnimator} or
+ * {@link androidx.core.animation.ObjectAnimator} improves animation testability. This file provides
+ * the androidx compatible versions of the interpolators defined in {@link Interpolators}.
+ * AnimatorTestRule can be used in Tests to manipulate the animation under test (e.g. artificially
+ * advancing the time).
+ */
+public class InterpolatorsAndroidX {
+
+ /*
+ * ============================================================================================
+ * Emphasized interpolators.
+ * ============================================================================================
+ */
+
+ /**
+ * The default emphasized interpolator. Used for hero / emphasized movement of content.
+ */
+ public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+ /**
+ * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
+ * is disappearing e.g. when moving off screen.
+ */
+ public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
+ 0.3f, 0f, 0.8f, 0.15f);
+
+ /**
+ * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
+ * is appearing e.g. when coming from off screen
+ */
+ public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+ 0.05f, 0.7f, 0.1f, 1f);
+
+
+ /*
+ * ============================================================================================
+ * Standard interpolators.
+ * ============================================================================================
+ */
+
+ /**
+ * The standard interpolator that should be used on every normal animation
+ */
+ public static final Interpolator STANDARD = new PathInterpolator(
+ 0.2f, 0f, 0f, 1f);
+
+ /**
+ * The standard accelerating interpolator that should be used on every regular movement of
+ * content that is disappearing e.g. when moving off screen.
+ */
+ public static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(
+ 0.3f, 0f, 1f, 1f);
+
+ /**
+ * The standard decelerating interpolator that should be used on every regular movement of
+ * content that is appearing e.g. when coming from off screen.
+ */
+ public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(
+ 0f, 0f, 0f, 1f);
+
+ /*
+ * ============================================================================================
+ * Legacy
+ * ============================================================================================
+ */
+
+ /**
+ * The default legacy interpolator as defined in Material 1. Also known as FAST_OUT_SLOW_IN.
+ */
+ public static final Interpolator LEGACY = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+
+ /**
+ * The default legacy accelerating interpolator as defined in Material 1.
+ * Also known as FAST_OUT_LINEAR_IN.
+ */
+ public static final Interpolator LEGACY_ACCELERATE = new PathInterpolator(0.4f, 0f, 1f, 1f);
+
+ /**
+ * The default legacy decelerating interpolator as defined in Material 1.
+ * Also known as LINEAR_OUT_SLOW_IN.
+ */
+ public static final Interpolator LEGACY_DECELERATE = new PathInterpolator(0f, 0f, 0.2f, 1f);
+
+ /**
+ * Linear interpolator. Often used if the interpolator is for different properties who need
+ * different interpolations.
+ */
+ public static final Interpolator LINEAR = new LinearInterpolator();
+
+ /*
+ * ============================================================================================
+ * Custom interpolators
+ * ============================================================================================
+ */
+
+ public static final Interpolator FAST_OUT_SLOW_IN = LEGACY;
+ public static final Interpolator FAST_OUT_LINEAR_IN = LEGACY_ACCELERATE;
+ public static final Interpolator LINEAR_OUT_SLOW_IN = LEGACY_DECELERATE;
+
+ /**
+ * Like {@link #FAST_OUT_SLOW_IN}, but used in case the animation is played in reverse (i.e. t
+ * goes from 1 to 0 instead of 0 to 1).
+ */
+ public static final Interpolator FAST_OUT_SLOW_IN_REVERSE =
+ new PathInterpolator(0.8f, 0f, 0.6f, 1f);
+ public static final Interpolator SLOW_OUT_LINEAR_IN = new PathInterpolator(0.8f, 0f, 1f, 1f);
+ public static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+ public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+ public static final Interpolator ACCELERATE = new AccelerateInterpolator();
+ public static final Interpolator ACCELERATE_DECELERATE = new AccelerateDecelerateInterpolator();
+ public static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f);
+ public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
+ public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
+ public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f,
+ 1.1f);
+ public static final Interpolator PANEL_CLOSE_ACCELERATED = new PathInterpolator(0.3f, 0, 0.5f,
+ 1);
+ public static final Interpolator BOUNCE = new BounceInterpolator();
+ /**
+ * For state transitions on the control panel that lives in GlobalActions.
+ */
+ public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
+ 1.0f);
+
+ /**
+ * Interpolator to be used when animating a move based on a click. Pair with enough duration.
+ */
+ public static final Interpolator TOUCH_RESPONSE =
+ new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
+ /**
+ * Like {@link #TOUCH_RESPONSE}, but used in case the animation is played in reverse (i.e. t
+ * goes from 1 to 0 instead of 0 to 1).
+ */
+ public static final Interpolator TOUCH_RESPONSE_REVERSE =
+ new PathInterpolator(0.9f, 0f, 0.7f, 1f);
+
+ /*
+ * ============================================================================================
+ * Functions / Utilities
+ * ============================================================================================
+ */
+
+ /**
+ * Calculate the amount of overshoot using an exponential falloff function with desired
+ * properties, where the overshoot smoothly transitions at the 1.0f boundary into the
+ * overshoot, retaining its acceleration.
+ *
+ * @param progress a progress value going from 0 to 1
+ * @param overshootAmount the amount > 0 of overshoot desired. A value of 0.1 means the max
+ * value of the overall progress will be at 1.1.
+ * @param overshootStart the point in (0,1] where the result should reach 1
+ * @return the interpolated overshoot
+ */
+ public static float getOvershootInterpolation(float progress, float overshootAmount,
+ float overshootStart) {
+ if (overshootAmount == 0.0f || overshootStart == 0.0f) {
+ throw new IllegalArgumentException("Invalid values for overshoot");
+ }
+ float b = MathUtils.log((overshootAmount + 1) / (overshootAmount)) / overshootStart;
+ return MathUtils.max(0.0f,
+ (float) (1.0f - Math.exp(-b * progress)) * (overshootAmount + 1.0f));
+ }
+
+ /**
+ * Similar to {@link #getOvershootInterpolation(float, float, float)} but the overshoot
+ * starts immediately here, instead of first having a section of non-overshooting
+ *
+ * @param progress a progress value going from 0 to 1
+ */
+ public static float getOvershootInterpolation(float progress) {
+ return MathUtils.max(0.0f, (float) (1.0f - Math.exp(-4 * progress)));
+ }
+
+ // Create the default emphasized interpolator
+ private static PathInterpolator createEmphasizedInterpolator() {
+ Path path = new Path();
+ // Doing the same as fast_out_extra_slow_in
+ path.moveTo(0f, 0f);
+ path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+ path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+ return new PathInterpolator(path);
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index f9c6841..43bfa74 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -320,9 +320,7 @@
counterWallpaper.cleanUp(finishTransaction)
// Release surface references now. This is apparently to free GPU
// memory while doing quick operations (eg. during CTS).
- for (i in info.changes.indices.reversed()) {
- info.changes[i].leash.release()
- }
+ info.releaseAllSurfaces()
for (i in leashMap.size - 1 downTo 0) {
leashMap.valueAt(i).release()
}
@@ -331,6 +329,7 @@
null /* wct */,
finishTransaction
)
+ finishTransaction.close()
} catch (e: RemoteException) {
Log.e(
"ActivityOptionsCompat",
@@ -364,6 +363,9 @@
) {
// TODO: hook up merge to recents onTaskAppeared if applicable. Until then,
// ignore any incoming merges.
+ // Clean up stuff though cuz GC takes too long for benchmark tests.
+ t.close()
+ info.releaseAllSurfaces()
}
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index ecee598..964ef8c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -28,7 +28,7 @@
private val source: View,
override val cuj: DialogCuj?,
) : DialogLaunchAnimator.Controller {
- override val viewRoot: ViewRootImpl
+ override val viewRoot: ViewRootImpl?
get() = source.viewRootImpl
override val sourceIdentity: Any = source
diff --git a/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt b/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
new file mode 100644
index 0000000..389eed0
--- /dev/null
+++ b/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.animation
+
+import androidx.test.filters.SmallTest
+import java.lang.reflect.Modifier
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class InterpolatorsAndroidXTest {
+
+ @Test
+ fun testInterpolatorsAndInterpolatorsAndroidXPublicMethodsAreEqual() {
+ assertEquals(
+ Interpolators::class.java.getPublicMethods(),
+ InterpolatorsAndroidX::class.java.getPublicMethods()
+ )
+ }
+
+ @Test
+ fun testInterpolatorsAndInterpolatorsAndroidXPublicFieldsAreEqual() {
+ assertEquals(
+ Interpolators::class.java.getPublicFields(),
+ InterpolatorsAndroidX::class.java.getPublicFields()
+ )
+ }
+
+ private fun <T> Class<T>.getPublicMethods() =
+ declaredMethods
+ .filter { Modifier.isPublic(it.modifiers) }
+ .map { it.toString().replace(name, "") }
+ .toSet()
+
+ private fun <T> Class<T>.getPublicFields() =
+ fields.filter { Modifier.isPublic(it.modifiers) }.map { it.name }.toSet()
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
index 1d808ba..74e6d85 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
@@ -59,11 +59,11 @@
!hasWorkerThreadAnnotation(context, node.getParentOfType(UClass::class.java))
) {
context.report(
- ISSUE,
- method,
- context.getLocation(node),
- "This method should be annotated with `@WorkerThread` because " +
- "it calls ${method.name}",
+ issue = ISSUE,
+ location = context.getLocation(node),
+ message =
+ "This method should be annotated with `@WorkerThread` because " +
+ "it calls ${method.name}",
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
index 1129929..344d0a3 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
@@ -52,10 +52,9 @@
val evaluator = context.evaluator
if (evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "`Context.${method.name}()` should be replaced with " +
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "`Context.${method.name}()` should be replaced with " +
"`BroadcastSender.${method.name}()`"
)
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
index bab76ab..14099eb 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
@@ -38,10 +38,9 @@
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Replace with injected `@Main Executor`."
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "Replace with injected `@Main Executor`."
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
index b622900..aa4b2f7 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
@@ -44,11 +44,11 @@
method.containingClass?.qualifiedName == CLASS_CONTEXT
) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Use `@Inject` to get system-level service handles instead of " +
- "`Context.getSystemService()`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message =
+ "Use `@Inject` to get system-level service handles instead of " +
+ "`Context.getSystemService()`"
)
} else if (
evaluator.isStatic(method) &&
@@ -56,10 +56,10 @@
method.containingClass?.qualifiedName == "android.accounts.AccountManager"
) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Replace `AccountManager.get()` with an injected instance of `AccountManager`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message =
+ "Replace `AccountManager.get()` with an injected instance of `AccountManager`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index 4ba3afc..5840e8f 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -38,10 +38,10 @@
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Register `BroadcastReceiver` using `BroadcastDispatcher` instead of `Context`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "Register `BroadcastReceiver` using `BroadcastDispatcher` instead " +
+ "of `Context`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
index 7be21a5..b15a41b 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
@@ -46,10 +46,10 @@
method.containingClass?.qualifiedName == "android.app.ActivityManager"
) {
context.report(
- ISSUE_SLOW_USER_ID_QUERY,
- method,
- context.getNameLocation(node),
- "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`"
+ issue = ISSUE_SLOW_USER_ID_QUERY,
+ location = context.getNameLocation(node),
+ message =
+ "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`"
)
}
if (
@@ -58,10 +58,9 @@
method.containingClass?.qualifiedName == "android.os.UserManager"
) {
context.report(
- ISSUE_SLOW_USER_INFO_QUERY,
- method,
- context.getNameLocation(node),
- "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`"
+ issue = ISSUE_SLOW_USER_INFO_QUERY,
+ location = context.getNameLocation(node),
+ message = "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
index 4b9aa13..bf02589 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -44,10 +44,9 @@
val evaluator = context.evaluator
if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
context.report(
- ISSUE,
- referenced,
- context.getNameLocation(reference),
- "Replace software bitmap with `Config.HARDWARE`"
+ issue = ISSUE,
+ location = context.getNameLocation(reference),
+ message = "Replace software bitmap with `Config.HARDWARE`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
index 1db0725..22f15bd 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
@@ -66,10 +66,9 @@
val subclassName = className.substring(CLASS_SETTINGS.length + 1)
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "`@Inject` a ${subclassName}Settings instead"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "`@Inject` a ${subclassName}Settings instead"
)
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index c35ac61..426211e 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -126,6 +126,32 @@
}
@Test
+ fun testSuppressUnbindService() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.content.ServiceConnection;
+
+ @SuppressLint("BindServiceOnMainThread")
+ public class TestClass {
+ public void unbind(Context context, ServiceConnection connection) {
+ context.unbindService(connection);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceOnMainThreadDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testWorkerMethod() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 376acb5..30b68f7 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -129,6 +129,34 @@
}
@Test
+ fun testSuppressSendBroadcastInActivity() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.app.Activity;
+ import android.os.UserHandle;
+
+ public class TestClass {
+ @SuppressWarnings("BroadcastSentViaContext")
+ public void send(Activity activity) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
+ }
+
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BroadcastSentViaContextDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testSendBroadcastInBroadcastSender() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index 301c338..ed3d14a 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -61,6 +61,32 @@
}
@Test
+ fun testSuppressGetMainThreadHandler() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.Handler;
+
+ @SuppressWarnings("NonInjectedMainThread")
+ public class TestClass {
+ public void test(Context context) {
+ Handler mainThreadHandler = context.getMainThreadHandler();
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedMainThreadDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testGetMainLooper() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index 0a74bfc..846129a 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -91,6 +91,32 @@
}
@Test
+ fun testSuppressGetServiceWithClass() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.UserManager;
+
+ public class TestClass {
+ @SuppressLint("NonInjectedService")
+ public void getSystemServiceWithoutDagger(Context context) {
+ context.getSystemService(UserManager.class);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedServiceDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testGetAccountManager() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 9ed7aa0..0ac8f8e 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -63,6 +63,34 @@
}
@Test
+ fun testSuppressRegisterReceiver() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+ import android.content.IntentFilter;
+
+ @SuppressWarnings("RegisterReceiverViaContext")
+ public class TestClass {
+ public void bind(Context context, BroadcastReceiver receiver,
+ IntentFilter filter) {
+ context.registerReceiver(receiver, filter, 0);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testRegisterReceiverAsUser() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 54cac7b..34a4249 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -76,7 +76,7 @@
import android.os.UserManager;
public class TestClass {
- public void slewlyGetUserInfo(UserManager userManager) {
+ public void slowlyGetUserInfo(UserManager userManager) {
userManager.getUserInfo();
}
}
@@ -101,6 +101,34 @@
}
@Test
+ fun testSuppressGetUserInfo() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.os.UserManager;
+
+ public class TestClass {
+ @SuppressWarnings("SlowUserInfoQuery")
+ public void slowlyGetUserInfo(UserManager userManager) {
+ userManager.getUserInfo();
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(
+ SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
+ SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
+ )
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testUserTrackerGetUserId() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index c632636..34becc6 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -63,6 +63,31 @@
}
@Test
+ fun testSuppressSoftwareBitmap() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ import android.graphics.Bitmap;
+
+ @SuppressWarnings("SoftwareBitmap")
+ public class TestClass {
+ public void test() {
+ Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testHardwareBitmap() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
index b83ed70..efe4c90 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -28,7 +28,7 @@
override fun getIssues(): List<Issue> = listOf(StaticSettingsProviderDetector.ISSUE)
@Test
- fun testGetServiceWithString() {
+ fun testSuppressGetServiceWithString() {
lint()
.files(
TestFiles.java(
@@ -204,5 +204,34 @@
)
}
+ @Test
+ fun testGetServiceWithString() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+
+ import android.provider.Settings;
+ import android.provider.Settings.Global;
+ import android.provider.Settings.Secure;
+
+ public class TestClass {
+ @SuppressWarnings("StaticSettingsProvider")
+ public void getSystemServiceWithoutDagger(Context context) {
+ final ContentResolver cr = mContext.getContentResolver();
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(StaticSettingsProviderDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
private val stubs = androidStubs
}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
index 50c3d7e..d6db574 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
@@ -262,7 +262,7 @@
private fun dialogController(cuj: DialogCuj?): DialogLaunchAnimator.Controller {
return object : DialogLaunchAnimator.Controller {
- override val viewRoot: ViewRootImpl = composeViewRoot.viewRootImpl
+ override val viewRoot: ViewRootImpl? = composeViewRoot.viewRootImpl
override val sourceIdentity: Any = this@ExpandableControllerImpl
override val cuj: DialogCuj? = cuj
diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml
index eada40e..278a89f 100644
--- a/packages/SystemUI/compose/features/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/AndroidManifest.xml
@@ -34,6 +34,11 @@
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove" />
+ <provider android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
+ android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
<provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
android:authorities="com.android.systemui.test.keyguard.clock.disabled"
android:enabled="false"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
deleted file mode 100644
index 4d94bab..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.user.ui.compose
-
-import android.graphics.drawable.Drawable
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.DropdownMenu
-import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.graphics.painter.ColorPainter
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.core.graphics.drawable.toBitmap
-import com.android.systemui.common.ui.compose.load
-import com.android.systemui.compose.SysUiOutlinedButton
-import com.android.systemui.compose.SysUiTextButton
-import com.android.systemui.compose.features.R
-import com.android.systemui.compose.theme.LocalAndroidColorScheme
-import com.android.systemui.user.ui.viewmodel.UserActionViewModel
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
-import java.lang.Integer.min
-import kotlin.math.ceil
-
-@Composable
-fun UserSwitcherScreen(
- viewModel: UserSwitcherViewModel,
- onFinished: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- val isFinishRequested: Boolean by viewModel.isFinishRequested.collectAsState(false)
- val users: List<UserViewModel> by viewModel.users.collectAsState(emptyList())
- val maxUserColumns: Int by viewModel.maximumUserColumns.collectAsState(1)
- val menuActions: List<UserActionViewModel> by viewModel.menu.collectAsState(emptyList())
- val isOpenMenuButtonVisible: Boolean by viewModel.isOpenMenuButtonVisible.collectAsState(false)
- val isMenuVisible: Boolean by viewModel.isMenuVisible.collectAsState(false)
-
- UserSwitcherScreenStateless(
- isFinishRequested = isFinishRequested,
- users = users,
- maxUserColumns = maxUserColumns,
- menuActions = menuActions,
- isOpenMenuButtonVisible = isOpenMenuButtonVisible,
- isMenuVisible = isMenuVisible,
- onMenuClosed = viewModel::onMenuClosed,
- onOpenMenuButtonClicked = viewModel::onOpenMenuButtonClicked,
- onCancelButtonClicked = viewModel::onCancelButtonClicked,
- onFinished = {
- onFinished()
- viewModel.onFinished()
- },
- modifier = modifier,
- )
-}
-
-@Composable
-private fun UserSwitcherScreenStateless(
- isFinishRequested: Boolean,
- users: List<UserViewModel>,
- maxUserColumns: Int,
- menuActions: List<UserActionViewModel>,
- isOpenMenuButtonVisible: Boolean,
- isMenuVisible: Boolean,
- onMenuClosed: () -> Unit,
- onOpenMenuButtonClicked: () -> Unit,
- onCancelButtonClicked: () -> Unit,
- onFinished: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- LaunchedEffect(isFinishRequested) {
- if (isFinishRequested) {
- onFinished()
- }
- }
-
- Box(
- modifier =
- modifier
- .fillMaxSize()
- .padding(
- horizontal = 60.dp,
- vertical = 40.dp,
- ),
- ) {
- UserGrid(
- users = users,
- maxUserColumns = maxUserColumns,
- modifier = Modifier.align(Alignment.Center),
- )
-
- Buttons(
- menuActions = menuActions,
- isOpenMenuButtonVisible = isOpenMenuButtonVisible,
- isMenuVisible = isMenuVisible,
- onMenuClosed = onMenuClosed,
- onOpenMenuButtonClicked = onOpenMenuButtonClicked,
- onCancelButtonClicked = onCancelButtonClicked,
- modifier = Modifier.align(Alignment.BottomEnd),
- )
- }
-}
-
-@Composable
-private fun UserGrid(
- users: List<UserViewModel>,
- maxUserColumns: Int,
- modifier: Modifier = Modifier,
-) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(44.dp),
- modifier = modifier,
- ) {
- val rowCount = ceil(users.size / maxUserColumns.toFloat()).toInt()
- (0 until rowCount).forEach { rowIndex ->
- Row(
- horizontalArrangement = Arrangement.spacedBy(64.dp),
- modifier = modifier,
- ) {
- val fromIndex = rowIndex * maxUserColumns
- val toIndex = min(users.size, (rowIndex + 1) * maxUserColumns)
- users.subList(fromIndex, toIndex).forEach { user ->
- UserItem(
- viewModel = user,
- )
- }
- }
- }
- }
-}
-
-@Composable
-private fun UserItem(
- viewModel: UserViewModel,
-) {
- val onClicked = viewModel.onClicked
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier =
- if (onClicked != null) {
- Modifier.clickable { onClicked() }
- } else {
- Modifier
- }
- .alpha(viewModel.alpha),
- ) {
- Box {
- UserItemBackground(modifier = Modifier.align(Alignment.Center).size(222.dp))
-
- UserItemIcon(
- image = viewModel.image,
- isSelectionMarkerVisible = viewModel.isSelectionMarkerVisible,
- modifier = Modifier.align(Alignment.Center).size(222.dp)
- )
- }
-
- // User name
- val text = viewModel.name.load()
- if (text != null) {
- // We use the box to center-align the text vertically as that is not possible with Text
- // alone.
- Box(
- modifier = Modifier.size(width = 222.dp, height = 48.dp),
- ) {
- Text(
- text = text,
- style = MaterialTheme.typography.titleLarge,
- color = colorResource(com.android.internal.R.color.system_neutral1_50),
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- modifier = Modifier.align(Alignment.Center),
- )
- }
- }
- }
-}
-
-@Composable
-private fun UserItemBackground(
- modifier: Modifier = Modifier,
-) {
- Image(
- painter = ColorPainter(LocalAndroidColorScheme.current.colorBackground),
- contentDescription = null,
- modifier = modifier.clip(CircleShape),
- )
-}
-
-@Composable
-private fun UserItemIcon(
- image: Drawable,
- isSelectionMarkerVisible: Boolean,
- modifier: Modifier = Modifier,
-) {
- Image(
- bitmap = image.toBitmap().asImageBitmap(),
- contentDescription = null,
- modifier =
- if (isSelectionMarkerVisible) {
- // Draws a ring
- modifier.border(
- width = 8.dp,
- color = LocalAndroidColorScheme.current.colorAccentPrimary,
- shape = CircleShape,
- )
- } else {
- modifier
- }
- .padding(16.dp)
- .clip(CircleShape)
- )
-}
-
-@Composable
-private fun Buttons(
- menuActions: List<UserActionViewModel>,
- isOpenMenuButtonVisible: Boolean,
- isMenuVisible: Boolean,
- onMenuClosed: () -> Unit,
- onOpenMenuButtonClicked: () -> Unit,
- onCancelButtonClicked: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- Row(
- modifier = modifier,
- ) {
- // Cancel button.
- SysUiTextButton(
- onClick = onCancelButtonClicked,
- ) {
- Text(stringResource(R.string.cancel))
- }
-
- // "Open menu" button.
- if (isOpenMenuButtonVisible) {
- Spacer(modifier = Modifier.width(8.dp))
- // To properly use a DropdownMenu in Compose, we need to wrap the button that opens it
- // and the menu itself in a Box.
- Box {
- SysUiOutlinedButton(
- onClick = onOpenMenuButtonClicked,
- ) {
- Text(stringResource(R.string.add))
- }
- Menu(
- viewModel = menuActions,
- isMenuVisible = isMenuVisible,
- onMenuClosed = onMenuClosed,
- )
- }
- }
- }
-}
-
-@Composable
-private fun Menu(
- viewModel: List<UserActionViewModel>,
- isMenuVisible: Boolean,
- onMenuClosed: () -> Unit,
- modifier: Modifier = Modifier,
-) {
- val maxItemWidth = LocalConfiguration.current.screenWidthDp.dp / 4
- DropdownMenu(
- expanded = isMenuVisible,
- onDismissRequest = onMenuClosed,
- modifier =
- modifier.background(
- color = MaterialTheme.colorScheme.inverseOnSurface,
- ),
- ) {
- viewModel.forEachIndexed { index, action ->
- MenuItem(
- viewModel = action,
- onClicked = { action.onClicked() },
- topPadding =
- if (index == 0) {
- 16.dp
- } else {
- 0.dp
- },
- bottomPadding =
- if (index == viewModel.size - 1) {
- 16.dp
- } else {
- 0.dp
- },
- modifier = Modifier.sizeIn(maxWidth = maxItemWidth),
- )
- }
- }
-}
-
-@Composable
-private fun MenuItem(
- viewModel: UserActionViewModel,
- onClicked: () -> Unit,
- topPadding: Dp,
- bottomPadding: Dp,
- modifier: Modifier = Modifier,
-) {
- val context = LocalContext.current
- val density = LocalDensity.current
-
- val icon =
- remember(viewModel.iconResourceId) {
- val drawable =
- checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId))
- val size = with(density) { 20.dp.toPx() }.toInt()
- drawable
- .toBitmap(
- width = size,
- height = size,
- )
- .asImageBitmap()
- }
-
- DropdownMenuItem(
- text = {
- Text(
- text = stringResource(viewModel.textResourceId),
- style = MaterialTheme.typography.bodyMedium,
- )
- },
- onClick = onClicked,
- leadingIcon = {
- Spacer(modifier = Modifier.width(10.dp))
- Image(
- bitmap = icon,
- contentDescription = null,
- )
- },
- modifier =
- modifier
- .heightIn(
- min = 56.dp,
- )
- .padding(
- start = 18.dp,
- end = 65.dp,
- top = topPadding,
- bottom = bottomPadding,
- ),
- )
-}
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
index e611e8b..979e1a0 100644
--- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
+++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
@@ -38,12 +38,18 @@
import platform.test.screenshot.getEmulatedDevicePathConfig
/** A rule for Compose screenshot diff tests. */
-class ComposeScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+class ComposeScreenshotTestRule(
+ emulationSpec: DeviceEmulationSpec,
+ assetPathRelativeToBuildRoot: String
+) : TestRule {
private val colorsRule = MaterialYouColorsRule()
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ SystemUIGoldenImagePathManager(
+ getEmulatedDevicePathConfig(emulationSpec),
+ assetPathRelativeToBuildRoot
+ )
)
private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
private val delegateRule =
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
new file mode 100644
index 0000000..dc450bb
--- /dev/null
+++ b/packages/SystemUI/customization/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2017 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUICustomizationLib",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ "src/**/*.aidl",
+ ],
+ static_libs: [
+ "PluginCoreLib",
+ "SystemUIAnimationLib",
+ "SystemUIPluginLib",
+ "SystemUIUnfoldLib",
+ "androidx.dynamicanimation_dynamicanimation",
+ "androidx.concurrent_concurrent-futures",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
+ "androidx.lifecycle_lifecycle-viewmodel-ktx",
+ "androidx.recyclerview_recyclerview",
+ "kotlinx_coroutines_android",
+ "kotlinx_coroutines",
+ "dagger2",
+ "jsr330",
+ ],
+ resource_dirs: [
+ "res",
+ ],
+ min_sdk_version: "current",
+ plugins: ["dagger2-compiler"],
+ kotlincflags: ["-Xjvm-default=enable"],
+}
diff --git a/packages/SystemUI/customization/AndroidManifest.xml b/packages/SystemUI/customization/AndroidManifest.xml
new file mode 100644
index 0000000..3277bff
--- /dev/null
+++ b/packages/SystemUI/customization/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.customization">
+
+</manifest>
diff --git a/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml b/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
similarity index 100%
rename from packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml
rename to packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/customization/res/layout/clock_default_large.xml
similarity index 100%
rename from packages/SystemUI/shared/res/layout/clock_default_large.xml
rename to packages/SystemUI/customization/res/layout/clock_default_large.xml
diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/customization/res/layout/clock_default_small.xml
similarity index 100%
rename from packages/SystemUI/shared/res/layout/clock_default_small.xml
rename to packages/SystemUI/customization/res/layout/clock_default_small.xml
diff --git a/packages/SystemUI/customization/res/values-h700dp/dimens.xml b/packages/SystemUI/customization/res/values-h700dp/dimens.xml
new file mode 100644
index 0000000..2a15981a
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-h700dp/dimens.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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
+ -->
+
+<resources>
+ <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
+ <dimen name="large_clock_text_size">170dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/customization/res/values-h800dp/dimens.xml b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
new file mode 100644
index 0000000..60afc8a
--- /dev/null
+++ b/packages/SystemUI/customization/res/values-h800dp/dimens.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ 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
+ -->
+
+<resources>
+ <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
+ <dimen name="large_clock_text_size">200dp</dimen>
+</resources>
diff --git a/packages/SystemUI/customization/res/values/attrs.xml b/packages/SystemUI/customization/res/values/attrs.xml
new file mode 100644
index 0000000..f9d66ee
--- /dev/null
+++ b/packages/SystemUI/customization/res/values/attrs.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+ the documentation output. To suppress comment lines from the documentation
+ output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+ <declare-styleable name="AnimatableClockView">
+ <attr name="dozeWeight" format="integer" />
+ <attr name="lockScreenWeight" format="integer" />
+ <attr name="chargeAnimationDelay" format="integer" />
+ </declare-styleable>
+</resources>
diff --git a/packages/SystemUI/shared/res/values/dimens.xml b/packages/SystemUI/customization/res/values/dimens.xml
similarity index 91%
rename from packages/SystemUI/shared/res/values/dimens.xml
rename to packages/SystemUI/customization/res/values/dimens.xml
index 8f90f0f..ba8f284 100644
--- a/packages/SystemUI/shared/res/values/dimens.xml
+++ b/packages/SystemUI/customization/res/values/dimens.xml
@@ -17,8 +17,8 @@
-->
<resources>
<!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
- <dimen name="large_clock_text_size">150sp</dimen>
- <dimen name="small_clock_text_size">86sp</dimen>
+ <dimen name="large_clock_text_size">150dp</dimen>
+ <dimen name="small_clock_text_size">86dp</dimen>
<!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
<item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
diff --git a/packages/SystemUI/shared/res/values/donottranslate.xml b/packages/SystemUI/customization/res/values/donottranslate.xml
similarity index 100%
rename from packages/SystemUI/shared/res/values/donottranslate.xml
rename to packages/SystemUI/customization/res/values/donottranslate.xml
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
similarity index 98%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 236aa66..22944b8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -33,9 +33,9 @@
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.TextAnimator
+import com.android.systemui.customization.R
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
-import com.android.systemui.shared.R
import java.io.PrintWriter
import java.util.Calendar
import java.util.Locale
@@ -613,7 +613,7 @@
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
// Constants for the animation
- private val MOVE_INTERPOLATOR = Interpolators.STANDARD
+ private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
// Calculate the positions of all of the digits...
// Offset each digit by, say, 0.1
@@ -637,7 +637,10 @@
// How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
// (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
// before moving).
- private const val MOVE_DIGIT_STEP = 0.1f
+ //
+ // The current specs dictate that each digit should have a 33ms gap between them. The
+ // overall time is 1s right now.
+ private const val MOVE_DIGIT_STEP = 0.033f
// Total available transition time for each digit, taking into account the step. If step is
// 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
similarity index 94%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 601cb66..59b4848 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -20,6 +20,7 @@
import android.os.Handler
import android.provider.Settings
import android.util.Log
+import androidx.annotation.OpenForTesting
import com.android.internal.annotations.Keep
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
@@ -27,7 +28,7 @@
import com.android.systemui.plugins.ClockProvider
import com.android.systemui.plugins.ClockProviderPlugin
import com.android.systemui.plugins.PluginListener
-import com.android.systemui.shared.plugins.PluginManager
+import com.android.systemui.plugins.PluginManager
import org.json.JSONObject
private val TAG = ClockRegistry::class.simpleName
@@ -41,6 +42,7 @@
val isEnabled: Boolean,
userHandle: Int,
defaultClockProvider: ClockProvider,
+ val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
) {
// Usually this would be a typealias, but a SAM provides better java interop
fun interface ClockChangeListener {
@@ -69,10 +71,13 @@
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
)
- ClockSetting.deserialize(json)?.clockId ?: DEFAULT_CLOCK_ID
+ if (json == null || json.isEmpty()) {
+ return fallbackClockId
+ }
+ ClockSetting.deserialize(json).clockId
} catch (ex: Exception) {
Log.e(TAG, "Failed to parse clock setting", ex)
- DEFAULT_CLOCK_ID
+ fallbackClockId
}
}
set(value) {
@@ -153,7 +158,8 @@
}
}
- fun getClocks(): List<ClockMetadata> {
+ @OpenForTesting
+ open fun getClocks(): List<ClockMetadata> {
if (!isEnabled) {
return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
similarity index 89%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index ca780c8..e1f2174 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -22,13 +22,13 @@
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
+import com.android.systemui.customization.R
import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockFaceController
import com.android.systemui.plugins.ClockFaceEvents
import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.shared.R
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
@@ -80,7 +80,7 @@
}
override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
- largeClock.recomputePadding()
+ largeClock.recomputePadding(null)
animations = DefaultClockAnimations(dozeFraction, foldFraction)
events.onColorPaletteChanged(resources)
events.onTimeZoneChanged(TimeZone.getDefault())
@@ -101,6 +101,7 @@
// MAGENTA is a placeholder, and will be assigned correctly in initialize
private var currentColor = Color.MAGENTA
private var isRegionDark = false
+ protected var targetRegion: Rect? = null
init {
view.setColors(currentColor, currentColor)
@@ -112,8 +113,20 @@
this@DefaultClockFaceController.isRegionDark = isRegionDark
updateColor()
}
+
+ override fun onTargetRegionChanged(targetRegion: Rect?) {
+ this@DefaultClockFaceController.targetRegion = targetRegion
+ recomputePadding(targetRegion)
+ }
+
+ override fun onFontSettingChanged(fontSizePx: Float) {
+ view.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSizePx)
+ recomputePadding(targetRegion)
+ }
}
+ open fun recomputePadding(targetRegion: Rect?) {}
+
fun updateColor() {
val color =
if (isRegionDark) {
@@ -128,14 +141,17 @@
currentColor = color
view.setColors(DOZE_COLOR, color)
- view.animateAppearOnLockscreen()
+ if (!animations.dozeState.isActive) {
+ view.animateAppearOnLockscreen()
+ }
}
}
inner class LargeClockFaceController(
view: AnimatableClockView,
) : DefaultClockFaceController(view) {
- fun recomputePadding() {
+ override fun recomputePadding(targetRegion: Rect?) {
+ // Ignore Target Region until top padding fixed in aod
val lp = view.getLayoutParams() as FrameLayout.LayoutParams
lp.topMargin = (-0.5f * view.bottom).toInt()
view.setLayoutParams(lp)
@@ -155,18 +171,6 @@
override fun onTimeZoneChanged(timeZone: TimeZone) =
clocks.forEach { it.onTimeZoneChanged(timeZone) }
- override fun onFontSettingChanged() {
- smallClock.view.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
- )
- largeClock.view.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
- )
- largeClock.recomputePadding()
- }
-
override fun onColorPaletteChanged(resources: Resources) {
largeClock.updateColor()
smallClock.updateColor()
@@ -188,7 +192,7 @@
dozeFraction: Float,
foldFraction: Float,
) : ClockAnimations {
- private val dozeState = AnimationState(dozeFraction)
+ internal val dozeState = AnimationState(dozeFraction)
private val foldState = AnimationState(foldFraction)
init {
@@ -229,7 +233,7 @@
get() = true
}
- private class AnimationState(
+ class AnimationState(
var fraction: Float,
) {
var isActive: Boolean = fraction > 0.5f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
similarity index 90%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 6627c5d..4c0504b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -17,23 +17,21 @@
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.customization.R
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProvider
-import com.android.systemui.shared.R
-import javax.inject.Inject
private val TAG = DefaultClockProvider::class.simpleName
const val DEFAULT_CLOCK_NAME = "Default Clock"
const val DEFAULT_CLOCK_ID = "DEFAULT"
/** Provides the default system clock */
-class DefaultClockProvider @Inject constructor(
+class DefaultClockProvider constructor(
val ctx: Context,
val layoutInflater: LayoutInflater,
- @Main val resources: Resources
+ val resources: Resources
) : ClockProvider {
override fun getClocks(): List<ClockMetadata> =
listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..f490c54
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.shared.quickaffordance.data.content
+
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+
+class FakeKeyguardQuickAffordanceProviderClient(
+ slots: List<KeyguardQuickAffordanceProviderClient.Slot> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ capacity = 1,
+ ),
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ capacity = 1,
+ ),
+ ),
+ affordances: List<KeyguardQuickAffordanceProviderClient.Affordance> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_1,
+ name = AFFORDANCE_1,
+ iconResourceId = 0,
+ ),
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_2,
+ name = AFFORDANCE_2,
+ iconResourceId = 0,
+ ),
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_3,
+ name = AFFORDANCE_3,
+ iconResourceId = 0,
+ ),
+ ),
+ flags: List<KeyguardQuickAffordanceProviderClient.Flag> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Flag(
+ name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED,
+ value = true,
+ )
+ ),
+) : KeyguardQuickAffordanceProviderClient {
+
+ private val slots = MutableStateFlow(slots)
+ private val affordances = MutableStateFlow(affordances)
+ private val flags = MutableStateFlow(flags)
+
+ private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+
+ override suspend fun insertSelection(slotId: String, affordanceId: String) {
+ val slotCapacity =
+ querySlots().find { it.id == slotId }?.capacity
+ ?: error("Slot with ID \"$slotId\" not found!")
+ val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+ while (affordances.size + 1 > slotCapacity) {
+ affordances.removeAt(0)
+ }
+ affordances.remove(affordanceId)
+ affordances.add(affordanceId)
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+ }
+
+ override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+ return slots.value
+ }
+
+ override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+ return flags.value
+ }
+
+ override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+ return slots.asStateFlow()
+ }
+
+ override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+ return flags.asStateFlow()
+ }
+
+ override suspend fun queryAffordances():
+ List<KeyguardQuickAffordanceProviderClient.Affordance> {
+ return affordances.value
+ }
+
+ override fun observeAffordances():
+ Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+ return affordances.asStateFlow()
+ }
+
+ override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return toSelectionList(selections.value, affordances.value)
+ }
+
+ override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+ return combine(selections, affordances) { selections, affordances ->
+ toSelectionList(selections, affordances)
+ }
+ }
+
+ override suspend fun deleteSelection(slotId: String, affordanceId: String) {
+ val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+ affordances.remove(affordanceId)
+
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+ }
+
+ override suspend fun deleteAllSelections(slotId: String) {
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = emptyList() }
+ }
+
+ override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable {
+ return BitmapDrawable()
+ }
+
+ fun setFlag(
+ name: String,
+ value: Boolean,
+ ) {
+ flags.value =
+ flags.value.toMutableList().apply {
+ removeIf { it.name == name }
+ add(KeyguardQuickAffordanceProviderClient.Flag(name = name, value = value))
+ }
+ }
+
+ fun setSlotCapacity(slotId: String, capacity: Int) {
+ slots.value =
+ slots.value.toMutableList().apply {
+ val index = indexOfFirst { it.id == slotId }
+ check(index != -1) { "Slot with ID \"$slotId\" doesn't exist!" }
+ set(
+ index,
+ KeyguardQuickAffordanceProviderClient.Slot(id = slotId, capacity = capacity)
+ )
+ }
+ }
+
+ fun addAffordance(affordance: KeyguardQuickAffordanceProviderClient.Affordance): Int {
+ affordances.value = affordances.value + listOf(affordance)
+ return affordances.value.size - 1
+ }
+
+ private fun toSelectionList(
+ selections: Map<String, List<String>>,
+ affordances: List<KeyguardQuickAffordanceProviderClient.Affordance>,
+ ): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return selections
+ .map { (slotId, affordanceIds) ->
+ affordanceIds.map { affordanceId ->
+ val affordanceName =
+ affordances.find { it.id == affordanceId }?.name
+ ?: error("No affordance with ID of \"$affordanceId\"!")
+ KeyguardQuickAffordanceProviderClient.Selection(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ affordanceName = affordanceName,
+ )
+ }
+ }
+ .flatten()
+ }
+
+ companion object {
+ const val AFFORDANCE_1 = "affordance_1"
+ const val AFFORDANCE_2 = "affordance_2"
+ const val AFFORDANCE_3 = "affordance_3"
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..3213b2e
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.shared.quickaffordance.data.content
+
+import android.annotation.SuppressLint
+import android.content.ContentValues
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import androidx.annotation.DrawableRes
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Client for using a content provider implementing the [Contract]. */
+interface KeyguardQuickAffordanceProviderClient {
+
+ /**
+ * Selects an affordance with the given ID for a slot on the lock screen with the given ID.
+ *
+ * Note that the maximum number of selected affordances on this slot is automatically enforced.
+ * Selecting a slot that is already full (e.g. already has a number of selected affordances at
+ * its maximum capacity) will automatically remove the oldest selected affordance before adding
+ * the one passed in this call. Additionally, selecting an affordance that's already one of the
+ * selected affordances on the slot will move the selected affordance to the newest location in
+ * the slot.
+ */
+ suspend fun insertSelection(
+ slotId: String,
+ affordanceId: String,
+ )
+
+ /** Returns all available slots supported by the device. */
+ suspend fun querySlots(): List<Slot>
+
+ /** Returns the list of flags. */
+ suspend fun queryFlags(): List<Flag>
+
+ /**
+ * Returns [Flow] for observing the collection of slots.
+ *
+ * @see [querySlots]
+ */
+ fun observeSlots(): Flow<List<Slot>>
+
+ /**
+ * Returns [Flow] for observing the collection of flags.
+ *
+ * @see [queryFlags]
+ */
+ fun observeFlags(): Flow<List<Flag>>
+
+ /**
+ * Returns all available affordances supported by the device, regardless of current slot
+ * placement.
+ */
+ suspend fun queryAffordances(): List<Affordance>
+
+ /**
+ * Returns [Flow] for observing the collection of affordances.
+ *
+ * @see [queryAffordances]
+ */
+ fun observeAffordances(): Flow<List<Affordance>>
+
+ /** Returns the current slot-affordance selections. */
+ suspend fun querySelections(): List<Selection>
+
+ /**
+ * Returns [Flow] for observing the collection of selections.
+ *
+ * @see [querySelections]
+ */
+ fun observeSelections(): Flow<List<Selection>>
+
+ /** Unselects an affordance with the given ID from the slot with the given ID. */
+ suspend fun deleteSelection(
+ slotId: String,
+ affordanceId: String,
+ )
+
+ /** Unselects all affordances from the slot with the given ID. */
+ suspend fun deleteAllSelections(
+ slotId: String,
+ )
+
+ /** Returns a [Drawable] with the given ID, loaded from the system UI package. */
+ suspend fun getAffordanceIcon(
+ @DrawableRes iconResourceId: Int,
+ tintColor: Int = Color.WHITE,
+ ): Drawable
+
+ /** Models a slot. A position that quick affordances can be positioned in. */
+ data class Slot(
+ /** Unique ID of the slot. */
+ val id: String,
+ /**
+ * The maximum number of quick affordances that are allowed to be positioned in this slot.
+ */
+ val capacity: Int,
+ )
+
+ /**
+ * Models a quick affordance. An action that can be selected by the user to appear in one or
+ * more slots on the lock screen.
+ */
+ data class Affordance(
+ /** Unique ID of the quick affordance. */
+ val id: String,
+ /** User-facing label for this affordance. */
+ val name: String,
+ /**
+ * Resource ID for the user-facing icon for this affordance. This resource is hosted by the
+ * System UI process so it must be used with
+ * `PackageManager.getResourcesForApplication(String)`.
+ */
+ val iconResourceId: Int,
+ /**
+ * Whether the affordance is enabled. Disabled affordances should be shown on the picker but
+ * should be rendered as "disabled". When tapped, the enablement properties should be used
+ * to populate UI that would explain to the user what to do in order to re-enable this
+ * affordance.
+ */
+ val isEnabled: Boolean = true,
+ /**
+ * If the affordance is disabled, this is a set of instruction messages to be shown to the
+ * user when the disabled affordance is selected. The instructions should help the user
+ * figure out what to do in order to re-neable this affordance.
+ */
+ val enablementInstructions: List<String>? = null,
+ /**
+ * If the affordance is disabled, this is a label for a button shown together with the set
+ * of instruction messages when the disabled affordance is selected. The button should help
+ * send the user to a flow that would help them achieve the instructions and re-enable this
+ * affordance.
+ *
+ * If `null`, the button should not be shown.
+ */
+ val enablementActionText: String? = null,
+ /**
+ * If the affordance is disabled, this is a "component name" of the format
+ * `packageName/action` to be used as an `Intent` for `startActivity` when the action button
+ * (shown together with the set of instruction messages when the disabled affordance is
+ * selected) is clicked by the user. The button should help send the user to a flow that
+ * would help them achieve the instructions and re-enable this affordance.
+ *
+ * If `null`, the button should not be shown.
+ */
+ val enablementActionComponentName: String? = null,
+ )
+
+ /** Models a selection of a quick affordance on a slot. */
+ data class Selection(
+ /** The unique ID of the slot. */
+ val slotId: String,
+ /** The unique ID of the quick affordance. */
+ val affordanceId: String,
+ /** The user-visible label for the quick affordance. */
+ val affordanceName: String,
+ )
+
+ /** Models a System UI flag. */
+ data class Flag(
+ /** The name of the flag. */
+ val name: String,
+ /** The value of the flag. */
+ val value: Boolean,
+ )
+}
+
+class KeyguardQuickAffordanceProviderClientImpl(
+ private val context: Context,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) : KeyguardQuickAffordanceProviderClient {
+
+ override suspend fun insertSelection(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.insert(
+ Contract.SelectionTable.URI,
+ ContentValues().apply {
+ put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
+ put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
+ }
+ )
+ }
+ }
+
+ override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.SlotTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
+ val capacityColumnIndex =
+ cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
+ if (idColumnIndex == -1 || capacityColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = cursor.getString(idColumnIndex),
+ capacity = cursor.getInt(capacityColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.FlagsTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val nameColumnIndex =
+ cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME)
+ val valueColumnIndex =
+ cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE)
+ if (nameColumnIndex == -1 || valueColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Flag(
+ name = cursor.getString(nameColumnIndex),
+ value = cursor.getInt(valueColumnIndex) == 1,
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+ return observeUri(Contract.SlotTable.URI).map { querySlots() }
+ }
+
+ override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+ return observeUri(Contract.FlagsTable.URI).map { queryFlags() }
+ }
+
+ override suspend fun queryAffordances():
+ List<KeyguardQuickAffordanceProviderClient.Affordance> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.AffordanceTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
+ val nameColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
+ val iconColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
+ val isEnabledColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.IS_ENABLED)
+ val enablementInstructionsColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS
+ )
+ val enablementActionTextColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT
+ )
+ val enablementComponentNameColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME
+ )
+ if (
+ idColumnIndex == -1 ||
+ nameColumnIndex == -1 ||
+ iconColumnIndex == -1 ||
+ isEnabledColumnIndex == -1 ||
+ enablementInstructionsColumnIndex == -1 ||
+ enablementActionTextColumnIndex == -1 ||
+ enablementComponentNameColumnIndex == -1
+ ) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = cursor.getString(idColumnIndex),
+ name = cursor.getString(nameColumnIndex),
+ iconResourceId = cursor.getInt(iconColumnIndex),
+ isEnabled = cursor.getInt(isEnabledColumnIndex) == 1,
+ enablementInstructions =
+ cursor
+ .getString(enablementInstructionsColumnIndex)
+ ?.split(
+ Contract.AffordanceTable
+ .ENABLEMENT_INSTRUCTIONS_DELIMITER
+ ),
+ enablementActionText =
+ cursor.getString(enablementActionTextColumnIndex),
+ enablementActionComponentName =
+ cursor.getString(enablementComponentNameColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeAffordances():
+ Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+ return observeUri(Contract.AffordanceTable.URI).map { queryAffordances() }
+ }
+
+ override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.SelectionTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val slotIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
+ val affordanceIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+ val affordanceNameColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
+ if (
+ slotIdColumnIndex == -1 ||
+ affordanceIdColumnIndex == -1 ||
+ affordanceNameColumnIndex == -1
+ ) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Selection(
+ slotId = cursor.getString(slotIdColumnIndex),
+ affordanceId = cursor.getString(affordanceIdColumnIndex),
+ affordanceName = cursor.getString(affordanceNameColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+ return observeUri(Contract.SelectionTable.URI).map { querySelections() }
+ }
+
+ override suspend fun deleteSelection(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
+ " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
+ arrayOf(
+ slotId,
+ affordanceId,
+ ),
+ )
+ }
+ }
+
+ override suspend fun deleteAllSelections(
+ slotId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ Contract.SelectionTable.Columns.SLOT_ID,
+ arrayOf(
+ slotId,
+ ),
+ )
+ }
+ }
+
+ @SuppressLint("UseCompatLoadingForDrawables")
+ override suspend fun getAffordanceIcon(
+ @DrawableRes iconResourceId: Int,
+ tintColor: Int,
+ ): Drawable {
+ return withContext(backgroundDispatcher) {
+ context.packageManager
+ .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
+ .getDrawable(iconResourceId, context.theme)
+ .apply { setTint(tintColor) }
+ }
+ }
+
+ private fun observeUri(
+ uri: Uri,
+ ): Flow<Unit> {
+ return callbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ context.contentResolver.registerContentObserver(
+ uri,
+ /* notifyForDescendants= */ true,
+ observer,
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+ .onStart { emit(Unit) }
+ }
+
+ companion object {
+ private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
new file mode 100644
index 0000000..17be74b
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.shared.quickaffordance.data.content
+
+import android.content.ContentResolver
+import android.net.Uri
+
+/** Contract definitions for querying content about keyguard quick affordances. */
+object KeyguardQuickAffordanceProviderContract {
+
+ const val AUTHORITY = "com.android.systemui.keyguard.quickaffordance"
+ const val PERMISSION = "android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
+
+ private val BASE_URI: Uri =
+ Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build()
+
+ /**
+ * Table for slots.
+ *
+ * Slots are positions where affordances can be placed on the lock screen. Affordances that are
+ * placed on slots are said to be "selected". The system supports the idea of multiple
+ * affordances per slot, though the implementation may limit the number of affordances on each
+ * slot.
+ *
+ * Supported operations:
+ * - Query - to know which slots are available, query the [SlotTable.URI] [Uri]. The result set
+ * will contain rows with the [SlotTable.Columns] columns.
+ */
+ object SlotTable {
+ const val TABLE_NAME = "slots"
+ val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+ object Columns {
+ /** String. Unique ID for this slot. */
+ const val ID = "id"
+ /** Integer. The maximum number of affordances that can be placed in the slot. */
+ const val CAPACITY = "capacity"
+ }
+ }
+
+ /**
+ * Table for affordances.
+ *
+ * Affordances are actions/buttons that the user can execute. They are placed on slots on the
+ * lock screen.
+ *
+ * Supported operations:
+ * - Query - to know about all the affordances that are available on the device, regardless of
+ * which ones are currently selected, query the [AffordanceTable.URI] [Uri]. The result set will
+ * contain rows, each with the columns specified in [AffordanceTable.Columns].
+ */
+ object AffordanceTable {
+ const val TABLE_NAME = "affordances"
+ val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+ const val ENABLEMENT_INSTRUCTIONS_DELIMITER = "]["
+ const val COMPONENT_NAME_SEPARATOR = "/"
+
+ object Columns {
+ /** String. Unique ID for this affordance. */
+ const val ID = "id"
+ /** String. User-visible name for this affordance. */
+ const val NAME = "name"
+ /**
+ * Integer. Resource ID for the drawable to load for this affordance. This is a resource
+ * ID from the system UI package.
+ */
+ const val ICON = "icon"
+ /** Integer. `1` if the affordance is enabled or `0` if it disabled. */
+ const val IS_ENABLED = "is_enabled"
+ /**
+ * String. List of strings, delimited by [ENABLEMENT_INSTRUCTIONS_DELIMITER] to be shown
+ * to the user if the affordance is disabled and the user selects the affordance. The
+ * first one is a title while the rest are the steps needed to re-enable the affordance.
+ */
+ const val ENABLEMENT_INSTRUCTIONS = "enablement_instructions"
+ /**
+ * String. Optional label for a button that, when clicked, opens a destination activity
+ * where the user can re-enable the disabled affordance.
+ */
+ const val ENABLEMENT_ACTION_TEXT = "enablement_action_text"
+ /**
+ * String. Optional package name and activity action string, delimited by
+ * [COMPONENT_NAME_SEPARATOR] to use with an `Intent` to start an activity that opens a
+ * destination where the user can re-enable the disabled affordance.
+ */
+ const val ENABLEMENT_COMPONENT_NAME = "enablement_action_intent"
+ }
+ }
+
+ /**
+ * Table for selections.
+ *
+ * Selections are pairs of slot and affordance IDs.
+ *
+ * Supported operations:
+ * - Insert - to insert an affordance and place it in a slot, insert values for the columns into
+ * the [SelectionTable.URI] [Uri]. The maximum capacity rule is enforced by the system.
+ * Selecting a new affordance for a slot that is already full will automatically remove the
+ * oldest affordance from the slot.
+ * - Query - to know which affordances are set on which slots, query the [SelectionTable.URI]
+ * [Uri]. The result set will contain rows, each of which with the columns from
+ * [SelectionTable.Columns].
+ * - Delete - to unselect an affordance, removing it from a slot, delete from the
+ * [SelectionTable.URI] [Uri], passing in values for each column.
+ */
+ object SelectionTable {
+ const val TABLE_NAME = "selections"
+ val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+ object Columns {
+ /** String. Unique ID for the slot. */
+ const val SLOT_ID = "slot_id"
+ /** String. Unique ID for the selected affordance. */
+ const val AFFORDANCE_ID = "affordance_id"
+ /** String. Human-readable name for the affordance. */
+ const val AFFORDANCE_NAME = "affordance_name"
+ }
+ }
+
+ /**
+ * Table for flags.
+ *
+ * Flags are key-value pairs.
+ *
+ * Supported operations:
+ * - Query - to know the values of flags, query the [FlagsTable.URI] [Uri]. The result set will
+ * contain rows, each of which with the columns from [FlagsTable.Columns].
+ */
+ object FlagsTable {
+ const val TABLE_NAME = "flags"
+ val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+ /**
+ * Flag denoting whether the customizable lock screen quick affordances feature is enabled.
+ */
+ const val FLAG_NAME_FEATURE_ENABLED = "is_feature_enabled"
+
+ object Columns {
+ /** String. Unique ID for the flag. */
+ const val NAME = "name"
+ /** Int. Value of the flag. `1` means `true` and `0` means `false`. */
+ const val VALUE = "value"
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt
similarity index 100%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index 95b986f..01d4f00 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -1,25 +1,73 @@
# Keyguard Quick Affordances
-These are interactive UI elements that appear at the bottom of the lockscreen when the device is
-locked. They allow the user to perform quick actions without unlocking their device. For example:
+Quick Affordances are interactive UI elements that appear on the lock screen when the device is
+locked. They allow the user to perform quick actions without necessarily unlocking their device. For example:
opening an screen that lets them control the smart devices in their home, access their touch-to-pay
-credit card, etc.
+credit card, turn on the flashlight, etc.
-## Adding a new Quick Affordance
-### Step 1: create a new quick affordance config
-* Create a new class under the [systemui/keyguard/domain/quickaffordance](../../src/com/android/systemui/keyguard/domain/quickaffordance) directory
-* Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
-* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
- * The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
- * It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
- * When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
-* Please include a unit test for your new implementation under [the correct directory](../../tests/src/com/android/systemui/keyguard/domain/quickaffordance)
+## Creating a new Quick Affordance
+All Quick Affordances are defined in System UI code.
-### Step 2: choose a position and priority
-* Add the new class as a dependency in the constructor of [KeyguardQuickAffordanceRegistry](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt)
-* Place the new class in one of the available positions in the `configsByPosition` property, note:
- * In each position, there is a list. The order matters. The order of that list is the priority order in which the framework considers each config. The first config whose state property returns `State.Visible` determines the button that is shown for that position
- * Please only add to one position. The framework treats each position individually and there is no good way to prevent the same config from making its button appear in more than one position at the same time
+To implement a new Quick Affordance, a developer may add a new implementation of `KeyguardQuickAffordanceConfig` in the `packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance` package/directory and add it to the set defined in `KeyguardDataQuickAffordanceModule`.
-### Step 3: manually verify the new quick affordance
-* Build and launch SysUI on a device
-* Verify that the quick affordance button for the new implementation is correctly visible and clicking it does the right thing
+Tests belong in the `packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance` package. This should be enough for the system to pick up the new config and make it available for selection by the user.
+
+## Slots
+"Slots" is what we call the position of Quick Affordances on the lock screen. Each slot has a unique ID and a capacity denoting how many Quick Affordances can be "placed" in that slot.
+
+By default, AOSP ships with a "bottom right" and a "bottom left" slot, each with a slot capacity of `1`, allowing only one Quick Affordance on each side of the lock screen.
+
+### Customizing Slots
+OEMs may choose to override the IDs and number of slots and/or override the default capacities. This can be achieved by overridding the `config_keyguardQuickAffordanceSlots` resource in `packages/SystemUI/res/values/config.xml`.
+
+### Default Quick Affordances
+OEMs may also choose to predefine default Quick Affordances for each slot. To achieve this, a developer may override the `config_keyguardQuickAffordanceDefaults` resource in `packages/SystemUI/res/values/config.xml`. Note that defaults only work until the user of the device selects a different quick affordance for that slot, even if they select the "None" option.
+
+## Selections
+"Selections" are many-to-many relationships between slots and quick affordances. We add a selection when the user selects a quick affordance for a specific slot. We remove a selection when the user un-selects a quick affordance in a slot or when the user selects an additional quick affordance for a slot that is already at capacity. The definition of each slot tells us the maximum number of quick affordances that may be selected for each slot.
+
+## Building a Quick affordance Picker Experience
+This section describes how to implement a potential picker, selector, or configuration experience for quick affordances.
+
+### Accessing Quick Affordance Data
+Quick Affordances structured data are exposed to other applications through the `KeyguardQuickAffordanceProvider` content provider which is owned by the System UI process.
+
+To access this content provider, applications must have the `android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES` permission which is a signature and privileged permission, limiting access to system apps or apps signed by the same signature as System UI. The `KeyguardQuickAffordanceProviderContract` file defines the content provider schema for consumers.
+
+Generally speaking, there are three important tables served by the content provider: `slots`, `affordances`, and `selections`. There is also a `flags` table, but that's not important and may be ignored.
+
+The `slots`, `affordances`, and `selections` tables may be queried using their `Uri` resulting with a `Cursor` where each row represents a slot, affordance, or selection, respectively. Note that the affordance list does not include the "None" option.
+
+### Modifying Quick Affordance Data
+The `selections` table accepts `insert` or `delete` operations to either add or remove a quick affordance on a slot.
+* To add a selection of a quick affordance on a slot, execute the `insert` operation on the `selections` table `Uri` and include the `slot_id` of the slot and `affordance_id` of the affordance, both in the `ContentValues`
+* To remove a selection of a specific quick affordance from a slot, execute the `delete` operation on the `selections` table `Uri` and include the `slot_id` of the slot and the `affordance_id` of the affordance to remove as the first and second selection arguments, respectively
+* To remove all selections of any currently-selected quick affordance from a specific slot, repeat the above, but omit the `affordance_id`
+
+### The Picker Experience
+A picker experience may:
+* Show the list of available slots based on the result of the `slots` table query
+* Show the list of available quick affordances on the device (regardless of selection) based on the result of the `affordances` table query
+* Show the quick affordances already selected for each slot based on the result of the `selections` table query
+* Select one quick affordance per slot at a time
+* Unselect an already-selected quick affordance from a slot
+* Unselect all already-selected quick affordances from a slot
+
+## Debugging
+To see the current state of the system, you can run `dumpsys`:
+
+```
+$ adb shell dumpsys activity service com.android.systemui/.SystemUIService KeyguardQuickAffordances
+```
+
+The output will spell out the current slot configuration, selections, and collection of available affordances, for example:
+```
+ KeyguardQuickAffordances:
+ ----------------------------------------------------------------------------
+ Slots & selections:
+ bottom_start: home (capacity = 1)
+ bottom_end is empty (capacity = 1)
+ Available affordances on device:
+ home ("Home")
+ wallet ("Wallet")
+ qr_code_scanner ("QR code scanner")
+```
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 9f211c9..b75c5c7 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -16,7 +16,6 @@
-packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
-packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
-packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
-packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSContainerController.kt
-packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
-packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
@@ -24,9 +23,9 @@
-packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
-packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+-packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+-packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+-packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt
-packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
@@ -754,7 +753,7 @@
-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/WifiRepositoryImplTest.kt
--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.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
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 89f5c2c..66e44b9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -70,10 +70,10 @@
}
/** Optional method for dumping debug information */
- fun dump(pw: PrintWriter) { }
+ fun dump(pw: PrintWriter) {}
/** Optional method for debug logging */
- fun setLogBuffer(logBuffer: LogBuffer) { }
+ fun setLogBuffer(logBuffer: LogBuffer) {}
}
/** Interface for a specific clock face version rendered by the clock */
@@ -88,40 +88,37 @@
/** Events that should call when various rendering parameters change */
interface ClockEvents {
/** Call every time tick */
- fun onTimeTick() { }
+ fun onTimeTick() {}
/** Call whenever timezone changes */
- fun onTimeZoneChanged(timeZone: TimeZone) { }
+ fun onTimeZoneChanged(timeZone: TimeZone) {}
/** Call whenever the text time format changes (12hr vs 24hr) */
- fun onTimeFormatChanged(is24Hr: Boolean) { }
+ fun onTimeFormatChanged(is24Hr: Boolean) {}
/** Call whenever the locale changes */
- fun onLocaleChanged(locale: Locale) { }
-
- /** Call whenever font settings change */
- fun onFontSettingChanged() { }
+ fun onLocaleChanged(locale: Locale) {}
/** Call whenever the color palette should update */
- fun onColorPaletteChanged(resources: Resources) { }
+ fun onColorPaletteChanged(resources: Resources) {}
}
/** Methods which trigger various clock animations */
interface ClockAnimations {
/** Runs an enter animation (if any) */
- fun enter() { }
+ fun enter() {}
/** Sets how far into AOD the device currently is. */
- fun doze(fraction: Float) { }
+ fun doze(fraction: Float) {}
/** Sets how far into the folding animation the device is. */
- fun fold(fraction: Float) { }
+ fun fold(fraction: Float) {}
/** Runs the battery animation (if any). */
- fun charge() { }
+ fun charge() {}
/** Move the clock, for example, if the notification tray appears in split-shade mode. */
- fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) { }
+ fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {}
/**
* Whether this clock has a custom position update animation. If true, the keyguard will call
@@ -135,11 +132,26 @@
/** Events that have specific data about the related face */
interface ClockFaceEvents {
/** Region Darkness specific to the clock face */
- fun onRegionDarknessChanged(isDark: Boolean) { }
+ fun onRegionDarknessChanged(isDark: Boolean) {}
+
+ /**
+ * Call whenever font settings change. Pass in a target font size in pixels. The specific clock
+ * design is allowed to ignore this target size on a case-by-case basis.
+ */
+ fun onFontSettingChanged(fontSizePx: Float) {}
+
+ /**
+ * Target region information for the clock face. For small clock, this will match the bounds of
+ * the parent view mostly, but have a target height based on the height of the default clock.
+ * For large clocks, the parent view is the entire device size, but most clocks will want to
+ * render within the centered targetRect to avoid obstructing other elements. The specified
+ * targetRegion is relative to the parent view.
+ */
+ fun onTargetRegionChanged(targetRegion: Rect?) {}
}
/** Some data about a clock design */
data class ClockMetadata(
val clockId: ClockId,
- val name: String
+ val name: String,
)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java
similarity index 93%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java
rename to packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java
index c89be86..80c64cd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManager.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginManager.java
@@ -12,12 +12,10 @@
* permissions and limitations under the License.
*/
-package com.android.systemui.shared.plugins;
+package com.android.systemui.plugins;
import android.text.TextUtils;
-import com.android.systemui.plugins.Plugin;
-import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.annotations.ProvidesInterface;
public interface PluginManager {
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 7538555..f96644f 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -22,6 +22,9 @@
-keep class ** extends androidx.preference.PreferenceFragment
-keep class com.android.systemui.tuner.*
+
+# The plugins subpackage acts as a shared library that might be referenced in
+# dynamically-loaded plugin APKs.
-keep class com.android.systemui.plugins.** {
*;
}
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml
new file mode 100644
index 0000000..e850d68
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_off.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#1f1f1f"
+ android:pathData="M8,22V11L6,8V2H18V8L16,11V22ZM12,15.5Q11.375,15.5 10.938,15.062Q10.5,14.625 10.5,14Q10.5,13.375 10.938,12.938Q11.375,12.5 12,12.5Q12.625,12.5 13.062,12.938Q13.5,13.375 13.5,14Q13.5,14.625 13.062,15.062Q12.625,15.5 12,15.5ZM8,5H16V4H8ZM16,7H8V7.4L10,10.4V20H14V10.4L16,7.4ZM12,12Z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml
new file mode 100644
index 0000000..91b9ae5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_flashlight_on.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#1f1f1f"
+ android:pathData="M6,5V2H18V5ZM12,15.5Q12.625,15.5 13.062,15.062Q13.5,14.625 13.5,14Q13.5,13.375 13.062,12.938Q12.625,12.5 12,12.5Q11.375,12.5 10.938,12.938Q10.5,13.375 10.5,14Q10.5,14.625 10.938,15.062Q11.375,15.5 12,15.5ZM8,22V11L6,8V7H18V8L16,11V22Z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index c297149..218c5cc 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -35,9 +35,9 @@
android:visibility="invisible" />
<FrameLayout
android:id="@+id/lockscreen_clock_view_large"
+ android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginTop="@dimen/keyguard_large_clock_top_margin"
android:clipChildren="false"
android:visibility="gone" />
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index c8ba237..a948c04 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -23,7 +23,7 @@
<string name="keyguard_enter_your_pin" msgid="5429932527814874032">"Enter your PIN"</string>
<string name="keyguard_enter_your_pattern" msgid="351503370332324745">"Enter your pattern"</string>
<string name="keyguard_enter_your_password" msgid="7225626204122735501">"Enter your password"</string>
- <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid card."</string>
+ <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid Card."</string>
<string name="keyguard_charged" msgid="5478247181205188995">"Charged"</string>
<string name="keyguard_plugged_in_wireless" msgid="2537874724955057383">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging wirelessly"</string>
<string name="keyguard_plugged_in_dock" msgid="2122073051904360987">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging"</string>
@@ -70,7 +70,7 @@
<string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Incorrect SIM PIN code you must now contact your carrier to unlock your device."</string>
<string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{Incorrect SIM PIN code, you have # remaining attempt before you must contact your carrier to unlock your device.}other{Incorrect SIM PIN code, you have # remaining attempts. }}"</string>
<string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"SIM is unusable. Contact your carrier."</string>
- <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code; you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string>
+ <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code, you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string>
<string name="kg_password_pin_failed" msgid="5136259126330604009">"SIM PIN operation failed!"</string>
<string name="kg_password_puk_failed" msgid="6778867411556937118">"SIM PUK operation failed!"</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Switch input method"</string>
@@ -83,12 +83,12 @@
<string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
- <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
+ <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognized"</string>
<string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"To use Face Unlock, turn on camera access in Settings"</string>
<string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Enter SIM PIN. You have # remaining attempt before you must contact your carrier to unlock your device.}other{Enter SIM PIN. You have # remaining attempts.}}"</string>
<string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{SIM is now disabled. Enter PUK code to continue. You have # remaining attempt before SIM becomes permanently unusable. Contact carrier for details.}other{SIM is now disabled. Enter PUK code to continue. You have # remaining attempts before SIM becomes permanently unusable. Contact carrier for details.}}"</string>
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
- <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
<string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
index 669f8fb..e5e17b7 100644
--- a/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-h650dp/dimens.xml
@@ -17,4 +17,7 @@
<resources>
<dimen name="widget_big_font_size">54dp</dimen>
+
+ <!-- Margin above the ambient indication container -->
+ <dimen name="ambient_indication_container_margin_top">10dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 579824a..57e5f8a 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -74,7 +74,7 @@
<string name="kg_password_pin_failed" msgid="5136259126330604009">"Bewerking met pincode voor simkaart is mislukt."</string>
<string name="kg_password_puk_failed" msgid="6778867411556937118">"Bewerking met pukcode voor simkaart is mislukt."</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Invoermethode wijzigen"</string>
- <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuigmodus"</string>
+ <string name="airplane_mode" msgid="2528005343938497866">"Vliegtuigmodus"</string>
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon vereist nadat het apparaat opnieuw is opgestart"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pincode vereist nadat het apparaat opnieuw is opgestart"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wachtwoord vereist nadat het apparaat opnieuw is opgestart"</string>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 3861d98..c5ffdc0 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -41,6 +41,9 @@
<!-- Minimum bottom margin under the security view -->
<dimen name="keyguard_security_view_bottom_margin">60dp</dimen>
+ <!-- Margin above the ambient indication container -->
+ <dimen name="ambient_indication_container_margin_top">0dp</dimen>
+
<dimen name="keyguard_eca_top_margin">18dp</dimen>
<dimen name="keyguard_eca_bottom_margin">12dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index a129fb6..da485a9 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -53,7 +53,7 @@
<string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
<!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. -->
- <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging is paused to protect battery</string>
+ <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging paused to protect battery</string>
<!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock. This is shown in small font at the bottom. -->
<string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index b86929e..04dffb6 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -25,11 +25,11 @@
</style>
<style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
<item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
- <item name="android:textSize">14sp</item>
+ <item name="android:textSize">16sp</item>
<item name="android:background">@drawable/kg_emergency_button_background</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:paddingLeft">12dp</item>
- <item name="android:paddingRight">12dp</item>
+ <item name="android:paddingLeft">26dp</item>
+ <item name="android:paddingRight">26dp</item>
<item name="android:stateListAnimator">@null</item>
</style>
<style name="NumPadKey" parent="Theme.SystemUI">
diff --git a/packages/SystemUI/res-product/values-en-rCA/strings.xml b/packages/SystemUI/res-product/values-en-rCA/strings.xml
index 04e63f5..131c42a 100644
--- a/packages/SystemUI/res-product/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-product/values-en-rCA/strings.xml
@@ -25,7 +25,7 @@
<string name="inattentive_sleep_warning_message" product="default" msgid="5693904520452332224">"The device will soon turn off; press to keep it on."</string>
<string name="keyguard_missing_sim_message" product="tablet" msgid="5018086454277963787">"No SIM card in tablet."</string>
<string name="keyguard_missing_sim_message" product="default" msgid="7053347843877341391">"No SIM card in phone."</string>
- <string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"PIN codes do not match"</string>
+ <string name="kg_invalid_confirm_pin_hint" product="default" msgid="6278551068943958651">"PIN codes does not match"</string>
<string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="302165994845009232">"You have incorrectly attempted to unlock the tablet <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, this tablet will be reset, which will delete all its data."</string>
<string name="kg_failed_attempts_almost_at_wipe" product="default" msgid="2594813176164266847">"You have incorrectly attempted to unlock the phone <xliff:g id="NUMBER_0">%1$d</xliff:g> times. After <xliff:g id="NUMBER_1">%2$d</xliff:g> more unsuccessful attempts, this phone will be reset, which will delete all its data."</string>
<string name="kg_failed_attempts_now_wiping" product="tablet" msgid="8710104080409538587">"You have incorrectly attempted to unlock the tablet <xliff:g id="NUMBER">%d</xliff:g> times. This tablet will be reset, which will delete all its data."</string>
diff --git a/packages/SystemUI/res/drawable/ic_ring_volume.xml b/packages/SystemUI/res/drawable/ic_ring_volume.xml
new file mode 100644
index 0000000..343fe5d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ring_volume.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:pathData="M11,7V2H13V7ZM17.6,9.85 L16.2,8.4 19.75,4.85 21.15,6.3ZM6.4,9.85 L2.85,6.3 4.25,4.85 7.8,8.4ZM12,12Q14.95,12 17.812,13.188Q20.675,14.375 22.9,16.75Q23.2,17.05 23.2,17.45Q23.2,17.85 22.9,18.15L20.6,20.4Q20.325,20.675 19.963,20.7Q19.6,20.725 19.3,20.5L16.4,18.3Q16.2,18.15 16.1,17.95Q16,17.75 16,17.5V14.65Q15.05,14.35 14.05,14.175Q13.05,14 12,14Q10.95,14 9.95,14.175Q8.95,14.35 8,14.65V17.5Q8,17.75 7.9,17.95Q7.8,18.15 7.6,18.3L4.7,20.5Q4.4,20.725 4.038,20.7Q3.675,20.675 3.4,20.4L1.1,18.15Q0.8,17.85 0.8,17.45Q0.8,17.05 1.1,16.75Q3.3,14.375 6.175,13.188Q9.05,12 12,12ZM6,15.35Q5.275,15.725 4.6,16.212Q3.925,16.7 3.2,17.3L4.2,18.3L6,16.9ZM18,15.4V16.9L19.8,18.3L20.8,17.35Q20.075,16.7 19.4,16.225Q18.725,15.75 18,15.4ZM6,15.35Q6,15.35 6,15.35Q6,15.35 6,15.35ZM18,15.4Q18,15.4 18,15.4Q18,15.4 18,15.4Z"
+ android:fillColor="?android:attr/colorPrimary"/>
+
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_ring_volume_off.xml b/packages/SystemUI/res/drawable/ic_ring_volume_off.xml
new file mode 100644
index 0000000..74f30d1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ring_volume_off.xml
@@ -0,0 +1,34 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
+<path
+ android:pathData="M0.8,4.2l8.1,8.1c-2.2,0.5 -5.2,1.6 -7.8,4.4c-0.4,0.4 -0.4,1 0,1.4l2.3,2.3c0.3,0.3 0.9,0.4 1.3,0.1l2.9,-2.2C7.8,18.1 8,17.8 8,17.5v-2.9c0.9,-0.3 1.7,-0.5 2.7,-0.6l8.5,8.5l1.4,-1.4L2.2,2.8L0.8,4.2z"
+ android:fillColor="?android:attr/colorPrimary"/>
+ <path
+ android:pathData="M11,2h2v5h-2z"
+ android:fillColor="?android:attr/colorPrimary"/>
+ <path
+ android:pathData="M21.2,6.3l-1.4,-1.4l-3.6,3.6l1.4,1.4C17.6,9.8 21,6.3 21.2,6.3z"
+ android:fillColor="?android:attr/colorPrimary"/>
+ <path
+ android:pathData="M22.9,16.7c-2.8,-3 -6.2,-4.1 -8.4,-4.5l7.2,7.2l1.3,-1.3C23.3,17.7 23.3,17.1 22.9,16.7z"
+ android:fillColor="?android:attr/colorPrimary"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_speaker_mute.xml b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
new file mode 100644
index 0000000..4e402cf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_mute.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorPrimary"
+ android:autoMirrored="true">
+ <path android:fillColor="#FFFFFFFF"
+ android:pathData="M19.8,22.6 L16.775,19.575Q16.15,19.975 15.45,20.263Q14.75,20.55 14,20.725V18.675Q14.35,18.55 14.688,18.425Q15.025,18.3 15.325,18.125L12,14.8V20L7,15H3V9H6.2L1.4,4.2L2.8,2.8L21.2,21.2ZM19.6,16.8 L18.15,15.35Q18.575,14.575 18.788,13.725Q19,12.875 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,13.3 20.638,14.525Q20.275,15.75 19.6,16.8ZM16.25,13.45 L14,11.2V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,12.375 16.438,12.738Q16.375,13.1 16.25,13.45ZM12,9.2 L9.4,6.6 12,4ZM10,15.15V12.8L8.2,11H5V13H7.85ZM9.1,11.9Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker_on.xml b/packages/SystemUI/res/drawable/ic_speaker_on.xml
new file mode 100644
index 0000000..2a90e05
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_on.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/textColorPrimary"
+ android:autoMirrored="true">
+ <path android:fillColor="#FFFFFFFF"
+ android:pathData="M14,20.725V18.675Q16.25,18.025 17.625,16.175Q19,14.325 19,11.975Q19,9.625 17.625,7.775Q16.25,5.925 14,5.275V3.225Q17.1,3.925 19.05,6.362Q21,8.8 21,11.975Q21,15.15 19.05,17.587Q17.1,20.025 14,20.725ZM3,15V9H7L12,4V20L7,15ZM14,16V7.95Q15.175,8.5 15.838,9.6Q16.5,10.7 16.5,12Q16.5,13.275 15.838,14.362Q15.175,15.45 14,16ZM10,8.85 L7.85,11H5V13H7.85L10,15.15ZM7.5,12Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_watch.xml b/packages/SystemUI/res/drawable/ic_watch.xml
new file mode 100644
index 0000000..8ff880c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_watch.xml
@@ -0,0 +1,29 @@
+<?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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M16,0L8,0l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24
+ h8l0.96,-5.73C18.81,16.81 20,14.54 20,12s-1.19,-4.81 -3.04,-6.27L16,0z
+ M12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml b/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml
new file mode 100644
index 0000000..8f6b4c2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_selected_effect.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="@android:color/white"/>
+ <corners android:radius="?android:attr/buttonCornerRadius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml b/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml
new file mode 100644
index 0000000..9a02296
--- /dev/null
+++ b/packages/SystemUI/res/drawable/screenrecord_options_spinner_popup_background.xml
@@ -0,0 +1,21 @@
+<!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/screenrecord_spinner_background_radius"/>
+ <solid android:color="?androidprv:attr/colorAccentSecondary" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml b/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml
new file mode 100644
index 0000000..34e7d0a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/screenshare_options_spinner_background.xml
@@ -0,0 +1,35 @@
+<!--
+ ~ 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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:paddingMode="stack">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="@dimen/screenrecord_spinner_background_radius" />
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/textColorTertiary" />
+ <solid android:color="@android:color/transparent"/>
+ </shape>
+ </item>
+ <item
+ android:drawable="@drawable/ic_ksh_key_down"
+ android:gravity="end|center_vertical"
+ android:textColor="?androidprv:attr/textColorPrimary"
+ android:width="@dimen/screenrecord_spinner_arrow_size"
+ android:height="@dimen/screenrecord_spinner_arrow_size"
+ android:end="20dp" />
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml b/packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml
index a3ed3d1..f8169d3 100644
--- a/packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml
+++ b/packages/SystemUI/res/drawable/udfps_enroll_checkmark.xml
@@ -26,10 +26,10 @@
android:fillType="evenOdd"/>
<path
android:pathData="M27,0C12.088,0 0,12.088 0,27C0,41.912 12.088,54 27,54C41.912,54 54,41.912 54,27C54,12.088 41.912,0 27,0ZM27,3.962C39.703,3.962 50.037,14.297 50.037,27C50.037,39.703 39.703,50.038 27,50.038C14.297,50.038 3.963,39.703 3.963,27C3.963,14.297 14.297,3.962 27,3.962Z"
- android:fillColor="?attr/biometricsEnrollProgress"
+ android:fillColor="@color/udfps_enroll_progress"
android:fillType="evenOdd"/>
<path
android:pathData="M23.0899,38.8534L10.4199,26.1824L13.2479,23.3544L23.0899,33.1974L41.2389,15.0474L44.0679,17.8754L23.0899,38.8534Z"
- android:fillColor="?attr/biometricsEnrollProgress"
+ android:fillColor="@color/udfps_enroll_progress"
android:fillType="evenOdd"/>
</vector>
diff --git a/packages/SystemUI/res/layout-sw600dp/global_actions_controls_list_view.xml b/packages/SystemUI/res/layout-sw600dp/global_actions_controls_list_view.xml
deleted file mode 100644
index ef49b9c..0000000
--- a/packages/SystemUI/res/layout-sw600dp/global_actions_controls_list_view.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<!--
- ~ Copyright (C) 2020 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.globalactions.MinHeightScrollView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:scrollbars="none">
- <LinearLayout
- android:id="@+id/global_actions_controls_list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_marginLeft="@dimen/global_actions_side_margin"
- android:layout_marginRight="@dimen/global_actions_side_margin" />
-</com.android.systemui.globalactions.MinHeightScrollView>
\ 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
new file mode 100644
index 0000000..73d3f02
--- /dev/null
+++ b/packages/SystemUI/res/layout/activity_rear_display_education.xml
@@ -0,0 +1,63 @@
+<?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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center" >
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:cardElevation="0dp"
+ app:cardCornerRadius="28dp"
+ app:cardBackgroundColor="@color/rear_display_overlay_animation_background_color">
+
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/rear_display_folded_animation"
+ android:layout_width="@dimen/rear_display_animation_width"
+ android:layout_height="@dimen/rear_display_animation_height"
+ android:layout_gravity="center"
+ android:contentDescription="@null"
+ android:scaleType="fitXY"
+ app:lottie_rawRes="@raw/rear_display_folded"
+ app:lottie_autoPlay="true"
+ app:lottie_repeatMode="reverse"/>
+ </androidx.cardview.widget.CardView>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/rear_display_fold_bottom_sheet_title"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:lineSpacingExtra="2sp"
+ android:paddingTop="@dimen/rear_display_title_top_padding"
+ android:paddingBottom="@dimen/rear_display_title_bottom_padding"
+ android:gravity="center_horizontal|center_vertical"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/rear_display_bottom_sheet_description"
+ android:textAppearance="@style/TextAppearance.Dialog.Body"
+ android:lineSpacingExtra="2sp"
+ android:translationY="-1.24sp"
+ android:gravity="center_horizontal|top"
+ />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
new file mode 100644
index 0000000..20b93d9
--- /dev/null
+++ b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
@@ -0,0 +1,75 @@
+<?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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center" >
+
+ <androidx.cardview.widget.CardView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:cardElevation="0dp"
+ app:cardCornerRadius="28dp"
+ app:cardBackgroundColor="@color/rear_display_overlay_animation_background_color">
+
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/rear_display_folded_animation"
+ android:layout_width="@dimen/rear_display_animation_width"
+ android:layout_height="@dimen/rear_display_animation_height"
+ android:layout_gravity="center"
+ android:contentDescription="@null"
+ android:scaleType="fitXY"
+ app:lottie_rawRes="@raw/rear_display_turnaround"
+ app:lottie_autoPlay="true"
+ app:lottie_repeatMode="reverse"/>
+ </androidx.cardview.widget.CardView>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/rear_display_unfold_bottom_sheet_title"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:lineSpacingExtra="2sp"
+ android:paddingTop="@dimen/rear_display_title_top_padding"
+ android:paddingBottom="@dimen/rear_display_title_bottom_padding"
+ android:gravity="center_horizontal|center_vertical"
+ />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/rear_display_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/controls_fullscreen.xml b/packages/SystemUI/res/layout/controls_fullscreen.xml
index 11a5665..e08e63b 100644
--- a/packages/SystemUI/res/layout/controls_fullscreen.xml
+++ b/packages/SystemUI/res/layout/controls_fullscreen.xml
@@ -15,28 +15,19 @@
limitations under the License.
-->
-<LinearLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/control_detail_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
- <com.android.systemui.globalactions.MinHeightScrollView
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:orientation="vertical"
- android:scrollbars="none">
<LinearLayout
android:id="@+id/global_actions_controls"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:clipChildren="false"
+ android:layout_height="match_parent"
android:orientation="vertical"
- android:clipToPadding="false"
android:paddingHorizontal="@dimen/controls_padding_horizontal" />
- </com.android.systemui.globalactions.MinHeightScrollView>
-</LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 9d01148..9efad22 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -18,7 +18,7 @@
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="@dimen/controls_top_margin"
android:layout_marginBottom="@dimen/controls_header_bottom_margin">
@@ -71,5 +71,27 @@
android:background="?android:attr/selectableItemBackgroundBorderless" />
</LinearLayout>
- <include layout="@layout/global_actions_controls_list_view" />
+ <ScrollView
+ android:id="@+id/controls_scroll_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:clipChildren="true"
+ android:scrollbars="none">
+ <include layout="@layout/global_actions_controls_list_view" />
+
+ </ScrollView>
+
+ <FrameLayout
+ android:id="@+id/controls_panel"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:layout_marginLeft="@dimen/global_actions_side_margin"
+ android:layout_marginRight="@dimen/global_actions_side_margin"
+ android:background="#ff0000"
+ android:padding="@dimen/global_actions_side_margin"
+ android:visibility="gone"
+ />
</merge>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index 2d67d95..efcb6f3 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -14,25 +14,32 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.shared.shadow.DoubleShadowTextClock
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/time_view"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:fontFamily="@*android:string/config_clockFontFamily"
- android:textColor="@android:color/white"
- android:format12Hour="@string/dream_time_complication_12_hr_time_format"
- android:format24Hour="@string/dream_time_complication_24_hr_time_format"
- android:fontFeatureSettings="pnum, lnum"
- android:letterSpacing="0.02"
- android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
- app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
- app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
- app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
- app:keyShadowAlpha="0.3"
- app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius"
- app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
- app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
- app:ambientShadowAlpha="0.3"
-/>
+ android:layout_height="wrap_content">
+
+ <com.android.systemui.shared.shadow.DoubleShadowTextClock
+ android:id="@+id/time_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@*android:string/config_clockFontFamily"
+ android:textColor="@android:color/white"
+ android:format12Hour="@string/dream_time_complication_12_hr_time_format"
+ android:format24Hour="@string/dream_time_complication_24_hr_time_format"
+ android:fontFeatureSettings="pnum, lnum"
+ android:letterSpacing="0.02"
+ android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
+ android:translationY="@dimen/dream_overlay_complication_clock_time_translation_y"
+ app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
+ app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
+ app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
+ app:keyShadowAlpha="0.3"
+ app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius"
+ app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
+ app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
+ app:ambientShadowAlpha="0.3"
+ />
+
+</FrameLayout>
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 4f0a78e..de96e97 100644
--- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml
@@ -14,16 +14,21 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<ImageView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/home_controls_chip"
- android:layout_height="@dimen/keyguard_affordance_fixed_height"
- android:layout_width="@dimen/keyguard_affordance_fixed_width"
- android:layout_gravity="bottom|start"
- android:scaleType="center"
- android:tint="?android:attr/textColorPrimary"
- android:src="@drawable/controls_icon"
- android:background="@drawable/keyguard_bottom_affordance_bg"
- android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
- android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
- android:contentDescription="@string/quick_controls_title" />
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding">
+
+ <ImageView
+ android:id="@+id/home_controls_chip"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_gravity="bottom|start"
+ android:scaleType="center"
+ android:tint="?android:attr/textColorPrimary"
+ android:src="@drawable/controls_icon"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:contentDescription="@string/quick_controls_title" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index ae2537f..f14be41 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -312,22 +312,15 @@
<LinearLayout
android:id="@+id/see_all_layout"
- android:layout_width="match_parent"
+ style="@style/InternetDialog.Network"
android:layout_height="64dp"
- android:clickable="true"
- android:focusable="true"
- android:background="?android:attr/selectableItemBackground"
- android:gravity="center_vertical|center_horizontal"
- android:orientation="horizontal"
- android:paddingStart="22dp"
- android:paddingEnd="22dp">
+ android:paddingStart="20dp">
<FrameLayout
android:layout_width="24dp"
android:layout_height="24dp"
android:clickable="false"
- android:layout_gravity="center_vertical|start"
- android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+ android:layout_gravity="center_vertical|start">
<ImageView
android:id="@+id/arrow_forward"
android:src="@drawable/ic_arrow_forward"
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index d27fa19..8b85940 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -34,30 +34,13 @@
android:paddingTop="@dimen/status_bar_padding_top"
android:layout_alignParentEnd="true"
android:gravity="center_vertical|end" >
- <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+
+ <include
android:id="@+id/user_switcher_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="horizontal"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:background="@drawable/status_bar_user_chip_bg"
- android:visibility="visible" >
- <ImageView android:id="@+id/current_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:scaleType="centerInside"
- android:paddingEnd="4dp" />
-
- <TextView android:id="@+id/current_user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- />
- </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ layout="@layout/status_bar_user_chip_container" />
<FrameLayout android:id="@+id/system_icons_container"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 9b8b611..530db0d 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -44,7 +44,7 @@
android:background="@drawable/qs_media_outline_album_bg"
/>
- <com.android.systemui.ripple.MultiRippleView
+ <com.android.systemui.surfaceeffects.ripple.MultiRippleView
android:id="@+id/touch_ripple_view"
android:layout_width="match_parent"
android:layout_height="@dimen/qs_media_session_height_expanded"
@@ -53,6 +53,15 @@
app:layout_constraintTop_toTopOf="@id/album_art"
app:layout_constraintBottom_toBottomOf="@id/album_art" />
+ <com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
+ android:id="@+id/turbulence_noise_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_expanded"
+ app:layout_constraintStart_toStartOf="@id/album_art"
+ app:layout_constraintEnd_toEndOf="@id/album_art"
+ app:layout_constraintTop_toTopOf="@id/album_art"
+ app:layout_constraintBottom_toBottomOf="@id/album_art" />
+
<!-- Guideline for output switcher -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/center_vertical_guideline"
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
index 79ba7ead..aa655e6 100644
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
@@ -41,7 +41,7 @@
android:layout_width="@dimen/qs_media_app_icon_size"
android:layout_height="@dimen/qs_media_app_icon_size"
android:layout_marginStart="@dimen/qs_media_padding"
- android:layout_marginTop="@dimen/qs_media_padding"
+ android:layout_marginTop="@dimen/qs_media_rec_icon_top_margin"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
diff --git a/packages/SystemUI/res/layout/screen_record_options.xml b/packages/SystemUI/res/layout/screen_record_options.xml
index d6c9e98..3f0eea9 100644
--- a/packages/SystemUI/res/layout/screen_record_options.xml
+++ b/packages/SystemUI/res/layout/screen_record_options.xml
@@ -16,7 +16,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/screenrecord_options_padding_bottom">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -72,7 +73,7 @@
android:gravity="center_vertical"
android:text="@string/screenrecord_taps_label"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:fontFamily="@*android:string/config_headlineFontFamily"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
android:textColor="?android:attr/textColorPrimary"
android:contentDescription="@string/screenrecord_taps_label"/>
<Switch
diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml
index ac46cdb..bd71989 100644
--- a/packages/SystemUI/res/layout/screen_share_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_share_dialog.xml
@@ -16,7 +16,7 @@
<!-- Scrollview is necessary to fit everything in landscape layout -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/screen_share_permission_dialog"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -32,10 +32,11 @@
android:gravity="center_horizontal">
<ImageView
+ android:id="@+id/screen_share_dialog_icon"
android:layout_width="@dimen/screenrecord_logo_size"
android:layout_height="@dimen/screenrecord_logo_size"
- android:src="@drawable/ic_screenrecord"
- android:tint="@color/screenrecord_icon_color"
+ android:src="@drawable/ic_media_projection_permission"
+ android:tint="?androidprv:attr/colorAccentPrimary"
android:importantForAccessibility="no"/>
<TextView
android:id="@+id/screen_share_dialog_title"
@@ -43,34 +44,37 @@
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:fontFamily="@*android:string/config_headlineFontFamily"
- android:layout_marginTop="22dp"
- android:layout_marginBottom="15dp"/>
+ android:layout_marginTop="@dimen/screenrecord_title_margin_top"
+ android:gravity="center"/>
<Spinner
android:id="@+id/screen_share_mode_spinner"
- android:layout_width="320dp"
- android:layout_height="72dp"
- android:layout_marginTop="24dp"
- android:layout_marginBottom="24dp" />
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/screenrecord_spinner_height"
+ android:layout_marginTop="@dimen/screenrecord_spinner_margin"
+ android:layout_marginBottom="@dimen/screenrecord_spinner_margin"
+ android:gravity="center_vertical"
+ android:background="@drawable/screenshare_options_spinner_background"
+ android:popupBackground="@drawable/screenrecord_options_spinner_popup_background"/>
<ViewStub
android:id="@+id/options_stub"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/text_warning"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/screenrecord_description"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:textColorSecondary"
android:gravity="start"
- android:layout_marginBottom="20dp"/>
+ android:lineHeight="@dimen/screenrecord_warning_line_height"/>
<!-- Buttons -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:layout_marginTop="36dp">
+ android:layout_marginTop="@dimen/screenrecord_buttons_margin_top">
<TextView
android:id="@+id/button_cancel"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml b/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml
new file mode 100644
index 0000000..66c2155
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_share_dialog_spinner_item_text.xml
@@ -0,0 +1,27 @@
+<!--
+ 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.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@android:id/text1"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?androidprv:attr/textColorOnAccent"
+ android:singleLine="true"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/screenrecord_spinner_height"
+ android:gravity="center_vertical"
+ android:ellipsize="marquee"
+ android:paddingStart="@dimen/screenrecord_spinner_text_padding_start"
+ android:paddingEnd="@dimen/screenrecord_spinner_text_padding_end"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_share_dialog_spinner_text.xml b/packages/SystemUI/res/layout/screen_share_dialog_spinner_text.xml
new file mode 100644
index 0000000..4cc4cba
--- /dev/null
+++ b/packages/SystemUI/res/layout/screen_share_dialog_spinner_text.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:textColor="?android:textColorPrimary"
+ android:singleLine="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:ellipsize="marquee"
+ android:paddingStart="@dimen/screenrecord_spinner_text_padding_start"
+ android:paddingEnd="@dimen/screenrecord_spinner_text_padding_end"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index e281511..3b71dc3 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -55,6 +55,7 @@
android:id="@+id/status_bar_start_side_container"
android:layout_height="match_parent"
android:layout_width="0dp"
+ android:clipChildren="false"
android:layout_weight="1">
<!-- Container that is wrapped around the views on the start half of the status bar.
@@ -136,31 +137,12 @@
android:gravity="center_vertical|end"
android:clipChildren="false">
- <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+ <include
android:id="@+id/user_switcher_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="horizontal"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:layout_marginEnd="16dp"
- android:background="@drawable/status_bar_user_chip_bg"
- android:visibility="visible" >
- <ImageView android:id="@+id/current_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:scaleType="centerInside"
- android:paddingEnd="4dp" />
-
- <TextView android:id="@+id/current_user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- />
- </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ layout="@layout/status_bar_user_chip_container" />
<include layout="@layout/system_icons" />
</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
new file mode 100644
index 0000000..b374074
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
@@ -0,0 +1,40 @@
+<?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.
+ -->
+
+<com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/user_switcher_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ android:background="@drawable/status_bar_user_chip_bg"
+ android:visibility="visible" >
+ <ImageView android:id="@+id/current_user_avatar"
+ android:layout_width="@dimen/status_bar_user_chip_avatar_size"
+ android:layout_height="@dimen/status_bar_user_chip_avatar_size"
+ android:layout_margin="4dp"
+ android:scaleType="centerInside" />
+
+ <TextView android:id="@+id/current_user_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="8dp"
+ android:textAppearance="@style/TextAppearance.StatusBar.UserChip"
+ />
+</com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 8388b67..bafdb11 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -26,12 +26,12 @@
android:fitsSystemWindows="true">
<com.android.systemui.statusbar.BackDropView
- android:id="@+id/backdrop"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- sysui:ignoreRightInset="true"
- >
+ android:id="@+id/backdrop"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ sysui:ignoreRightInset="true"
+ >
<ImageView android:id="@+id/backdrop_back"
android:layout_width="match_parent"
android:scaleType="centerCrop"
@@ -49,7 +49,7 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
- />
+ />
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_notifications"
@@ -57,17 +57,17 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
- />
+ />
<com.android.systemui.statusbar.LightRevealScrim
- android:id="@+id/light_reveal_scrim"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:id="@+id/light_reveal_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
<include layout="@layout/status_bar_expanded"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
<include layout="@layout/brightness_mirror_container" />
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
index 887e3e7..f1bc883 100644
--- a/packages/SystemUI/res/layout/wireless_charging_layout.xml
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -22,7 +22,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <com.android.systemui.ripple.RippleView
+ <com.android.systemui.surfaceeffects.ripple.RippleView
android:id="@+id/wireless_charging_ripple"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/raw/rear_display_folded.json b/packages/SystemUI/res/raw/rear_display_folded.json
new file mode 100644
index 0000000..5140f41
--- /dev/null
+++ b/packages/SystemUI/res/raw/rear_display_folded.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":412,"h":300,"nm":"Close to Open - Generic Version V01","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"SHUTTER_ANIMATION 2","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":270.564,"ix":3},"y":{"a":0,"k":239.916,"ix":4}},"a":{"a":0,"k":[460.228,450.736,0],"ix":1,"l":2},"s":{"a":0,"k":[138.889,138.889,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.528,0],[0,-2.528],[2.528,0],[0,2.528]],"o":[[2.528,0],[0,2.528],[-2.528,0],[0,-2.528]],"v":[[0,-4.5],[4.5,0],[0,4.5],[-4.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 599","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[140,140],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_01","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.337,0],[0,-5.337],[5.337,0],[0,5.337]],"o":[[5.337,0],[0,5.337],[-5.337,0],[0,-5.337]],"v":[[0,-9.5],[9.5,0],[0,9.5],[-9.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 598","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_02","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":196,"st":-90,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"CAMERA_CASING","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.125,149.875,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[20,20,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":77.334,"s":[{"i":[[0.121,-8.986],[-0.129,-6.81],[0,0],[0.079,-13.74],[-0.055,-2.977],[0,0],[-0.084,4.25],[0,5.834],[0,20.89],[-0.063,-6.336]],"o":[[-0.029,2.163],[0.129,6.811],[0,0],[-0.079,13.74],[0.123,6.697],[0,0],[0.045,-2.268],[0,-6.193],[0,-0.486],[0.247,24.895]],"v":[[-248.474,-424.663],[-248.252,-408.338],[-247.986,-395.258],[-248.158,-368.523],[-248.248,-337.322],[-243.792,-329.875],[-243.834,-338.44],[-243.875,-350.098],[-244.25,-495.639],[-248.374,-488.414]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78,"s":[{"i":[[0.121,-8.986],[-0.035,-6.791],[0,0],[0.257,-54.598],[-0.08,-2.976],[0,0],[-0.084,4.25],[0,5.834],[0,20.89],[0.187,-5.461]],"o":[[-0.029,2.163],[0.035,6.791],[0,0],[-0.257,54.598],[0.121,4.51],[0,0],[0.045,-2.268],[0,-6.193],[0,-0.486],[-0.853,24.882]],"v":[[-246.724,-424.581],[-246.689,-408.295],[-246.611,-395.254],[-247.145,-286.802],[-247.56,-173.885],[-239.293,-170.875],[-239.335,-179.44],[-239.251,-191.098],[-239,-495.889],[-246.437,-493.601]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79,"s":[{"i":[[0.714,-8.993],[0.023,-5.892],[0,0],[0.218,-62.519],[-0.04,-3.021],[0,0],[-2.041,1.97],[0,5.92],[0,21.197],[1.374,-7.356]],"o":[[-0.174,2.184],[-0.023,5.892],[0,0],[-0.218,62.519],[0.061,5.942],[0,0],[0.335,-2.259],[0,-6.284],[0,-0.493],[-3.801,24.629]],"v":[[-249.702,-423.916],[-249.966,-409.403],[-249.968,-398.166],[-250.414,-273.882],[-250.801,-145.067],[-238.043,-139.562],[-231.043,-142.595],[-229.959,-182.924],[-230,-497.57],[-247.031,-494.301]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[1.306,-9],[0.081,-4.992],[0,0],[0.2,-61.534],[0,-3.065],[0,0],[-6.497,19.5],[0,6.005],[0,21.503],[2.562,-9.251]],"o":[[-0.32,2.204],[-0.081,4.992],[0,0],[-0.2,61.535],[0,7.375],[0,0],[0.738,-2.215],[0,-6.375],[0,-0.5],[-6.75,24.375]],"v":[[-252.681,-423.25],[-253.243,-410.511],[-253.325,-401.078],[-253.726,-278.775],[-254.126,-151.875],[-236.876,-143.875],[-224.128,-171.375],[-221.128,-194.75],[-221,-499.25],[-241.375,-496.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0,0],[2.977,-4.617],[0.592,-15.582],[0.085,-28.87],[-7.478,-2.938],[0,0],[-7.716,4.062],[0.774,19.04],[0.351,22.315],[4.742,-7.015]],"o":[[0,0],[-5.02,7.785],[-0.054,1.42],[-0.036,12.426],[7.58,-2.313],[0,0],[6.882,-3.623],[-0.821,-20.201],[-4.272,-4.668],[-5.774,8.542]],"v":[[-281.915,-495.91],[-289.56,-482.16],[-299.005,-452.549],[-298.089,-174.301],[-285.022,-158.312],[-272.428,-163.108],[-254.243,-172.187],[-246.548,-198.415],[-247.911,-507.832],[-271.41,-509.792]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83,"s":[{"i":[[0,0],[5.84,-4.09],[0.943,-15.582],[-0.504,-28.868],[-9.184,-4.813],[0,0],[-12.283,4.062],[0.017,19.08],[0.558,22.315],[10.444,-5.833]],"o":[[0,0],[-9.041,6.332],[-0.086,1.42],[0.293,16.801],[12.066,-2.313],[0,0],[10.956,-3.623],[-0.018,-20.335],[-6.483,-6.543],[-12.057,6.734]],"v":[[-335.521,-505.91],[-351.465,-495.91],[-370.479,-463.799],[-369.043,-170.551],[-352.691,-149.562],[-328.662,-154.983],[-300.058,-162.812],[-286.232,-189.04],[-289.767,-509.707],[-316.694,-517.917]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86,"s":[{"i":[[0,0],[10.236,-2.84],[1.247,-15.582],[0,-28.874],[-6.428,0.187],[0,0],[-16.249,4.062],[0,17.165],[0.739,22.315],[11.672,-3.958]],"o":[[0,0],[-13.709,3.803],[-0.114,1.42],[0,17.426],[15.447,-2.313],[0,0],[14.494,-3.623],[0,-20.252],[-3.545,-8.418],[-17.281,5.861]],"v":[[-411.704,-507.785],[-438.297,-499.035],[-463.451,-466.924],[-464.325,-183.676],[-446.9,-161.437],[-413.235,-166.858],[-377.21,-173.437],[-354.78,-199.04],[-356.455,-507.207],[-381.047,-517.292]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[11.399,-2.84],[1.389,-15.582],[-0.154,-28.874],[-10.466,-4.813],[0,0],[-18.094,4.062],[0.311,21.54],[0.823,22.315],[12.693,-2.083]],"o":[[0,0],[-15.266,3.803],[-0.127,1.42],[0.136,25.551],[16.344,-2.733],[0,0],[16.14,-3.623],[-0.293,-20.255],[-6.423,-10.918],[-20.046,3.29]],"v":[[-453.081,-516.535],[-484.085,-509.035],[-512.096,-476.924],[-511.208,-201.176],[-491.095,-170.812],[-450.071,-176.858],[-415.594,-182.187],[-391.248,-214.04],[-394.202,-507.207],[-419.568,-523.542]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91,"s":[{"i":[[0,0],[11.98,-2.84],[1.46,-15.582],[-0.514,-28.87],[-22.839,0.574],[0,0],[-19.017,4.062],[-1.512,28.415],[0.865,22.315],[18.516,-1.458]],"o":[[0,0],[-16.044,3.803],[-0.133,1.42],[0.514,28.87],[17.426,-0.438],[0,0],[16.962,-3.623],[1.076,-20.231],[-2.031,-12.793],[-21.281,1.676]],"v":[[-517.52,-526.535],[-550.105,-520.285],[-579.544,-488.174],[-580.475,-228.676],[-552.254,-182.687],[-515.05,-186.858],[-479.472,-193.437],[-453.852,-232.165],[-454.219,-505.957],[-481.641,-531.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[9.594,-0.965],[1.674,-15.582],[-0.589,-28.87],[-26.176,0.574],[0,0],[-26.125,4.687],[-0.648,20.252],[0.991,22.315],[23.63,-1.458]],"o":[[0,0],[-18.792,1.889],[-0.153,1.42],[0.589,28.87],[19.971,-0.438],[0,0],[19.531,-3.504],[0.648,-20.252],[-5.417,-15.918],[-24.412,1.507]],"v":[[-566.624,-545.91],[-605.219,-542.785],[-638.958,-510.674],[-640.256,-243.676],[-605.048,-197.687],[-564.208,-201.233],[-524.5,-205.937],[-494.422,-252.79],[-495.208,-520.332],[-526.755,-548.542]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101,"s":[{"i":[[0,0],[10.756,0.606],[1.787,-15.582],[-0.629,-28.87],[-29.482,0.187],[0,0],[-26.75,1.562],[-0.693,20.252],[1.058,22.315],[29.006,2.879]],"o":[[0,0],[-20.135,-1.134],[-0.163,1.42],[0.629,28.87],[22.579,-0.143],[0,0],[21.139,-1.234],[0.693,-20.252],[-0.991,-20.899],[-22.881,-2.271]],"v":[[-590.882,-561.535],[-630.768,-559.035],[-666.802,-526.924],[-667.496,-244.926],[-626.768,-198.312],[-587.72,-199.358],[-544.491,-201.562],[-512.976,-245.915],[-513.29,-522.832],[-546.76,-561.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.524,-1.302],[0,0],[-22.316,2.187],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[23.032,1.051],[0,0],[21.519,-2.108],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-603.82,-572.785],[-647.049,-572.785],[-683.846,-540.674],[-684.555,-258.676],[-646.156,-212.687],[-598.091,-212.483],[-558.309,-214.687],[-525.505,-260.29],[-527.086,-532.832],[-561.264,-571.667]],"c":true}]},{"t":115,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.524,-1.302],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[23.032,1.051],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-605.07,-581.535],[-648.299,-581.535],[-685.096,-549.424],[-685.805,-263.676],[-647.406,-217.687],[-601.841,-216.858],[-560.092,-217.187],[-527.348,-259.04],[-528.336,-541.582],[-562.514,-580.417]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294118524,0.250980407,0.262745112181,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":78,"op":241,"st":60,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"FRONT_LENS","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[269.043,44.23,0],"ix":2,"l":2},"a":{"a":0,"k":[387.188,-561.875,0],"ix":1,"l":2},"s":{"a":0,"k":[18.519,18.519,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[1.484,-19.734],[20.984,-0.234],[1.484,19.266],[-18.016,-0.234]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[385.5,-561.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":241,"st":60,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"FRONT_SCREEN 2","parent":5,"td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[268.577,150.446,0],"ix":2,"l":2},"a":{"a":0,"k":[-2.142,1.067,0],"ix":1,"l":2},"s":{"a":0,"k":[18.894,18.894,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[635,1210],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":50,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.641,0.871],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":241,"st":60,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Figure","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[207.459,144.79,0],"to":[0.312,0,0],"ti":[-1.146,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":63.334,"s":[209.334,144.79,0],"to":[1.146,0,0],"ti":[-2.125,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":66.666,"s":[214.334,144.79,0],"to":[2.125,0,0],"ti":[-2.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[222.084,144.79,0],"to":[2.812,0,0],"ti":[-4.396,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[231.209,144.79,0],"to":[4.396,0,0],"ti":[-5.104,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[248.459,144.79,0],"to":[5.104,0,0],"ti":[-3.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[261.834,144.79,0],"to":[3.812,0,0],"ti":[-2.667,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[271.334,144.79,0],"to":[2.667,0,0],"ti":[-1.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[277.834,144.79,0],"to":[1.812,0,0],"ti":[-1.083,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106.666,"s":[282.209,144.79,0],"to":[1.083,0,0],"ti":[-0.458,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113.334,"s":[284.334,144.79,0],"to":[0.458,0,0],"ti":[-0.104,0,0]},{"t":120,"s":[284.959,144.79,0]}],"ix":2,"l":2},"a":{"a":0,"k":[270.209,145.54,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.55,0.55,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.001]},"t":60,"s":[108,108,100]},{"t":120,"s":[106.5,106.5,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.939,-1.387],[6.061,-1.3],[0.907,1.308]],"o":[[1.066,0.711],[2.423,1.734],[-4.85,1.04],[0,0]],"v":[[-0.582,-6.237],[5.035,-2.37],[2.01,5.979],[-6.77,3.354]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.596078455448,0.321568638086,0.239215686917,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.12,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[272.026,152.086],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Nose","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.926,7.277],[-8.848,0],[-3.783,2.669],[18.309,-0.059],[0.81,-15.17],[-4.916,0]],"o":[[3.926,7.277],[4.982,0],[-0.845,-15.213],[-18.267,0.059],[3.755,2.604],[8.848,0]],"v":[[-0.106,3.616],[20.359,15.839],[33.729,11.589],[-0.12,-15.891],[-33.793,11.7],[-20.571,15.839]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,9.805],[9.805,0],[0,-9.805],[-9.805,0]],"o":[[0,-9.805],[-9.805,0],[0,9.805],[9.805,0]],"v":[[-6.59,-22.526],[-24.345,-40.281],[-42.099,-22.526],[-24.345,-4.772]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.337254911661,0.23137255013,0.129411771894,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.47,108.276],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Hair","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-7.446,13.962],[0,0],[0,0],[-0.313,2.62],[0.015,4.351],[0,0],[-18.83,0.06],[-0.051,-15.806],[0,0],[-0.49,-2.669],[-1.873,-4.352],[-0.89,-3.988],[0,-4.307],[8.014,-5.508],[12.335,-0.04],[8.035,5.309],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.222,-4.479],[0.309,-2.598],[0,0],[-0.051,-15.806],[18.83,-0.06],[0,0],[0.013,4.276],[0.495,2.702],[3.604,8.387],[0.884,3.962],[0,7.459],[-7.997,5.495],[-12.35,0.04],[-8.04,-5.313],[0,0],[0,-6.072]],"v":[[-36.861,-4.598],[-36.804,-4.703],[-36.772,-4.819],[-34.61,-14.535],[-34.324,-24.06],[-34.324,-24.128],[-0.614,-53.129],[33.278,-24.345],[33.278,-24.252],[33.789,-14.641],[37.134,-4.922],[43.41,12.489],[44.307,23.718],[31.53,43.997],[0.092,53.129],[-31.449,44.589],[-44.307,24.009],[-44.307,23.977]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[4.598,1.656],[-2.64,7.412],[-4.598,-1.656],[2.64,-7.412]],"o":[[-4.6,-1.657],[2.64,-7.412],[4.6,1.657],[-2.64,7.412]],"v":[[30.635,-0.09],[27.088,-16.51],[40.195,-26.931],[43.742,-10.511]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[4.598,-1.656],[2.64,7.412],[-4.598,1.656],[-2.64,-7.412]],"o":[[-4.6,1.657],[-2.64,-7.412],[4.6,-1.657],[2.64,7.412]],"v":[[-32.206,-0.025],[-45.315,-10.446],[-41.768,-26.866],[-28.659,-16.445]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431391716,0.403921574354,0.305882364511,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.996,145.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Head","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[32.75,0],[5.125,-6.5],[0,0],[0,0],[0.125,7.75]],"o":[[-36.75,0],[-0.25,11],[0,0],[0,0],[-14.25,-12.5]],"v":[[269.5,203.875],[202.375,231.25],[202.125,265.125],[335.375,265.125],[335.375,229.75]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.105865478516,0.450958251953,0.901947021484,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Body","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,-5.243]],"o":[[0,0],[0,0],[0,0],[5.243,0],[0,0]],"v":[[335.369,264.28],[202.496,264.441],[202.496,35.601],[325.98,35.551],[335.473,45.044]],"c":true},"ix":2},"nm":"Mask","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.815673828125,0.88232421875,0.980377197266,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Background","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":660,"st":60,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"FOLDABLE_BODY","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.125,149.875,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[20,20,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-16.75],[0,0],[0.131,-20.875],[0,0],[0,0],[-1.054,43.75],[0,0],[55.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.07,32.5],[0,0],[0,0],[50.138,1],[0,0],[0.75,-40.75],[0,0],[0,0],[-0.317,13.125]],"v":[[-380.75,-475.25],[-381.945,472.625],[-381.756,573.5],[-381.766,647.625],[278.237,648.25],[356.555,572.75],[356.375,-566.125],[277.933,-644.25],[-380.936,-644.5],[-380.183,-550.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":63.334,"s":[{"i":[[0,-16.75],[0,0],[0.131,-20.875],[0,0],[0,0],[-1.054,43.75],[0,0],[55.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.07,32.5],[0,0],[0,0],[50.138,1],[0,0],[0.75,-40.75],[0,0],[0,0],[-0.317,13.125]],"v":[[-373.625,-475],[-373.633,472.562],[-373.444,573.438],[-373.454,647.562],[287.112,648.375],[364.93,573.625],[364.75,-566.938],[285.995,-645],[-373.811,-644.25],[-373.058,-550.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":66.666,"s":[{"i":[[0,-16.75],[0,0],[0.131,-20.875],[0,0],[0,0],[-1.054,43.75],[0,0],[55.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.07,32.5],[0,0],[0,0],[50.138,1],[0,0],[0.75,-40.75],[0,0],[0,0],[-0.317,13.125]],"v":[[-350,-475.25],[-349.82,472.5],[-349.631,573.375],[-349.641,647.5],[311.487,647],[388.805,573],[389.125,-566.75],[310.058,-644.75],[-350.186,-644.5],[-349.433,-550.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[0,-16.75],[0,0],[0.131,-20.875],[0,0],[0,0],[-1.054,43.75],[0,0],[54.942,-0.625],[0,0],[0,0]],"o":[[0,0],[0.07,32.5],[0,0],[0,0],[50.138,1],[0,0],[0.187,-41.375],[0,0],[0,0],[-0.317,13.125]],"v":[[-315.875,-476.875],[-315.445,471],[-315.256,571.875],[-315.266,646],[348.612,647.562],[427.555,572.75],[427.438,-565.75],[349.058,-643.688],[-316.077,-643.625],[-315.308,-552.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[{"i":[[0,-16.75],[0,0],[0.131,-20.875],[0,0],[0,0],[-1.054,43.75],[0,0],[54.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.07,32.5],[0,0],[0,0],[50.138,1],[0,0],[0.75,-40.75],[0,0],[0,0],[-0.317,13.125]],"v":[[-278.75,-478],[-278.57,469.75],[-278.381,570.625],[-278.391,644.75],[395.237,645.125],[471.554,571],[471.25,-564],[393.808,-642.125],[-278.968,-642.25],[-278.183,-553.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-16.75],[0,0],[-0.119,-21.25],[0,0],[0,0],[-1.054,42.938],[0,0],[54.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.32,32.625],[0,0],[0,0],[50.075,0.562],[0,0],[0.75,-40.75],[0,0],[0,0],[0.183,12.75]],"v":[[-248.25,-479],[-248.82,468.5],[-248.381,550.25],[-248.516,643.75],[440.3,644.25],[515.804,570.375],[516,-564.375],[438.058,-641],[-248.468,-640.125],[-248.308,-541.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.334,"s":[{"i":[[0,-16.75],[0,0],[0.081,-18.8],[0,0],[0,0],[-1.054,42.287],[0,0],[54.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.52,32.725],[0,0],[0,0],[50.025,0.212],[0,0],[0.75,-40.75],[0,0],[0,0],[-1.317,12.05]],"v":[[-223.65,-479.7],[-224.12,467.9],[-224.081,534.35],[-224.116,642.5],[471.15,643.15],[548.004,570.275],[548.2,-564.675],[468.658,-640.1],[-224.668,-638.65],[-224.208,-523.1]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,-16.75],[0,0],[-2.744,-18],[0,0],[0,0],[-1.054,42.125],[0,0],[54.442,-0.625],[0,0],[0,0]],"o":[[0,0],[0.57,32.75],[0,0],[0,0],[50.013,0.125],[0,0],[0.75,-40.75],[0,0],[0,0],[-1.692,11.875]],"v":[[-233.75,-479.75],[-233.57,467.5],[-229.881,530.125],[-213.641,642.5],[478.862,642.875],[556.054,570.25],[556.25,-564.75],[476.308,-639.875],[-212.468,-638.5],[-230.683,-518.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.666,"s":[{"i":[[0.133,-15.433],[0,0],[-4.881,-15.762],[0,0],[0,0],[-1.067,41.975],[0,0],[54.08,-0.675],[0,0],[0,0]],"o":[[0,0],[0.458,32.95],[0,0],[0,0],[49.838,0.162],[0,0],[0.75,-40.625],[0,0],[0,0],[-6.455,9.608]],"v":[[-267.883,-471.067],[-267.329,462.242],[-262.735,514.346],[-201.341,642],[486.187,642.638],[563.142,570.212],[563.35,-564.412],[483.87,-639.625],[-201.81,-638.275],[-260.045,-517.358]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82,"s":[{"i":[[0.4,-12.8],[0,0],[-9.156,-11.287],[0,0],[0,0],[-1.092,41.675],[0,0],[53.355,-0.775],[0,0],[0,0]],"o":[[0,0],[0.233,33.35],[0,0],[0,0],[49.488,0.238],[0,0],[0.75,-40.375],[0,0],[0,0],[-10.48,6.825]],"v":[[-331.65,-460.45],[-331.47,451.725],[-319.444,518.037],[-181.241,641.75],[500.837,642.162],[577.317,570.138],[577.55,-563.737],[498.995,-639.125],[-180.493,-637.825],[-318.77,-515.325]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0,-16.75],[0,0],[-13.431,-6.812],[0,0],[0,0],[-1.117,41.375],[0,0],[52.63,-0.875],[0,0],[0,0]],"o":[[0,0],[0.008,33.75],[0,0],[0,0],[49.138,0.312],[0,0],[0.75,-40.125],[0,0],[0,0],[-18.005,8.375]],"v":[[-387.75,-461.75],[-388.82,454.125],[-369.569,523.312],[-159.641,641.25],[515.487,641.688],[591.492,570.062],[591.75,-563.062],[514.12,-638.625],[-160.843,-637.375],[-367.495,-521.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.666,"s":[{"i":[[0,-16.75],[0,0],[-20.006,-7.188],[0,0],[0,0],[-1.142,41.075],[0,0],[49.955,-0.925],[0,0],[0,0]],"o":[[0,0],[-0.217,34.15],[0,0],[0,0],[48.788,0.387],[0,0],[0.75,-39.875],[0,0],[0,0],[-24.98,7.975]],"v":[[-439.2,-468.15],[-438.72,458.425],[-413.244,530.237],[-141.041,640.35],[527.537,641.212],[603.067,569.987],[603.15,-564.787],[526.445,-638.125],[-143.993,-637.125],[-411.07,-528.125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0,-16.75],[0,0],[-29.869,-7.75],[0,0],[0,0],[-1.179,40.625],[0,0],[45.942,-1],[0,0],[0,0]],"o":[[0,0],[-0.555,34.75],[0,0],[0,0],[48.263,0.5],[0,0],[0.75,-39.5],[0,0],[0,0],[-35.442,7.375]],"v":[[-503.25,-477.75],[-503.57,464.25],[-468.756,540],[-113.141,639],[545.612,640.5],[620.429,569.875],[620.25,-567.375],[544.933,-637.375],[-118.718,-636.75],[-463.308,-537.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0.062,-18.625],[0,0],[-31.869,-6.438],[0,0],[0,0],[-1.179,40.438],[0,0],[44.63,-1],[0,0],[0,0]],"o":[[0,0],[-0.43,34.75],[0,0],[0,0],[47.638,0.625],[0,0],[0.5,-39.062],[0,0],[0,0],[-38.817,5.938]],"v":[[-584.938,-494.688],[-585.445,486.188],[-542.506,557.375],[-88.141,639],[570.612,639.75],[646.054,568.625],[646.188,-566.875],[570.495,-636.625],[-92.093,-636],[-537.183,-555.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0.125,-20.5],[0,0],[-33.869,-5.125],[0,0],[0,0],[-1.179,40.25],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.305,34.75],[0,0],[0,0],[47.013,0.75],[0,0],[0.25,-38.625],[0,0],[0,0],[-42.192,4.5]],"v":[[-640.125,-510.625],[-640.32,507.125],[-589.256,573.75],[-63.141,639],[591.112,639.25],[667.179,567.625],[666.625,-567.5],[593.058,-635.5],[-65.468,-635.25],[-584.558,-571.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0.062,-30.685],[0,0],[-36.306,-4.562],[0,0],[0,0],[-0.59,40.56],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.43,36.25],[0,0],[0,0],[45.45,0.25],[0,0],[0.25,-38.625],[0,0],[0,0],[-41.442,3.938]],"v":[[-676.125,-519.625],[-676.008,522.062],[-617.819,589.812],[-46.391,638.5],[609.862,638.75],[684.617,567.25],[684.375,-566.125],[610.808,-634.75],[-47.593,-634.5],[-616.558,-587.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,-40.869],[0,0],[-38.744,-4],[0,0],[0,0],[0,40.869],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.555,37.75],[0,0],[0,0],[43.888,-0.25],[0,0],[0.25,-38.625],[0,0],[0,0],[-40.692,3.375]],"v":[[-697.625,-528.125],[-696.945,535.5],[-634.381,602.875],[-29.641,638],[625.612,638.25],[699.054,566.875],[699.125,-564.75],[625.558,-634],[-29.718,-633.75],[-633.558,-599.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106.666,"s":[{"i":[[0,-40.869],[0,0],[-42.994,-3.25],[0,0],[0,0],[0,40.869],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.93,42.625],[0,0],[0,0],[43.888,-0.25],[0,0],[0.25,-38.625],[0,0],[0,0],[-41.067,2.125]],"v":[[-715.875,-547.625],[-715.945,550.5],[-647.881,622.875],[-8.891,637.25],[646.487,637.75],[719.679,565.375],[718.875,-563.25],[646.183,-633.75],[-9.218,-633.875],[-649.058,-619.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113.334,"s":[{"i":[[0,-40.869],[0,0],[-44.369,-2],[0,0],[0,0],[0,40.869],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.305,42.5],[0,0],[0,0],[43.888,-0.25],[0,0],[0.25,-38.625],[0,0],[0,0],[-40.423,0]],"v":[[-720.125,-562.375],[-720.82,561.25],[-649.131,634],[3.109,637.25],[658.987,636.625],[729.679,566.125],[729.625,-564.75],[657.308,-633.375],[2.907,-633.625],[-649.808,-630.5]],"c":true}]},{"t":120,"s":[{"i":[[0,-40.869],[0,0],[-46.994,-1.25],[0,0],[0,0],[0,40.869],[0,0],[43.317,-1],[0,0],[0,0]],"o":[[0,0],[-0.305,42.5],[0,0],[0,0],[43.888,-0.25],[0,0],[0.75,-40],[0,0],[0,0],[-40.423,0]],"v":[[-721.75,-564.375],[-721.57,561.5],[-649.631,637.75],[-3.641,637.25],[659.862,637],[733.179,565.625],[732.75,-564.875],[659.058,-634.25],[2.032,-633.75],[-648.683,-634.375]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"rd","nm":"Round Corners 1","r":{"a":0,"k":18,"ix":1},"ix":2,"mn":"ADBE Vector Filter - RC","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":241,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"BUTTON","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[71.498,114.825,0],"ix":2,"l":2},"a":{"a":0,"k":[-698.509,46.873,0],"ix":1,"l":2},"s":{"a":0,"k":[40,18,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-0.549,1.538],[0,0]],"o":[[0,0],[-0.554,-3.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[27.005,73.269],[26.312,72.137],[26,64.269],[25.741,-76.022],[26.183,-82.526],[27.125,-83.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-0.987,1.288],[0,0]],"o":[[0,0],[-0.741,-2.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[10.88,76.769],[7.625,75.627],[6.812,68.26],[6.553,-78.594],[7.808,-85.536],[11.375,-86.11]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.424,1.038],[0,0]],"o":[[0,0],[-0.929,-1.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-1.995,80.394],[-4.313,79.127],[-5.625,72.26],[-5.884,-81.156],[-3.817,-88.536],[-1.625,-89.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.581,0.831],[0,0]],"o":[[0,0],[-1.154,-1.025],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-4.92,81.663],[-10.263,80.552],[-11.8,73.81],[-11.934,-81.831],[-9.692,-89.236],[-4.55,-89.626]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.817,0.519],[0,0]],"o":[[0,0],[-1.491,-0.875],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-10.917,83.519],[-16.922,82.69],[-18.797,76.135],[-18.744,-82.844],[-16.239,-90.286],[-10.547,-90.546]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.052,0.208],[0,0]],"o":[[0,0],[-1.829,-0.725],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-16.289,85.394],[-21.456,84.827],[-23.669,78.46],[-23.428,-83.856],[-20.661,-91.336],[-15.919,-91.446]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.054,-0.625],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-20.027,86.635],[-23.438,86.252],[-25.875,80.01],[-25.509,-84.531],[-22.567,-92.036],[-19.656,-92.055]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.069,-0.563],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-20.345,86.897],[-24.538,86.552],[-26.994,80.535],[-26.609,-85.031],[-23.661,-92.186],[-19.969,-92.205]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.116,-0.375],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-21.718,87.685],[-26.535,87.452],[-29.048,82.11],[-28.605,-86.531],[-25.64,-92.636],[-21.323,-92.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.163,-0.188],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-22.982,88.472],[-27.674,88.352],[-30.243,83.685],[-29.742,-88.031],[-26.76,-93.086],[-22.568,-93.105]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.194,-0.063],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.293,88.997],[-28.485,88.952],[-31.091,84.735],[-30.553,-89.031],[-27.558,-93.386],[-23.866,-93.405]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.558,89.26],[-28.188,89.252],[-30.812,85.26],[-30.255,-89.531],[-27.255,-93.536],[-24.125,-93.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.765,89.36],[-28.394,89.352],[-31.219,85.359],[-30.748,-89.532],[-27.548,-93.536],[-24.419,-93.555]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-25.373,90.005],[-27.502,89.998],[-31.127,86],[-31.005,-89.291],[-27.005,-93.291],[-25.375,-93.31]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-698.509,46.873],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":96,"op":241,"st":60,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"BUTTON 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[71.498,179.325,0],"ix":2,"l":2},"a":{"a":0,"k":[-698.509,46.873,0],"ix":1,"l":2},"s":{"a":0,"k":[40,25,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-0.549,1.538],[0,0]],"o":[[0,0],[-0.554,-3.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[27.005,73.269],[26.312,72.137],[26,64.269],[25.741,-76.022],[26.183,-82.526],[27.125,-83.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-0.987,1.288],[0,0]],"o":[[0,0],[-0.741,-2.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[10.88,76.769],[7.625,75.627],[6.812,68.26],[6.553,-78.594],[7.808,-85.536],[11.375,-86.11]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.424,1.038],[0,0]],"o":[[0,0],[-0.929,-1.125],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-1.995,80.394],[-4.313,79.127],[-5.625,72.26],[-5.884,-81.156],[-3.817,-88.536],[-1.625,-89.046]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.581,0.831],[0,0]],"o":[[0,0],[-1.154,-1.025],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-4.92,81.663],[-10.263,80.552],[-11.8,73.81],[-11.934,-81.831],[-9.692,-89.236],[-4.55,-89.626]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-1.817,0.519],[0,0]],"o":[[0,0],[-1.491,-0.875],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-10.917,83.519],[-16.922,82.69],[-18.797,76.135],[-18.744,-82.844],[-16.239,-90.286],[-10.547,-90.546]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.052,0.208],[0,0]],"o":[[0,0],[-1.829,-0.725],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-16.289,85.394],[-21.456,84.827],[-23.669,78.46],[-23.428,-83.856],[-20.661,-91.336],[-15.919,-91.446]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":106.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.054,-0.625],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-20.027,86.635],[-23.438,86.252],[-25.875,80.01],[-25.509,-84.531],[-22.567,-92.036],[-19.656,-92.055]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.069,-0.563],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-20.345,86.897],[-24.538,86.552],[-26.994,80.535],[-26.609,-85.031],[-23.661,-92.186],[-19.969,-92.205]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.116,-0.375],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-21.718,87.685],[-26.535,87.452],[-29.048,82.11],[-28.605,-86.531],[-25.64,-92.636],[-21.323,-92.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.163,-0.188],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-22.982,88.472],[-27.674,88.352],[-30.243,83.685],[-29.742,-88.031],[-26.76,-93.086],[-22.568,-93.105]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.194,-0.063],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.293,88.997],[-28.485,88.952],[-31.091,84.735],[-30.553,-89.031],[-27.558,-93.386],[-23.866,-93.405]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113.334,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.558,89.26],[-28.188,89.252],[-30.812,85.26],[-30.255,-89.531],[-27.255,-93.536],[-24.125,-93.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.666,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-24.765,89.36],[-28.394,89.352],[-31.219,85.359],[-30.748,-89.532],[-27.548,-93.536],[-24.419,-93.555]],"c":true}]},{"t":120,"s":[{"i":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"o":[[0,0],[-2.209,0],[0,0],[0,-2.209],[0,0],[0,0]],"v":[[-25.373,89.63],[-27.502,89.623],[-31.127,85.625],[-31.005,-89.666],[-27.005,-93.666],[-25.375,-93.685]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-698.509,46.873],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":96,"op":241,"st":60,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/rear_display_turnaround.json b/packages/SystemUI/res/raw/rear_display_turnaround.json
new file mode 100644
index 0000000..82204c7
--- /dev/null
+++ b/packages/SystemUI/res/raw/rear_display_turnaround.json
@@ -0,0 +1 @@
+{"v":"5.8.1","fr":60,"ip":0,"op":181,"w":412,"h":300,"nm":"Turnaround - Generic Version V01","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"SHUTTER_ANIMATION 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":55,"s":[100]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":320.842,"ix":3},"y":{"a":0,"k":149.716,"ix":4}},"a":{"a":0,"k":[460.228,450.736,0],"ix":1,"l":2},"s":{"a":0,"k":[150,150,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.528,0],[0,-2.528],[2.528,0],[0,2.528]],"o":[[2.528,0],[0,2.528],[-2.528,0],[0,-2.528]],"v":[[0,-4.5],[4.5,0],[0,4.5],[-4.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 599","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[140,140],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_01","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.337,0],[0,-5.337],[5.337,0],[0,5.337]],"o":[[5.337,0],[0,5.337],[-5.337,0],[0,-5.337]],"v":[[0,-9.5],[9.5,0],[0,9.5],[-9.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 598","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_02","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":-90,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"SHUTTER_ANIMATION","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":51,"s":[0]},{"t":56,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":286.342,"ix":3},"y":{"a":0,"k":245.716,"ix":4}},"a":{"a":0,"k":[460.228,450.736,0],"ix":1,"l":2},"s":{"a":0,"k":[150,150,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.528,0],[0,-2.528],[2.528,0],[0,2.528]],"o":[[2.528,0],[0,2.528],[-2.528,0],[0,-2.528]],"v":[[0,-4.5],[4.5,0],[0,4.5],[-4.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 599","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[140,140],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":129,"s":[0]},{"t":139,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_01","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.337,0],[0,-5.337],[5.337,0],[0,5.337]],"o":[[5.337,0],[0,5.337],[-5.337,0],[0,-5.337]],"v":[[0,-9.5],[9.5,0],[0,9.5],[-9.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 598","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[460.228,450.736],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":129,"s":[0]},{"t":139,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"SHUTTER_02","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":129,"op":279,"st":39,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"PATCH 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[207.062,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[54.347,-125.337],[54.347,125.337],[-15.847,125.337],[-15.847,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[34.347,-125.337],[34.347,125.337],[-35.847,125.337],[-35.847,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.166,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-3.683,-125.337],[-3.683,125.337],[-73.877,125.337],[-73.877,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-3.867,-125.337],[-3.867,125.337],[-74.061,125.337],[-74.061,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":113.334,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.606,-125.337],[-1.606,125.337],[-71.799,125.337],[-71.799,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":115,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[0.775,-125.337],[0.775,125.337],[-69.418,125.337],[-69.418,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":117.5,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[4.347,-125.337],[4.347,125.337],[-65.847,125.337],[-65.847,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":121.666,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[9.686,-125.337],[9.686,125.337],[-60.508,125.337],[-60.508,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":122.5,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[11.954,-125.337],[11.954,125.337],[-58.24,125.337],[-58.24,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":125.834,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[14.759,-125.337],[14.759,125.337],[-55.435,125.337],[-55.435,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126.666,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[15.668,-125.337],[15.668,125.337],[-54.525,125.337],[-54.525,-125.337]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":128.334,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[15.987,-125.337],[15.987,125.337],[-54.206,125.337],[-54.206,-125.337]],"c":true}]},{"t":135,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[16.847,-125.337],[16.847,125.337],[-53.347,125.337],[-53.347,-125.337]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-674.097,51.337],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":99,"op":300,"st":60,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"PATCH","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[207.908,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[722.046,-80],[722.046,165.813],[708.25,165.813],[708.25,-80]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[736.168,-80],[736.168,165.813],[722.372,165.813],[722.372,-80]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65.834,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[737.98,-80],[737.98,165.813],[724.184,165.813],[724.184,-80]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":66.666,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[739.435,-80],[739.435,165.813],[725.639,165.813],[725.639,-80]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":67.5,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[740.796,-80],[740.796,165.813],[727,165.813],[727,-80]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":69.166,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[743.651,-79.231],[743.651,166.583],[729.855,166.583],[729.855,-79.231]],"c":true}]},{"t":72.5,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[738.112,-77.692],[738.112,168.121],[724.316,168.121],[724.316,-77.692]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":74,"st":60,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"BUTTON_OUT_BOTTOM 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":60,"s":[206.312,95.219,0],"to":[0,-2.417,0],"ti":[0,0,0]},{"i":{"x":0.333,"y":1},"o":{"x":0.167,"y":0.167},"t":88,"s":[206.312,80.719,0],"to":[0,0,0],"ti":[0,-2.417,0]},{"t":128,"s":[206.312,95.219,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,18,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-9.113],[0,0],[3.544,-0.062],[0,9.113],[0,0],[-1.905,0.228]],"o":[[0,0],[0,9.113],[-1.908,0.034],[0,0],[0,-9.113],[4.17,-0.5]],"v":[[687.673,-97.826],[687.673,65.299],[679.205,76.875],[675.75,65.375],[675.75,-97.75],[679.205,-108.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,-9.113],[0,0],[4.532,-0.062],[0,9.113],[0,0],[-2.436,0.228]],"o":[[0,0],[0,9.113],[-2.44,0.034],[0,0],[0,-9.113],[5.332,-0.5]],"v":[[699.977,-97.944],[699.977,68.306],[689.293,80],[684.875,68.5],[684.875,-97.75],[689.293,-108.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[0,-9.113],[0,0],[4.532,-0.062],[0,9.113],[0,0],[-2.436,0.228]],"o":[[0,0],[0,9.113],[-2.44,0.034],[0,0],[0,-9.113],[5.332,-0.5]],"v":[[711.227,-100.491],[711.227,76.697],[700.543,87.875],[696.125,76.375],[696.125,-100.812],[700.543,-111.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73,"s":[{"i":[[0,-9.113],[0,0],[5.113,-0.062],[0,9.113],[0,0],[-2.748,0.228]],"o":[[0,0],[0,9.113],[-2.753,0.034],[0,0],[0,-9.113],[6.016,-0.5]],"v":[[700.685,-102.905],[700.685,81.345],[692.568,93.125],[687.583,81.625],[687.583,-102.625],[692.568,-113.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74.166,"s":[{"i":[[0,-9.113],[0,0],[5.694,-0.062],[0,9.113],[0,0],[-3.061,0.228]],"o":[[0,0],[0,9.113],[-3.065,0.034],[0,0],[0,-9.113],[6.699,-0.5]],"v":[[691.581,-103.125],[691.581,83.5],[686.03,95],[680.479,83.5],[680.479,-103.125],[686.03,-114]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[0,-9.113],[0,0],[6.275,-0.062],[0,9.113],[0,0],[-3.373,0.228]],"o":[[0,0],[0,9.113],[-3.378,0.034],[0,0],[0,-9.113],[7.383,-0.5]],"v":[[683.359,-103.625],[683.359,85.375],[677.242,96.875],[671.125,85.375],[671.125,-103.625],[677.242,-114.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75.834,"s":[{"i":[[0,-9.113],[0,0],[6.856,-0.062],[0,9.113],[0,0],[-3.685,0.228]],"o":[[0,0],[0,9.113],[-3.691,0.034],[0,0],[0,-9.113],[8.066,-0.5]],"v":[[672.305,-104.125],[672.305,87.25],[665.621,98.75],[658.938,87.25],[658.938,-104.125],[665.621,-115]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-9.113],[0,0],[7.438,-0.062],[0,9.113],[0,0],[-3.997,0.228]],"o":[[0,0],[0,9.113],[-4.004,0.034],[0,0],[0,-9.113],[8.75,-0.5]],"v":[[657.5,-104.625],[657.5,89.125],[650.25,100.625],[643,89.125],[643,-104.625],[650.25,-115.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":77.5,"s":[{"i":[[0,-9.113],[0,0],[7.149,-0.047],[0,9.113],[0,0],[-4.569,0.171]],"o":[[0,0],[0,9.113],[-4.573,0.025],[0,0],[0,-9.113],[8.133,-0.375]],"v":[[637.562,-104.531],[637.562,90.531],[629.281,103.281],[621,90.531],[621,-104.531],[629.281,-116.812]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78.334,"s":[{"i":[[0,-9.113],[0,0],[6.86,-0.031],[0,9.113],[0,0],[-5.14,0.114]],"o":[[0,0],[0,9.113],[-5.143,0.017],[0,0],[0,-9.113],[7.516,-0.25]],"v":[[612.625,-104.438],[612.625,91.938],[603.312,105.938],[594,91.938],[594,-104.438],[603.312,-118.125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[0,-9.113],[0,0],[6.571,-0.016],[0,9.113],[0,0],[-5.711,0.057]],"o":[[0,0],[0,9.113],[-5.713,0.008],[0,0],[0,-9.113],[6.899,-0.125]],"v":[[580.188,-104.094],[580.188,93.594],[569.844,108.844],[559.5,93.594],[559.5,-104.094],[569.844,-119.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,-9.113],[0,0],[6.282,0],[0,9.113],[0,0],[-6.282,0]],"o":[[0,0],[0,9.113],[-6.282,0],[0,0],[0,-9.113],[6.282,0]],"v":[[540.25,-103.75],[540.25,95.25],[528.875,111.75],[517.5,95.25],[517.5,-103.75],[528.875,-120.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[0,-9.113],[0,0],[6.731,0],[0,9.113],[0,0],[-6.731,0]],"o":[[0,0],[0,9.113],[-6.731,0],[0,0],[0,-9.113],[6.731,0]],"v":[[491.25,-104.562],[491.25,97.688],[479.062,114.188],[466.875,97.688],[466.875,-104.562],[479.062,-121.062]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0,-9.113],[0,0],[7.18,0],[0,9.113],[0,0],[-7.18,0]],"o":[[0,0],[0,9.113],[-7.18,0],[0,0],[0,-9.113],[7.18,0]],"v":[[432.958,-105.375],[432.958,100.125],[419.958,116.625],[406.958,100.125],[406.958,-105.375],[419.958,-121.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[0,-9.113],[0,0],[7.628,0],[0,9.113],[0,0],[-7.628,0]],"o":[[0,0],[0,9.113],[-7.628,0],[0,0],[0,-9.113],[7.628,0]],"v":[[364.854,-106.188],[364.854,102.562],[351.042,119.062],[337.229,102.562],[337.229,-106.188],[351.042,-122.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0,-9.113],[0,0],[8.077,0],[0,9.113],[0,0],[-8.077,0]],"o":[[0,0],[0,9.113],[-8.077,0],[0,0],[0,-9.113],[8.077,0]],"v":[[286.75,-107],[286.75,105],[272.125,121.5],[257.5,105],[257.5,-107],[272.125,-123.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0,-9.113],[0,0],[8.33,0],[0,9.113],[0,0],[-8.33,0]],"o":[[0,0],[0,9.113],[-8.33,0],[0,0],[0,-9.113],[8.33,0]],"v":[[201.667,-107.5],[201.667,106.167],[186.583,122.667],[171.5,106.167],[171.5,-107.5],[186.583,-124]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85,"s":[{"i":[[0,-9.113],[0,0],[8.583,0],[0,9.113],[0,0],[-8.583,0]],"o":[[0,0],[0,9.113],[-8.583,0],[0,0],[0,-9.113],[8.583,0]],"v":[[110.333,-108],[110.333,107.333],[94.792,123.833],[79.25,107.333],[79.25,-108],[94.792,-124.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85.834,"s":[{"i":[[0,-9.113],[0,0],[8.837,0],[0,9.113],[0,0],[-8.837,0]],"o":[[0,0],[0,9.113],[-8.837,0],[0,0],[0,-9.113],[8.837,0]],"v":[[16.5,-108.5],[16.5,108.5],[0.5,125],[-15.5,108.5],[-15.5,-108.5],[0.5,-125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0,-9.113],[0,0],[8.698,0],[0,9.113],[0,0],[-8.699,0]],"o":[[0,0],[0,9.113],[-8.699,0],[0,0],[0,-9.113],[8.698,0]],"v":[[-75.766,-108.229],[-75.766,107.667],[-91.516,124.167],[-107.266,107.667],[-107.266,-108.229],[-91.516,-124.729]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0,-9.113],[0,0],[8.56,0],[0,9.113],[0,0],[-8.56,0]],"o":[[0,0],[0,9.113],[-8.56,0],[0,0],[0,-9.113],[8.56,0]],"v":[[-166.781,-107.958],[-166.781,106.833],[-182.281,123.333],[-197.781,106.833],[-197.781,-107.958],[-182.281,-124.458]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[0,-9.113],[0,0],[8.422,0],[0,9.113],[0,0],[-8.422,0]],"o":[[0,0],[0,9.113],[-8.422,0],[0,0],[0,-9.113],[8.422,0]],"v":[[-252.172,-107.688],[-252.172,106],[-267.422,122.5],[-282.672,106],[-282.672,-107.688],[-267.422,-124.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[0,-9.113],[0,0],[8.284,0],[0,9.113],[0,0],[-8.284,0]],"o":[[0,0],[0,9.113],[-8.284,0],[0,0],[0,-9.113],[8.284,0]],"v":[[-330.062,-107.417],[-330.062,105.167],[-345.062,121.667],[-360.062,105.167],[-360.062,-107.417],[-345.062,-123.917]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,-9.113],[0,0],[8.146,0],[0,9.113],[0,0],[-8.146,0]],"o":[[0,0],[0,9.113],[-8.146,0],[0,0],[0,-9.113],[8.146,0]],"v":[[-400.781,-107.146],[-400.781,103.396],[-415.531,119.896],[-430.281,103.396],[-430.281,-107.146],[-415.531,-123.646]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[0,-9.113],[0,0],[8.008,0],[0,9.113],[0,0],[-8.008,0]],"o":[[0,0],[0,9.113],[-8.008,0],[0,0],[0,-9.113],[8.008,0]],"v":[[-462.75,-106.875],[-462.75,101.625],[-477.25,118.125],[-491.75,101.625],[-491.75,-106.875],[-477.25,-123.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[0,-9.113],[0,0],[7.525,0],[0,9.113],[0,0],[-7.525,0]],"o":[[0,0],[0,9.113],[-7.525,0],[0,0],[0,-9.113],[7.525,0]],"v":[[-518.308,-106.5],[-518.308,100.125],[-531.933,116.125],[-545.558,100.125],[-545.558,-106.5],[-531.933,-122.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[0,-9.113],[0,0],[7.042,0],[0,9.113],[0,0],[-7.042,0]],"o":[[0,0],[0,9.113],[-7.042,0],[0,0],[0,-9.113],[7.042,0]],"v":[[-566.367,-106.125],[-566.367,98.625],[-579.117,114.125],[-591.867,98.625],[-591.867,-106.125],[-579.117,-121.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,-9.113],[0,0],[6.558,0],[0,9.113],[0,0],[-6.558,0]],"o":[[0,0],[0,9.113],[-6.558,0],[0,0],[0,-9.113],[6.558,0]],"v":[[-606.925,-105.75],[-606.925,97.125],[-618.8,112.125],[-630.675,97.125],[-630.675,-105.75],[-618.8,-120.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[0,-9.113],[0,0],[6.075,0],[0,9.113],[0,0],[-6.075,0]],"o":[[0,0],[0,9.113],[-6.075,0],[0,0],[0,-9.113],[6.075,0]],"v":[[-641.963,-105.375],[-641.963,95.625],[-652.963,110.125],[-663.963,95.625],[-663.963,-105.375],[-652.963,-119.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,-9.113],[0,0],[5.592,0],[0,9.113],[0,0],[-5.592,0]],"o":[[0,0],[0,9.113],[-5.592,0],[0,0],[0,-9.113],[5.592,0]],"v":[[-671.062,-105],[-671.062,94.125],[-681.188,108.125],[-691.312,94.125],[-691.312,-105],[-681.188,-119]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[0,-9.113],[0,0],[5.295,0],[0,9.113],[0,0],[-5.295,0]],"o":[[0,0],[0,9.113],[-5.295,0],[0,0],[0,-9.113],[5.295,0]],"v":[[-696.971,-104.5],[-696.971,92.5],[-706.558,106.25],[-716.146,92.5],[-716.146,-104.5],[-706.558,-118.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0,-9.113],[0,0],[4.998,0],[0,9.113],[0,0],[-4.998,0]],"o":[[0,0],[0,9.113],[-4.998,0],[0,0],[0,-9.113],[4.998,0]],"v":[[-718.192,-104],[-718.192,90.875],[-727.242,104.375],[-736.292,90.875],[-736.292,-104],[-727.242,-117.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,-9.113],[0,0],[4.701,0],[0,9.113],[0,0],[-4.701,0]],"o":[[0,0],[0,9.113],[-4.701,0],[0,0],[0,-9.113],[4.701,0]],"v":[[-735.35,-103.5],[-735.35,89.25],[-743.862,102.5],[-752.375,89.25],[-752.375,-103.5],[-743.862,-116.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98.334,"s":[{"i":[[0,-9.113],[0,0],[4.404,0],[0,9.113],[0,0],[-4.404,0]],"o":[[0,0],[0,9.113],[-4.404,0],[0,0],[0,-9.113],[4.404,0]],"v":[[-750.425,-103],[-750.425,87.625],[-758.4,100.625],[-766.375,87.625],[-766.375,-103],[-758.4,-116]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,-9.113],[0,0],[4.108,0],[0,9.113],[0,0],[-4.108,0]],"o":[[0,0],[0,9.113],[-4.108,0],[0,0],[0,-9.113],[4.108,0]],"v":[[-761.75,-102.5],[-761.75,86],[-769.188,98.75],[-776.625,86],[-776.625,-102.5],[-769.188,-115.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,-9.113],[0,0],[3.844,-0.015],[0.141,9.991],[0,0],[-4.51,0]],"o":[[0,0],[0,9.113],[-4.635,0.031],[0,0],[0.016,-9.897],[3.844,0]],"v":[[-771.312,-102.188],[-771.312,84.594],[-778.273,97.344],[-785.234,84.594],[-785.234,-102.188],[-778.273,-114.938]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[0,-9.113],[0,0],[3.581,-0.031],[0.281,10.869],[0,0],[-4.913,0]],"o":[[0,0],[0,9.113],[-5.163,0.062],[0,0],[0.031,-10.681],[3.581,0]],"v":[[-779.25,-101.875],[-779.25,83.188],[-785.734,95.938],[-792.219,83.188],[-792.219,-101.875],[-785.734,-114.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0,-9.113],[0,0],[3.317,-0.046],[0.422,11.747],[0,0],[-5.316,0]],"o":[[0,0],[0,9.113],[-5.691,0.094],[0,0],[0.047,-11.466],[3.318,0]],"v":[[-786.125,-101.562],[-786.125,81.781],[-792.133,94.531],[-798.141,81.781],[-798.141,-101.562],[-792.133,-114.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102.5,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.061],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.219,0.125],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-791.75,-101.25],[-791.75,80.375],[-797.281,93.125],[-802.812,80.375],[-802.812,-101.25],[-797.281,-114]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.07],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.51,0.153],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-796.934,-100.556],[-796.934,78.292],[-802.465,91.042],[-807.997,78.292],[-807.997,-100.556],[-802.465,-113.306]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.079],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.802,0.181],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-799.618,-99.861],[-799.618,76.208],[-805.149,88.958],[-810.681,76.208],[-810.681,-99.861],[-805.149,-112.611]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":108.334,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.092],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.24,0.222],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-800.61,-98.819],[-800.61,73.083],[-806.141,85.833],[-811.672,73.083],[-811.672,-98.819],[-806.141,-111.569]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-799.188,-98.125],[-799.188,71],[-804.719,83.75],[-814.636,70.875],[-814.636,-98.25],[-804.719,-110.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-787.944,-96.125],[-787.944,66.75],[-793.475,79.5],[-804.019,66.875],[-804.019,-96],[-793.475,-108.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-780.109,-96.579],[-780.109,65.77],[-785.64,78.355],[-796.184,65.566],[-796.184,-96.783],[-785.64,-108.836]],"c":true}]},{"t":128,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-779.207,-96.704],[-779.207,65.118],[-784.738,77.54],[-794.029,65.112],[-794.029,-96.71],[-784.738,-108.467]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[53.5,61],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":60,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"BUTTON_OUT_BOTTOM","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,157.219,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,25,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-9.113],[0,0],[3.544,-0.062],[0,9.113],[0,0],[-1.905,0.228]],"o":[[0,0],[0,9.113],[-1.908,0.034],[0,0],[0,-9.113],[4.17,-0.5]],"v":[[688.299,-98],[688.299,65.125],[679.205,76.875],[675.75,65.375],[675.75,-97.75],[679.205,-108.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,-9.113],[0,0],[4.532,-0.062],[0,9.113],[0,0],[-2.436,0.228]],"o":[[0,0],[0,9.113],[-2.44,0.034],[0,0],[0,-9.113],[5.332,-0.5]],"v":[[701.856,-97.875],[701.856,68.375],[689.293,80],[684.875,68.5],[684.875,-97.75],[689.293,-108.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[0,-9.113],[0,0],[4.532,-0.062],[0,9.113],[0,0],[-2.436,0.228]],"o":[[0,0],[0,9.113],[-2.44,0.034],[0,0],[0,-9.113],[5.332,-0.5]],"v":[[709.974,-101.062],[709.974,76.125],[700.543,87.875],[696.125,76.375],[696.125,-100.812],[700.543,-111.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73,"s":[{"i":[[0,-9.113],[0,0],[5.113,-0.062],[0,9.113],[0,0],[-2.748,0.228]],"o":[[0,0],[0,9.113],[-2.753,0.034],[0,0],[0,-9.113],[6.016,-0.5]],"v":[[703.818,-102.625],[703.818,81.625],[692.568,93.125],[687.583,81.625],[687.583,-102.625],[692.568,-113.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74.166,"s":[{"i":[[0,-9.113],[0,0],[5.694,-0.062],[0,9.113],[0,0],[-3.061,0.228]],"o":[[0,0],[0,9.113],[-3.065,0.034],[0,0],[0,-9.113],[6.699,-0.5]],"v":[[691.581,-103.125],[691.581,83.5],[686.03,95],[680.479,83.5],[680.479,-103.125],[686.03,-114]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[0,-9.113],[0,0],[6.275,-0.062],[0,9.113],[0,0],[-3.373,0.228]],"o":[[0,0],[0,9.113],[-3.378,0.034],[0,0],[0,-9.113],[7.383,-0.5]],"v":[[683.359,-103.625],[683.359,85.375],[677.242,96.875],[671.125,85.375],[671.125,-103.625],[677.242,-114.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75.834,"s":[{"i":[[0,-9.113],[0,0],[6.856,-0.062],[0,9.113],[0,0],[-3.685,0.228]],"o":[[0,0],[0,9.113],[-3.691,0.034],[0,0],[0,-9.113],[8.066,-0.5]],"v":[[672.305,-104.125],[672.305,87.25],[665.621,98.75],[658.938,87.25],[658.938,-104.125],[665.621,-115]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-9.113],[0,0],[7.438,-0.062],[0,9.113],[0,0],[-3.997,0.228]],"o":[[0,0],[0,9.113],[-4.004,0.034],[0,0],[0,-9.113],[8.75,-0.5]],"v":[[657.5,-104.625],[657.5,89.125],[650.25,100.625],[643,89.125],[643,-104.625],[650.25,-115.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":77.5,"s":[{"i":[[0,-9.113],[0,0],[7.149,-0.047],[0,9.113],[0,0],[-4.569,0.171]],"o":[[0,0],[0,9.113],[-4.573,0.025],[0,0],[0,-9.113],[8.133,-0.375]],"v":[[637.562,-104.531],[637.562,90.531],[629.281,103.281],[621,90.531],[621,-104.531],[629.281,-116.812]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78.334,"s":[{"i":[[0,-9.113],[0,0],[6.86,-0.031],[0,9.113],[0,0],[-5.14,0.114]],"o":[[0,0],[0,9.113],[-5.143,0.017],[0,0],[0,-9.113],[7.516,-0.25]],"v":[[612.625,-104.438],[612.625,91.938],[603.312,105.938],[594,91.938],[594,-104.438],[603.312,-118.125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[0,-9.113],[0,0],[6.571,-0.016],[0,9.113],[0,0],[-5.711,0.057]],"o":[[0,0],[0,9.113],[-5.713,0.008],[0,0],[0,-9.113],[6.899,-0.125]],"v":[[580.188,-104.094],[580.188,93.594],[569.844,108.844],[559.5,93.594],[559.5,-104.094],[569.844,-119.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,-9.113],[0,0],[6.282,0],[0,9.113],[0,0],[-6.282,0]],"o":[[0,0],[0,9.113],[-6.282,0],[0,0],[0,-9.113],[6.282,0]],"v":[[540.25,-103.75],[540.25,95.25],[528.875,111.75],[517.5,95.25],[517.5,-103.75],[528.875,-120.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[0,-9.113],[0,0],[6.731,0],[0,9.113],[0,0],[-6.731,0]],"o":[[0,0],[0,9.113],[-6.731,0],[0,0],[0,-9.113],[6.731,0]],"v":[[491.25,-104.562],[491.25,97.688],[479.062,114.188],[466.875,97.688],[466.875,-104.562],[479.062,-121.062]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0,-9.113],[0,0],[7.18,0],[0,9.113],[0,0],[-7.18,0]],"o":[[0,0],[0,9.113],[-7.18,0],[0,0],[0,-9.113],[7.18,0]],"v":[[432.958,-105.375],[432.958,100.125],[419.958,116.625],[406.958,100.125],[406.958,-105.375],[419.958,-121.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[0,-9.113],[0,0],[7.628,0],[0,9.113],[0,0],[-7.628,0]],"o":[[0,0],[0,9.113],[-7.628,0],[0,0],[0,-9.113],[7.628,0]],"v":[[364.854,-106.188],[364.854,102.562],[351.042,119.062],[337.229,102.562],[337.229,-106.188],[351.042,-122.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0,-9.113],[0,0],[8.077,0],[0,9.113],[0,0],[-8.077,0]],"o":[[0,0],[0,9.113],[-8.077,0],[0,0],[0,-9.113],[8.077,0]],"v":[[286.75,-107],[286.75,105],[272.125,121.5],[257.5,105],[257.5,-107],[272.125,-123.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0,-9.113],[0,0],[8.33,0],[0,9.113],[0,0],[-8.33,0]],"o":[[0,0],[0,9.113],[-8.33,0],[0,0],[0,-9.113],[8.33,0]],"v":[[201.667,-107.5],[201.667,106.167],[186.583,122.667],[171.5,106.167],[171.5,-107.5],[186.583,-124]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85,"s":[{"i":[[0,-9.113],[0,0],[8.583,0],[0,9.113],[0,0],[-8.583,0]],"o":[[0,0],[0,9.113],[-8.583,0],[0,0],[0,-9.113],[8.583,0]],"v":[[110.333,-108],[110.333,107.333],[94.792,123.833],[79.25,107.333],[79.25,-108],[94.792,-124.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85.834,"s":[{"i":[[0,-9.113],[0,0],[8.837,0],[0,9.113],[0,0],[-8.837,0]],"o":[[0,0],[0,9.113],[-8.837,0],[0,0],[0,-9.113],[8.837,0]],"v":[[16.5,-108.5],[16.5,108.5],[0.5,125],[-15.5,108.5],[-15.5,-108.5],[0.5,-125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0,-9.113],[0,0],[8.698,0],[0,9.113],[0,0],[-8.699,0]],"o":[[0,0],[0,9.113],[-8.699,0],[0,0],[0,-9.113],[8.698,0]],"v":[[-75.766,-108.229],[-75.766,107.667],[-91.516,124.167],[-107.266,107.667],[-107.266,-108.229],[-91.516,-124.729]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0,-9.113],[0,0],[8.56,0],[0,9.113],[0,0],[-8.56,0]],"o":[[0,0],[0,9.113],[-8.56,0],[0,0],[0,-9.113],[8.56,0]],"v":[[-166.781,-107.958],[-166.781,106.833],[-182.281,123.333],[-197.781,106.833],[-197.781,-107.958],[-182.281,-124.458]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[0,-9.113],[0,0],[8.422,0],[0,9.113],[0,0],[-8.422,0]],"o":[[0,0],[0,9.113],[-8.422,0],[0,0],[0,-9.113],[8.422,0]],"v":[[-252.172,-107.688],[-252.172,106],[-267.422,122.5],[-282.672,106],[-282.672,-107.688],[-267.422,-124.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[0,-9.113],[0,0],[8.284,0],[0,9.113],[0,0],[-8.284,0]],"o":[[0,0],[0,9.113],[-8.284,0],[0,0],[0,-9.113],[8.284,0]],"v":[[-330.062,-107.417],[-330.062,105.167],[-345.062,121.667],[-360.062,105.167],[-360.062,-107.417],[-345.062,-123.917]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,-9.113],[0,0],[8.146,0],[0,9.113],[0,0],[-8.146,0]],"o":[[0,0],[0,9.113],[-8.146,0],[0,0],[0,-9.113],[8.146,0]],"v":[[-400.781,-107.146],[-400.781,103.396],[-415.531,119.896],[-430.281,103.396],[-430.281,-107.146],[-415.531,-123.646]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[0,-9.113],[0,0],[8.008,0],[0,9.113],[0,0],[-8.008,0]],"o":[[0,0],[0,9.113],[-8.008,0],[0,0],[0,-9.113],[8.008,0]],"v":[[-462.75,-106.875],[-462.75,101.625],[-477.25,118.125],[-491.75,101.625],[-491.75,-106.875],[-477.25,-123.375]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[0,-9.113],[0,0],[7.525,0],[0,9.113],[0,0],[-7.525,0]],"o":[[0,0],[0,9.113],[-7.525,0],[0,0],[0,-9.113],[7.525,0]],"v":[[-518.308,-106.5],[-518.308,100.125],[-531.933,116.125],[-545.558,100.125],[-545.558,-106.5],[-531.933,-122.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[0,-9.113],[0,0],[7.042,0],[0,9.113],[0,0],[-7.042,0]],"o":[[0,0],[0,9.113],[-7.042,0],[0,0],[0,-9.113],[7.042,0]],"v":[[-566.367,-106.125],[-566.367,98.625],[-579.117,114.125],[-591.867,98.625],[-591.867,-106.125],[-579.117,-121.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,-9.113],[0,0],[6.558,0],[0,9.113],[0,0],[-6.558,0]],"o":[[0,0],[0,9.113],[-6.558,0],[0,0],[0,-9.113],[6.558,0]],"v":[[-606.925,-105.75],[-606.925,97.125],[-618.8,112.125],[-630.675,97.125],[-630.675,-105.75],[-618.8,-120.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[0,-9.113],[0,0],[6.075,0],[0,9.113],[0,0],[-6.075,0]],"o":[[0,0],[0,9.113],[-6.075,0],[0,0],[0,-9.113],[6.075,0]],"v":[[-641.963,-105.375],[-641.963,95.625],[-652.963,110.125],[-663.963,95.625],[-663.963,-105.375],[-652.963,-119.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,-9.113],[0,0],[5.592,0],[0,9.113],[0,0],[-5.592,0]],"o":[[0,0],[0,9.113],[-5.592,0],[0,0],[0,-9.113],[5.592,0]],"v":[[-671.062,-105],[-671.062,94.125],[-681.188,108.125],[-691.312,94.125],[-691.312,-105],[-681.188,-119]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[0,-9.113],[0,0],[5.295,0],[0,9.113],[0,0],[-5.295,0]],"o":[[0,0],[0,9.113],[-5.295,0],[0,0],[0,-9.113],[5.295,0]],"v":[[-696.971,-104.5],[-696.971,92.5],[-706.558,106.25],[-716.146,92.5],[-716.146,-104.5],[-706.558,-118.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0,-9.113],[0,0],[4.998,0],[0,9.113],[0,0],[-4.998,0]],"o":[[0,0],[0,9.113],[-4.998,0],[0,0],[0,-9.113],[4.998,0]],"v":[[-718.192,-104],[-718.192,90.875],[-727.242,104.375],[-736.292,90.875],[-736.292,-104],[-727.242,-117.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,-9.113],[0,0],[4.701,0],[0,9.113],[0,0],[-4.701,0]],"o":[[0,0],[0,9.113],[-4.701,0],[0,0],[0,-9.113],[4.701,0]],"v":[[-735.35,-103.5],[-735.35,89.25],[-743.862,102.5],[-752.375,89.25],[-752.375,-103.5],[-743.862,-116.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98.334,"s":[{"i":[[0,-9.113],[0,0],[4.404,0],[0,9.113],[0,0],[-4.404,0]],"o":[[0,0],[0,9.113],[-4.404,0],[0,0],[0,-9.113],[4.404,0]],"v":[[-750.425,-103],[-750.425,87.625],[-758.4,100.625],[-766.375,87.625],[-766.375,-103],[-758.4,-116]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,-9.113],[0,0],[4.108,0],[0,9.113],[0,0],[-4.108,0]],"o":[[0,0],[0,9.113],[-4.108,0],[0,0],[0,-9.113],[4.108,0]],"v":[[-761.75,-102.5],[-761.75,86],[-769.188,98.75],[-776.625,86],[-776.625,-102.5],[-769.188,-115.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,-9.113],[0,0],[3.844,-0.015],[0.141,9.991],[0,0],[-4.51,0]],"o":[[0,0],[0,9.113],[-4.635,0.031],[0,0],[0.016,-9.897],[3.844,0]],"v":[[-771.312,-102.188],[-771.312,84.594],[-778.273,97.344],[-785.234,84.594],[-785.234,-102.188],[-778.273,-114.938]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[0,-9.113],[0,0],[3.581,-0.031],[0.281,10.869],[0,0],[-4.913,0]],"o":[[0,0],[0,9.113],[-5.163,0.062],[0,0],[0.031,-10.681],[3.581,0]],"v":[[-779.25,-101.875],[-779.25,83.188],[-785.734,95.938],[-792.219,83.188],[-792.219,-101.875],[-785.734,-114.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0,-9.113],[0,0],[3.317,-0.046],[0.422,11.747],[0,0],[-5.316,0]],"o":[[0,0],[0,9.113],[-5.691,0.094],[0,0],[0.047,-11.466],[3.318,0]],"v":[[-786.125,-101.562],[-786.125,81.781],[-792.133,94.531],[-798.141,81.781],[-798.141,-101.562],[-792.133,-114.312]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102.5,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.061],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.219,0.125],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-791.75,-101.25],[-791.75,80.375],[-797.281,93.125],[-802.812,80.375],[-802.812,-101.25],[-797.281,-114]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.07],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.51,0.153],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-796.934,-100.556],[-796.934,78.292],[-802.465,91.042],[-807.997,78.292],[-807.997,-100.556],[-802.465,-113.306]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-9.113],[0,0],[3.054,-0.079],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-6.802,0.181],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-799.618,-99.861],[-799.618,76.208],[-805.149,88.958],[-810.681,76.208],[-810.681,-99.861],[-805.149,-112.611]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":108.334,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.092],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.24,0.222],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-800.61,-98.819],[-800.61,73.083],[-806.141,85.833],[-811.672,73.083],[-811.672,-98.819],[-806.141,-111.569]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-799.188,-98.125],[-799.188,71],[-804.719,83.75],[-814.636,70.875],[-814.636,-98.25],[-804.719,-110.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":119,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-787.944,-96.125],[-787.944,66.75],[-793.475,79.5],[-804.019,66.875],[-804.019,-96],[-793.475,-108.875]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-780.109,-96.579],[-780.109,65.77],[-785.64,78.355],[-796.184,65.566],[-796.184,-96.783],[-785.64,-108.836]],"c":true}]},{"t":128,"s":[{"i":[[0,-9.113],[0,0],[3.053,-0.101],[0.562,12.625],[0,0],[-5.719,0]],"o":[[0,0],[0,9.113],[-7.531,0.25],[0,0],[0.062,-12.25],[3.055,0]],"v":[[-779.207,-96.704],[-779.207,65.118],[-784.738,77.54],[-794.029,65.112],[-794.029,-96.71],[-784.738,-108.467]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[53.5,61],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":60,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"CENTER_LENS","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.013,149.919,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-5.969],[5.969,0],[0,0],[0,5.969],[-5.969,0],[0,0]],"o":[[0,5.969],[0,0],[-5.969,0],[0,-5.969],[0,0],[5.969,0]],"v":[[21.408,0],[10.601,10.808],[-10.601,10.808],[-21.408,0],[-10.601,-10.808],[10.601,-10.808]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":62.5,"s":[{"i":[[-0.162,-5.954],[5.954,-0.162],[0,0],[0.162,5.954],[-5.954,0.162],[0,0]],"o":[[0.162,5.954],[0,0],[-5.954,0.162],[-0.162,-5.954],[0,0],[5.954,-0.162]],"v":[[23.471,-4.918],[12.899,6.54],[-8.812,6.806],[-19.793,-3.944],[-9.024,-15.178],[12.5,-15.603]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":64.166,"s":[{"i":[[-0.27,-5.944],[5.944,-0.27],[0,0],[0.27,5.944],[-5.944,0.27],[0,0]],"o":[[0.27,5.944],[0,0],[-5.944,0.27],[-0.27,-5.944],[0,0],[5.944,-0.27]],"v":[[24.429,-9.031],[14.014,2.862],[-8.037,3.305],[-19.132,-7.407],[-8.389,-18.924],[13.349,-19.633]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":66.666,"s":[{"i":[[-0.432,-5.929],[5.929,-0.432],[0,0],[0.432,5.929],[-5.929,0.432],[0,0]],"o":[[0.432,5.929],[0,0],[-5.929,0.432],[-0.432,-5.929],[0,0],[5.929,-0.432]],"v":[[24.667,-16.787],[14.488,-4.243],[-8.074,-3.534],[-19.342,-14.188],[-8.638,-26.132],[13.424,-27.266]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[-0.54,-5.92],[5.92,-0.54],[0,0],[0.54,5.92],[-5.92,0.54],[0,0]],"o":[[0.54,5.92],[0,0],[-5.92,0.54],[-0.54,-5.92],[0,0],[5.92,-0.54]],"v":[[23.575,-22.686],[13.553,-9.709],[-9.348,-8.823],[-20.732,-19.439],[-10.053,-31.666],[12.223,-33.083]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70,"s":[{"i":[[-0.648,-5.91],[5.91,-0.648],[0,0],[0.648,5.91],[-5.91,0.648],[0,0]],"o":[[0.648,5.91],[0,0],[-5.91,0.648],[-0.648,-5.91],[0,0],[5.91,-0.648]],"v":[[20.259,-30.049],[10.394,-16.638],[-13.048,-15.324],[-24.296,-25.801],[-13.644,-38.312],[9.173,-40.114]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":71.666,"s":[{"i":[[-0.756,-5.9],[5.9,-0.756],[0,0],[0.756,5.9],[-5.9,0.756],[0,0]],"o":[[0.756,5.9],[0,0],[-5.9,0.756],[-0.756,-5.9],[0,0],[5.9,-0.756]],"v":[[14.171,-38.307],[4.463,-24.462],[-19.518,-22.721],[-30.632,-33.06],[-20.005,-45.855],[3.351,-48.04]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72.5,"s":[{"i":[[-0.81,-5.895],[5.895,-0.81],[0,0],[0.81,5.895],[-5.895,0.81],[0,0]],"o":[[0.81,5.895],[0,0],[-5.895,0.81],[-0.81,-5.895],[0,0],[5.895,-0.81]],"v":[[9.346,-43.279],[-0.283,-29.218],[-24.535,-27.263],[-35.581,-37.533],[-24.967,-50.47],[-1.34,-52.846]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[{"i":[[-0.864,-5.89],[5.89,-0.864],[0,0],[0.864,5.89],[-5.89,0.864],[0,0]],"o":[[0.864,5.89],[0,0],[-5.89,0.864],[-0.864,-5.89],[0,0],[5.89,-0.864]],"v":[[3.001,-48.298],[-6.55,-34.019],[-30.497,-31.851],[-41.476,-42.052],[-30.875,-55.131],[-7.553,-57.699]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74.166,"s":[{"i":[[-0.918,-5.885],[5.885,-0.918],[0,0],[0.918,5.885],[-5.885,0.918],[0,0]],"o":[[0.918,5.885],[0,0],[-5.885,0.918],[-0.918,-5.885],[0,0],[5.885,-0.918]],"v":[[-4.97,-53.817],[-14.443,-39.321],[-38.084,-36.939],[-48.996,-47.071],[-38.408,-60.292],[-15.391,-63.052]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[-0.972,-5.88],[5.88,-0.972],[0,0],[0.972,5.88],[-5.88,0.972],[0,0]],"o":[[0.972,5.88],[0,0],[-5.88,0.972],[-0.972,-5.88],[0,0],[5.88,-0.972]],"v":[[-14.941,-59.46],[-24.335,-44.748],[-47.671,-42.152],[-58.515,-52.215],[-47.94,-65.577],[-25.229,-68.529]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[-1.08,-5.87],[5.87,-1.08],[0,0],[1.08,5.87],[-5.87,1.08],[0,0]],"o":[[1.08,5.87],[0,0],[-5.87,1.08],[-1.08,-5.87],[0,0],[5.87,-1.08]],"v":[[-42.382,-72.123],[-51.619,-56.976],[-74.346,-53.953],[-85.055,-63.877],[-74.506,-77.524],[-52.404,-80.859]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":77.5,"s":[{"i":[[-0.856,-5.865],[5.839,-1.205],[0,0],[0.985,5.907],[-5.839,1.205],[0,0]],"o":[[0.855,5.886],[0,0],[-5.839,1.205],[-0.983,-5.883],[0,0],[5.839,-1.205]],"v":[[-61.785,-78.763],[-70.703,-63.449],[-91.979,-60.128],[-102.22,-70.048],[-92,-83.986],[-71.252,-87.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78.334,"s":[{"i":[[-0.632,-5.86],[5.807,-1.33],[0,0],[0.89,5.944],[-5.807,1.33],[0,0]],"o":[[0.63,5.901],[0,0],[-5.807,1.33],[-0.886,-5.896],[0,0],[5.807,-1.33]],"v":[[-84.656,-85.779],[-93.256,-70.297],[-113.08,-66.679],[-122.854,-76.593],[-112.964,-90.823],[-93.568,-94.893]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[-0.408,-5.856],[5.776,-1.455],[0,0],[0.796,5.981],[-5.776,1.455],[0,0]],"o":[[0.406,5.917],[0,0],[-5.776,1.455],[-0.79,-5.909],[0,0],[5.776,-1.455]],"v":[[-112.027,-92.795],[-120.308,-77.146],[-138.681,-73.229],[-147.987,-83.138],[-138.427,-97.659],[-120.384,-102.097]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[-0.184,-5.851],[5.744,-1.58],[0,0],[0.701,6.017],[-5.744,1.58],[0,0]],"o":[[0.181,5.932],[0,0],[-5.744,1.58],[-0.693,-5.922],[0,0],[5.744,-1.58]],"v":[[-144.7,-99.873],[-152.663,-84.056],[-169.585,-79.842],[-178.423,-89.745],[-169.193,-104.558],[-152.502,-109.364]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[0.039,-5.846],[5.713,-1.705],[0,0],[0.606,6.054],[-5.713,1.705],[0,0]],"o":[[-0.044,5.947],[0,0],[-5.713,1.705],[-0.596,-5.935],[0,0],[5.713,-1.705]],"v":[[-181.936,-106.857],[-189.58,-90.873],[-205.051,-86.361],[-213.421,-96.259],[-204.521,-111.364],[-189.183,-116.536]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0.263,-5.841],[5.681,-1.83],[0,0],[0.511,6.091],[-5.681,1.83],[0,0]],"o":[[-0.269,5.963],[0,0],[-5.681,1.83],[-0.499,-5.948],[0,0],[5.682,-1.83]],"v":[[-225.421,-113.467],[-232.747,-97.315],[-246.767,-92.505],[-254.67,-102.398],[-246.098,-117.794],[-232.114,-123.334]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[0.201,-5.842],[4.849,-1.973],[0,0],[0.428,5.773],[-4.511,1.767],[0,0]],"o":[[-0.205,5.947],[0,0],[-4.849,1.973],[-0.429,-5.935],[0,0],[4.849,-1.973]],"v":[[-275.671,-118.903],[-281.694,-102.971],[-292.741,-98.418],[-299.149,-108.831],[-292.426,-123.38],[-281.329,-128.511]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0.138,-5.843],[4.017,-2.117],[0,0],[0.345,5.456],[-3.34,1.705],[0,0]],"o":[[-0.141,5.931],[0,0],[-4.017,2.117],[-0.359,-5.922],[0,0],[4.017,-2.117]],"v":[[-331.129,-123.089],[-335.849,-107.376],[-343.923,-103.082],[-348.837,-114.013],[-343.962,-127.716],[-335.753,-132.438]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0.075,-5.844],[3.184,-2.261],[0,0],[0.262,5.138],[-2.169,1.643],[0,0]],"o":[[-0.077,5.915],[0,0],[-3.184,2.261],[-0.289,-5.909],[0,0],[4.042,-3.442]],"v":[[-389.712,-125.963],[-393.129,-110.469],[-398.605,-106.433],[-402.024,-117.883],[-398.998,-130.74],[-393.301,-136.302]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85,"s":[{"i":[[0.012,-5.845],[2.352,-2.405],[0,0],[0.179,4.821],[-0.999,1.58],[0,0]],"o":[[-0.012,5.899],[0,0],[-2.352,2.405],[-0.219,-5.895],[0,0],[4.066,-4.767]],"v":[[-451.421,-127.712],[-453.534,-112.437],[-455.663,-108.66],[-457.587,-120.628],[-456.41,-132.638],[-453.975,-136.541]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0.012,-5.845],[0.626,-2.62],[0,0],[0.179,4.821],[-0.999,1.58],[0,0]],"o":[[-0.012,5.899],[0,0],[-1.245,-2.898],[-0.219,-5.895],[0,0],[0.566,-0.017]],"v":[[-512.671,-125.712],[-513.534,-115.437],[-514.163,-115.66],[-514.337,-122.878],[-513.66,-133.888],[-512.975,-134.041]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[509.408,-610.192],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":87,"st":60,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"FRONT_LENS 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[-1.125,-0.688],[0,-10.77],[3.375,0.312],[0,10.77]],"o":[[2.027,1.239],[0,10.77],[-1.122,-0.104],[0,-10.77]],"v":[[-333.875,52.938],[-332.25,68.688],[-333.875,88.188],[-335.5,68.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[-1.661,-0.597],[0,-10.77],[3.518,0.277],[0,10.77]],"o":[[2.541,1.071],[0,10.77],[-1.622,-0.098],[0,-10.77]],"v":[[-304.741,51.121],[-301.683,67.085],[-304.277,86.335],[-307.339,67.121]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[-2.197,-0.507],[0,-10.77],[3.661,0.241],[0,10.77]],"o":[[3.055,0.903],[0,10.77],[-2.123,-0.093],[0,-10.77]],"v":[[-277.357,49.304],[-272.866,65.482],[-276.429,84.482],[-280.929,65.554]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[-3.27,-0.326],[0,-10.77],[3.946,0.17],[0,10.77]],"o":[[4.083,0.567],[0,10.77],[-3.124,-0.082],[0,-10.77]],"v":[[-227.089,45.67],[-219.732,62.277],[-225.232,80.777],[-232.607,62.42]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[-3.806,-0.235],[0,-10.77],[4.089,0.134],[0,10.77]],"o":[[4.597,0.399],[0,10.77],[-3.624,-0.077],[0,-10.77]],"v":[[-205.132,43.467],[-196.561,60.289],[-202.811,78.539],[-211.478,60.467]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[-4.342,-0.145],[0,-10.77],[4.232,0.098],[0,10.77]],"o":[[5.111,0.231],[0,10.77],[-4.125,-0.071],[0,-10.77]],"v":[[-185.301,41.265],[-175.515,58.301],[-182.515,76.301],[-192.473,58.515]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[-4.878,-0.054],[0,-10.77],[4.375,0.062],[0,10.77]],"o":[[5.625,0.062],[0,10.77],[-4.625,-0.066],[0,-10.77]],"v":[[-167.125,38.688],[-156.125,55.938],[-163.875,73.688],[-175.125,56.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[-5.277,0],[-0.118,-10.643],[4.875,0.012],[-0.062,10.649]],"o":[[5.85,0.012],[0.088,10.399],[-5.238,-0.009],[0.07,-10.767]],"v":[[-150.531,36.3],[-139.006,53.575],[-147.306,71.35],[-159.156,53.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[-5.675,0.055],[-0.236,-10.516],[5.375,-0.037],[-0.125,10.528]],"o":[[6.075,-0.038],[0.175,10.028],[-5.852,0.048],[0.14,-10.765]],"v":[[-135.188,33.788],[-123.137,51.088],[-131.988,68.887],[-144.438,51.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[-6.073,0.11],[-0.353,-10.39],[5.875,-0.087],[-0.188,10.407]],"o":[[6.3,-0.087],[0.262,9.657],[-6.465,0.104],[0.211,-10.763]],"v":[[-121.771,31.306],[-109.196,48.631],[-118.596,66.456],[-131.646,48.656]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[-6.472,0.164],[-0.471,-10.263],[6.375,-0.138],[-0.25,10.287]],"o":[[6.525,-0.137],[0.35,9.287],[-7.078,0.161],[0.281,-10.76]],"v":[[-109.104,28.825],[-96.004,46.175],[-105.954,64.025],[-119.604,46.125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[-6.87,0.219],[-0.589,-10.137],[6.875,-0.188],[-0.312,10.166]],"o":[[6.75,-0.188],[0.438,8.916],[-7.691,0.218],[0.351,-10.758]],"v":[[-97.75,26.438],[-84.125,43.812],[-94.625,61.688],[-108.875,43.688]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98.334,"s":[{"i":[[-7.268,0.274],[-0.706,-10.01],[7.375,-0.238],[-0.375,10.045]],"o":[[6.975,-0.238],[0.525,8.545],[-8.305,0.275],[0.421,-10.756]],"v":[[-87.767,24.188],[-73.617,41.588],[-84.667,59.488],[-99.517,41.387]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[-8.065,0.383],[-0.942,-9.757],[8.375,-0.338],[-0.5,9.804]],"o":[[7.425,-0.337],[0.7,7.804],[-9.531,0.388],[0.562,-10.751]],"v":[[-70.425,19.688],[-55.225,37.137],[-67.375,55.088],[-83.425,36.788]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[-8.862,0.492],[-1.177,-9.504],[9.375,-0.438],[-0.625,9.562]],"o":[[7.875,-0.438],[0.875,7.062],[-10.758,0.502],[0.702,-10.747]],"v":[[-56.375,15.188],[-40.125,32.688],[-53.375,50.688],[-70.625,32.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[-9.071,0.394],[-0.942,-9.584],[9.481,-0.35],[-0.5,9.631]],"o":[[8.281,-0.35],[0.7,7.631],[-10.588,0.402],[0.562,-10.579]],"v":[[-45.213,11.238],[-28.625,28.887],[-42.775,47.012],[-60.2,28.488]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[-9.28,0.295],[-0.706,-9.665],[9.588,-0.262],[-0.375,9.7]],"o":[[8.688,-0.262],[0.525,8.2],[-10.417,0.301],[0.421,-10.411]],"v":[[-36.05,7.496],[-19.125,25.296],[-34.175,43.546],[-51.775,24.996]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[-9.384,0.246],[-0.589,-9.705],[9.641,-0.219],[-0.312,9.735]],"o":[[8.891,-0.219],[0.438,8.485],[-10.332,0.251],[0.351,-10.327]],"v":[[-31.875,5.625],[-14.781,23.5],[-30.281,41.812],[-47.969,23.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[-9.593,0.148],[-0.353,-9.786],[9.747,-0.131],[-0.188,9.803]],"o":[[9.297,-0.131],[0.262,9.053],[-10.162,0.151],[0.211,-10.159]],"v":[[-25.337,2.55],[-7.906,20.575],[-24.306,39.012],[-42.169,20.425]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[-9.907,0],[0,-9.907],[9.907,0],[0,9.907]],"o":[[9.907,0],[0,9.907],[-9.907,0],[0,-9.907]],"v":[[-17.125,-2.062],[0.812,16.188],[-16.938,34.812],[-35.062,16.188]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.166,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[-8.781,-9.156],[10.719,10.344],[-8.781,29.844],[-28.281,10.344]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118.334,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[-4.188,-13.375],[15.312,6.125],[-4.188,25.625],[-23.688,6.125]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":122.5,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[-1.562,-16.438],[17.938,3.062],[-1.562,22.562],[-21.062,3.062]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126.666,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[-0.188,-18.25],[19.312,1.25],[-0.188,20.75],[-19.688,1.25]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":130.834,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[0.5,-19.469],[20,0.031],[0.5,19.531],[-19,0.031]],"c":true}]},{"t":135,"s":[{"i":[[-10.77,0],[0,-10.77],[10.77,0],[0,10.77]],"o":[[10.77,0],[0,10.77],[-10.77,0],[0,-10.77]],"v":[[0.688,-20.062],[20.188,-0.562],[0.688,18.938],[-18.812,-0.562]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[385.5,-561.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":87,"op":342,"st":60,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"CENTER_SCREEN 3","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[699,-539],[699,539],[653,585],[-0.999,585],[-653,585],[-699,539],[-699,-539],[-653,-585],[1,-585],[653,-585]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":64.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[707,-547],[707,547],[661,593],[-0.999,585],[-639.273,577],[-685.273,531],[-685.273,-527],[-639.273,-573],[1,-585],[661,-593]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[715,-563],[715,567],[669,613],[-0.999,585],[-617.545,561],[-663.545,515],[-663.545,-511],[-617.545,-557],[1,-585],[669,-609]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[709,-577.4],[709,579],[663,625],[-0.999,585],[-587.709,551.4],[-636.709,505.4],[-636.709,-501.4],[-587.709,-547.4],[1,-585],[663,-623.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72.5,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[705,-587],[705,587],[659,633],[-0.999,585],[-567.818,545],[-613.818,499],[-613.818,-495],[-567.818,-541],[1,-585],[659,-633]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[697.4,-593.4],[697.4,595],[651.4,641],[-0.999,585],[-553.873,539.4],[-599.873,493.4],[-599.873,-490.2],[-553.873,-536.2],[1,-585],[651.4,-639.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[693.8,-599.8],[693.8,603],[647.8,649],[-0.999,585],[-537.927,533.8],[-583.927,487.8],[-583.927,-485.4],[-537.927,-531.4],[1,-585],[647.8,-645.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[678.2,-606.2],[678.2,611],[632.2,657],[-0.999,585],[-519.982,528.2],[-565.982,482.2],[-565.982,-480.6],[-519.982,-526.6],[1,-585],[632.2,-652.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[666.6,-612.6],[666.6,619],[620.6,665],[-0.999,585],[-499.036,522.6],[-545.036,476.6],[-545.036,-475.8],[-499.036,-521.8],[1,-585],[620.6,-658.6]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[647,-619],[647,627],[601,673],[-0.999,585],[-478.091,517],[-524.091,471],[-524.091,-471],[-478.091,-517],[1,-585],[601,-665]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.223,2.5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-22.441,-1.8],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[600.891,-638.2],[600.891,649.545],[561.618,691.182],[-5.908,586.273],[-428.856,507],[-467.174,466.273],[-467.174,-463],[-420.674,-508.5],[1,-585.6],[554.891,-684.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.132,3.75],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-20.96,-2.7],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[561.836,-647.8],[561.836,660.818],[525.927,700.273],[-8.363,586.909],[-398.739,502],[-433.216,463.909],[-433.216,-459],[-386.466,-504.25],[1,-585.9],[515.836,-693.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.041,5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-19.478,-3.6],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[522.782,-657.4],[522.782,672.091],[490.236,709.364],[-10.817,587.546],[-368.621,497],[-399.258,461.546],[-399.258,-455],[-352.258,-500],[1,-586.2],[476.782,-703.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-24.95,6.25],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-17.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[471.727,-667],[471.727,683.364],[442.545,718.455],[-13.272,588.182],[-332.504,492],[-359.3,459.182],[-359.3,-451],[-312.05,-495.75],[1,-586.5],[425.727,-713]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0.129,-24.588],[0,0],[21.663,1.508],[0,0],[0,0],[0.008,23.224],[0,0],[-21.2,5.875],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-15.329,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[21.8,-2.083]],"v":[[412.352,-679.333],[412.352,694.197],[387.295,726.538],[-16.188,588.015],[-289.754,488.25],[-312.675,457.182],[-312.675,-449],[-272.55,-491.375],[-4.583,-587],[372.894,-720.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[0.258,-23.77],[0,0],[17.922,3.015],[0,0],[0,0],[0.017,21.043],[0,0],[-17.45,5.5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-12.663,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[18.194,-4.167]],"v":[[344.977,-691.667],[344.977,705.03],[324.045,734.621],[-19.105,587.849],[-247.004,484.5],[-266.05,455.182],[-266.05,-447],[-233.05,-487],[-10.166,-587.5],[312.06,-728.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0.386,-22.953],[0,0],[14.18,4.523],[0,0],[0,0],[0.025,18.862],[0,0],[-13.7,5.125],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-9.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[14.589,-6.25]],"v":[[269.602,-704],[269.602,715.864],[252.795,742.705],[-22.022,587.682],[-204.254,480.75],[-219.425,453.182],[-219.425,-445],[-193.55,-482.625],[-15.75,-588],[243.227,-736]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0.773,-20.5],[0,0],[2.955,9.045],[0,0],[0,0],[0.05,12.318],[0,0],[-2.45,4],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-1.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[3.773,-12.5]],"v":[[13.477,-707],[13.477,713.364],[9.045,731.955],[-30.772,587.182],[-58.004,481.5],[-61.55,459.182],[-61.55,-451],[-57.05,-481.5],[-32.5,-581.5],[6.727,-725]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":87,"st":60,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Figure 10","tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":72.5,"s":[100]},{"t":80.833984375,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[206.959,124.54,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[206.959,124.54,0],"to":[-1.125,0,0],"ti":[1.125,0,0]},{"t":85.833984375,"s":[200.209,124.54,0]}],"ix":2,"l":2},"a":{"a":0,"k":[270.209,145.54,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.55,0.55,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,-0.47]},"t":60,"s":[125,125,100]},{"t":110,"s":[125,125,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[-1.939,-1.387],[6.061,-1.3],[0.907,1.308]],"o":[[1.066,0.711],[2.423,1.734],[-4.85,1.04],[0,0]],"v":[[-0.582,-6.237],[5.035,-2.37],[2.01,5.979],[-6.77,3.354]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[0,0],[-1.877,-1.382],[5.87,-1.295],[0.878,1.303]],"o":[[1.032,0.708],[2.346,1.727],[-4.697,1.036],[0,0]],"v":[[-0.131,-5.771],[5.309,-1.919],[2.379,6.397],[-6.124,3.782]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,0],[-1.627,-1.365],[5.086,-1.278],[0.761,1.287]],"o":[[0.894,0.699],[2.033,1.706],[-4.07,1.023],[0,0]],"v":[[1.024,-4.856],[5.738,-1.053],[3.2,7.16],[-4.168,4.578]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0,0],[-0.79,-1.365],[2.613,-0.951],[0.598,1.216]],"o":[[0.434,0.699],[0.987,1.706],[-1.617,0.588],[0,0]],"v":[[-0.98,-5.389],[1.308,-1.586],[-0.339,6.331],[-4.212,3.985]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0,0],[-0.387,-1.365],[1.28,-0.951],[0.293,1.216]],"o":[[0.213,0.699],[0.483,1.706],[-0.792,0.588],[0,0]],"v":[[-1.492,-5.389],[-0.371,-1.586],[-1.178,6.331],[-3.075,3.985]],"c":false}]},{"t":85.833984375,"s":[{"i":[[0,0],[-0.123,-1.365],[0.408,-0.951],[0.093,1.216]],"o":[[0.068,0.699],[0.154,1.706],[-0.253,0.588],[0,0]],"v":[[-2,-5.389],[-1.642,-1.586],[-1.9,6.331],[-2.505,3.985]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.596078455448,0.321568638086,0.239215686917,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.12,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[272.026,152.086],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Nose","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[-3.926,7.277],[-8.848,0],[-3.783,2.669],[18.309,-0.059],[0.81,-15.17],[-4.916,0]],"o":[[3.926,7.277],[4.982,0],[-0.845,-15.213],[-18.267,0.059],[3.755,2.604],[8.848,0]],"v":[[-0.106,3.616],[20.359,15.839],[33.729,11.589],[-0.12,-15.891],[-33.793,11.7],[-20.571,15.839]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[-3.803,7.248],[-8.57,0],[-3.664,2.658],[17.733,-0.059],[0.785,-15.11],[-4.761,0]],"o":[[3.803,7.248],[4.825,0],[-0.819,-15.153],[-17.692,0.058],[3.637,2.594],[8.57,0]],"v":[[0.379,4.215],[20.2,16.39],[33.149,12.156],[0.365,-15.216],[-32.248,12.267],[-19.442,16.39]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[-3.295,7.158],[-7.426,0],[-3.175,2.625],[15.365,-0.058],[0.472,-17.15],[-4.125,0]],"o":[[3.295,7.158],[4.181,0],[-0.823,-14.189],[-15.33,0.058],[3.151,2.562],[7.426,0]],"v":[[-0.465,5.074],[17.661,15.91],[29.594,11.253],[1.662,-15.066],[-26.597,13.501],[-16.213,17.098]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[-1.951,7.158],[-4.397,0],[-1.88,2.625],[9.099,-0.058],[0.279,-17.15],[-2.443,0]],"o":[[1.951,7.158],[2.476,0],[-0.487,-14.189],[-9.078,0.058],[1.866,2.562],[4.397,0]],"v":[[-0.259,5.074],[10.475,15.91],[17.542,11.253],[1.001,-15.066],[-15.734,13.501],[-9.585,17.098]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[-0.956,7.158],[-2.154,0],[-0.921,2.625],[4.456,-0.058],[0.137,-17.15],[-1.196,0]],"o":[[0.956,7.158],[1.213,0],[-0.239,-14.189],[-4.446,0.058],[0.914,2.562],[2.154,0]],"v":[[-0.818,5.074],[4.203,15.91],[8.137,11.253],[0.036,-15.066],[-7.924,13.501],[-5.622,16.388]],"c":true}]},{"t":85.833984375,"s":[{"i":[[-0.305,7.158],[-0.687,0],[-0.294,2.625],[1.422,-0.058],[0.044,-17.15],[-0.382,0]],"o":[[0.305,7.158],[0.387,0],[-0.076,-14.189],[-1.419,0.058],[0.292,2.562],[0.687,0]],"v":[[-0.725,5.074],[0.877,15.91],[2.132,11.253],[-0.453,-15.066],[-2.992,13.501],[-2.258,16.388]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,9.805],[9.805,0],[0,-9.805],[-9.805,0]],"o":[[0,-9.805],[-9.805,0],[0,9.805],[9.805,0]],"v":[[-6.59,-22.526],[-24.345,-40.281],[-42.099,-22.526],[-24.345,-4.772]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[0,9.767],[9.497,0],[0,-9.767],[-9.497,0]],"o":[[0,-9.767],[-9.497,0],[0,9.767],[9.497,0]],"v":[[-5.901,-21.825],[-23.097,-39.51],[-40.292,-21.825],[-23.097,-4.14]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,9.646],[7.896,0],[0,-9.646],[-7.896,0]],"o":[[0,-9.646],[-7.896,0],[0,9.646],[7.896,0]],"v":[[-4.974,-20.167],[-18.558,-37.632],[-33.567,-20.167],[-19.984,-2.94]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0,9.646],[4.638,0],[0,-9.646],[-4.639,0]],"o":[[0,-9.646],[-4.638,0],[0,9.646],[4.639,0]],"v":[[-2.828,-18.982],[-10.807,-36.448],[-19.625,-18.982],[-11.645,-1.755]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0,9.435],[2.13,0],[0,-9.435],[-2.131,0]],"o":[[0,-9.435],[-2.13,0],[0,9.435],[2.131,0]],"v":[[-2.351,-18.418],[-6.016,-35.501],[-10.066,-18.418],[-6.401,-1.568]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0,9.435],[0.68,0],[0,-9.435],[-0.68,0]],"o":[[0,-9.435],[-0.68,0],[0,9.435],[0.68,0]],"v":[[-1.214,-18.418],[-2.384,-35.501],[-3.676,-18.418],[-2.506,-1.568]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.337254911661,0.23137255013,0.129411771894,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.47,108.276],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Hair","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[-7.446,13.962],[0,0],[0,0],[-0.313,2.62],[0.015,4.351],[0,0],[-18.83,0.06],[-0.051,-15.806],[0,0],[-0.49,-2.669],[-1.873,-4.352],[-0.89,-3.988],[0,-4.307],[8.014,-5.508],[12.335,-0.04],[8.035,5.309],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.222,-4.479],[0.309,-2.598],[0,0],[-0.051,-15.806],[18.83,-0.06],[0,0],[0.013,4.276],[0.495,2.702],[3.604,8.387],[0.884,3.962],[0,7.459],[-7.997,5.495],[-12.35,0.04],[-8.04,-5.313],[0,0],[0,-6.072]],"v":[[-36.861,-4.598],[-36.804,-4.703],[-36.772,-4.819],[-34.61,-14.535],[-34.324,-24.06],[-34.324,-24.128],[-0.614,-53.129],[33.278,-24.345],[33.278,-24.252],[33.789,-14.641],[37.134,-4.922],[43.41,12.489],[44.307,23.718],[31.53,43.997],[0.092,53.129],[-31.449,44.589],[-44.307,24.009],[-44.307,23.977]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[-7.211,13.907],[0,0],[0,0],[-0.301,2.61],[0.014,4.334],[0,0],[-18.237,0.06],[-0.049,-15.744],[0,0],[-0.473,-2.659],[-1.812,-4.336],[-0.861,-3.972],[0,-4.29],[7.763,-5.486],[11.947,-0.04],[7.782,5.289],[0,7.963],[0,0]],"o":[[0,0],[0,0],[1.183,-4.461],[0.299,-2.588],[0,0],[-0.049,-15.745],[18.237,-0.06],[0,0],[0.012,4.259],[0.479,2.691],[3.491,8.354],[0.856,3.947],[0,7.43],[-7.746,5.474],[-11.961,0.04],[-7.787,-5.292],[0,0],[0,-6.048]],"v":[[-35.236,-4.113],[-35.18,-4.218],[-35.15,-4.333],[-33.055,-14.011],[-32.779,-23.499],[-32.779,-23.567],[-0.13,-52.454],[32.696,-23.783],[32.696,-23.69],[33.191,-14.117],[36.43,-4.435],[42.509,12.908],[43.377,24.093],[31.002,44.292],[0.554,53.389],[-29.994,44.882],[-42.447,24.383],[-42.447,24.35]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[-6.248,13.734],[0,0],[0,0],[-0.261,2.577],[0.012,4.28],[0,0],[-15.802,0.059],[-0.043,-15.549],[0,0],[-0.41,-2.626],[-1.57,-4.282],[-0.746,-3.923],[0,-4.237],[6.726,-5.417],[10.352,-0.039],[6.743,5.223],[0,7.864],[0,0]],"o":[[0,0],[0,0],[1.025,-4.406],[0.259,-2.556],[0,0],[-0.237,-16.662],[15.802,-0.059],[0,0],[0.011,4.206],[0.415,2.658],[3.025,8.25],[0.742,3.898],[0,7.337],[-6.712,5.406],[-10.364,0.039],[-6.747,-5.226],[0,0],[0,-5.973]],"v":[[-29.256,-3.138],[-29.208,-3.242],[-29.181,-3.355],[-27.366,-12.913],[-27.127,-22.283],[-27.127,-22.35],[1.163,-52.304],[29.606,-22.564],[29.606,-22.471],[30.035,-13.018],[32.842,-3.456],[38.109,13.671],[38.861,24.717],[28.139,44.665],[2.706,53.173],[-23.764,44.772],[-34.554,24.528],[-34.554,24.496]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[-3.7,13.734],[0,0],[0,0],[-0.155,2.577],[0.007,4.28],[0,0],[-9.358,0.059],[-0.025,-15.549],[0,0],[-0.243,-2.626],[-0.93,-4.282],[-0.442,-3.923],[0,-4.237],[3.983,-5.417],[6.13,-0.039],[3.993,5.223],[0,7.864],[0,0]],"o":[[0,0],[0,0],[0.607,-4.406],[0.153,-2.556],[0,0],[-0.14,-16.662],[9.358,-0.059],[0,0],[0.006,4.206],[0.246,2.658],[1.791,8.25],[0.439,3.898],[0,7.337],[-3.975,5.406],[-6.138,0.039],[-3.996,-5.226],[0,0],[0,-5.973]],"v":[[-17.523,-3.138],[-17.495,-3.242],[-17.479,-3.355],[-16.405,-12.913],[-16.263,-22.283],[-16.263,-22.35],[0.491,-52.304],[17.334,-22.564],[17.334,-22.471],[17.588,-13.018],[19.251,-3.456],[22.37,13.671],[22.815,24.717],[16.465,44.665],[1.404,53.173],[-14.271,44.772],[-20.661,24.528],[-20.661,24.496]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[-1.812,13.734],[0,0],[0,0],[-0.076,2.577],[0.004,4.28],[0,0],[-4.583,0.059],[-0.012,-15.549],[0,0],[-0.119,-2.626],[-0.455,-4.282],[-0.216,-3.923],[0,-4.237],[1.951,-5.417],[3.002,-0.039],[1.956,5.223],[0,7.864],[0,0]],"o":[[0,0],[0,0],[0.297,-4.406],[0.075,-2.556],[0,0],[-0.069,-16.662],[4.583,-0.059],[0,0],[0.003,4.206],[0.12,2.658],[0.877,8.25],[0.215,3.898],[0,7.337],[-1.947,5.406],[-3.006,0.039],[-1.957,-5.226],[0,0],[0,-5.973]],"v":[[-9.069,-3.138],[-9.055,-3.242],[-9.047,-3.355],[-8.521,-12.913],[-8.451,-22.283],[-8.451,-22.35],[-0.482,-52.304],[8.004,-22.564],[8.004,-22.471],[8.128,-13.018],[8.469,-3.22],[9.997,13.434],[10.215,24.48],[7.578,44.665],[0.202,53.173],[-7.476,44.772],[-10.605,24.528],[-10.605,24.496]],"c":true}]},{"t":85.833984375,"s":[{"i":[[-0.578,13.734],[0,0],[0,0],[-0.024,2.577],[0.001,4.28],[0,0],[-1.462,0.059],[-0.004,-15.549],[0,0],[-0.038,-2.626],[-0.145,-4.282],[-0.069,-3.923],[0,-4.237],[0.622,-5.417],[0.958,-0.039],[0.624,5.223],[0,7.864],[0,0]],"o":[[0,0],[0,0],[0.095,-4.406],[0.024,-2.556],[0,0],[-0.022,-16.662],[1.462,-0.059],[0,0],[0.001,4.206],[0.038,2.658],[0.28,8.25],[0.069,3.898],[0,7.337],[-0.621,5.406],[-0.959,0.039],[-0.624,-5.226],[0,0],[0,-5.973]],"v":[[-3.716,-3.138],[-3.711,-3.242],[-3.709,-3.355],[-3.541,-12.913],[-3.519,-22.283],[-3.519,-22.35],[-0.976,-52.304],[1.731,-22.564],[1.731,-22.471],[1.771,-13.018],[1.88,-3.22],[2.367,13.434],[2.437,24.48],[1.596,44.665],[-0.758,53.173],[-3.207,44.772],[-4.206,24.528],[-4.206,24.496]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[4.598,1.656],[-2.64,7.412],[-4.598,-1.656],[2.64,-7.412]],"o":[[-4.6,-1.657],[2.64,-7.412],[4.6,1.657],[-2.64,7.412]],"v":[[30.635,-0.09],[27.088,-16.51],[40.195,-26.931],[43.742,-10.511]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[4.453,1.649],[-2.557,7.383],[-4.453,-1.649],[2.557,-7.383]],"o":[[-4.455,-1.65],[2.557,-7.383],[4.455,1.65],[-2.557,7.383]],"v":[[30.136,0.377],[26.7,-15.979],[39.395,-26.359],[42.831,-10.003]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[3.958,1.629],[-2.273,7.291],[-3.958,-1.629],[2.273,-7.291]],"o":[[-3.96,-1.63],[2.273,-7.291],[3.96,1.63],[-2.273,7.291]],"v":[[26.809,-1.08],[23.755,-17.233],[35.038,-27.484],[38.092,-11.332]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[2.344,1.629],[-1.346,7.291],[-2.344,-1.629],[1.346,-7.291]],"o":[[-2.345,-1.63],[1.346,-7.291],[2.345,1.63],[-1.346,7.291]],"v":[[15.441,-1.791],[13.633,-17.943],[20.314,-28.195],[22.123,-12.042]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[1.148,1.629],[-0.659,7.291],[-1.148,-1.629],[0.659,-7.291]],"o":[[-1.149,-1.63],[0.659,-7.291],[1.148,1.63],[-0.659,7.291]],"v":[[7.076,-1.791],[6.191,-17.943],[9.463,-28.195],[10.349,-12.042]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0.366,1.629],[-0.21,7.291],[-0.366,-1.629],[0.21,-7.291]],"o":[[-0.366,-1.63],[0.21,-7.291],[0.366,1.63],[-0.21,7.291]],"v":[[1.436,-1.791],[1.153,-17.943],[2.197,-28.195],[2.48,-12.042]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[4.598,-1.656],[2.64,7.412],[-4.598,1.656],[-2.64,-7.412]],"o":[[-4.6,1.657],[-2.64,-7.412],[4.6,-1.657],[2.64,7.412]],"v":[[-32.206,-0.025],[-45.315,-10.446],[-41.768,-26.866],[-28.659,-16.445]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[4.453,-1.65],[2.557,7.383],[-4.453,1.65],[-2.557,-7.383]],"o":[[-4.455,1.651],[-2.557,-7.383],[4.455,-1.651],[2.557,7.383]],"v":[[-30.727,0.442],[-43.424,-9.938],[-39.988,-26.294],[-27.292,-15.913]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[3.859,-1.629],[2.216,7.291],[-3.859,1.629],[-2.216,-7.291]],"o":[[-3.86,1.63],[-2.216,-7.291],[3.86,-1.63],[2.216,7.291]],"v":[[-25.349,1.36],[-36.351,-8.891],[-33.374,-25.043],[-22.372,-14.792]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[2.285,-1.629],[1.312,7.291],[-2.285,1.629],[-1.312,-7.291]],"o":[[-2.286,1.63],[-1.312,-7.291],[2.286,-1.63],[1.312,7.291]],"v":[[-14.973,2.071],[-21.488,-8.18],[-19.725,-24.332],[-13.21,-14.081]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[1.119,-1.629],[0.643,7.291],[-1.119,1.629],[-0.643,-7.291]],"o":[[-1.12,1.63],[-0.643,-7.291],[1.12,-1.63],[0.643,7.291]],"v":[[-7.819,2.071],[-11.01,-8.18],[-10.147,-24.332],[-6.956,-14.081]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0.357,-1.629],[0.205,7.291],[-0.357,1.629],[-0.205,-7.291]],"o":[[-0.357,1.63],[-0.205,-7.291],[0.357,-1.63],[0.205,7.291]],"v":[[-3.317,2.071],[-4.335,-8.18],[-4.06,-24.332],[-3.042,-14.081]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431391716,0.403921574354,0.305882364511,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.996,145.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Head","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[32.75,0],[9.05,-8.723],[0,0],[0,0],[12.963,11.172]],"o":[[-36.75,0],[-9.05,8.723],[0,0],[0,0],[-12.963,-11.172]],"v":[[269.5,203.875],[202.375,231.25],[180.49,265.037],[357.49,265.037],[335.375,229.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[31.944,0],[8.827,-8.723],[0,0],[0,0],[12.644,11.172]],"o":[[-35.846,0],[-8.827,8.723],[0,0],[0,0],[-12.644,-11.172]],"v":[[269.549,203.875],[204.076,231.25],[186.694,262.646],[356.307,266.949],[333.803,229.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[29.964,0.368],[8.765,-9.559],[0,0],[0,0],[10.827,12.493]],"o":[[-28.262,-0.347],[-8.765,9.559],[0,0],[0,0],[-10.827,-12.493]],"v":[[266.904,204.072],[217.226,226.01],[201.569,257.67],[350.704,277.632],[328.366,234.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[17.745,0.368],[4.241,-8.325],[0,0],[0,0],[6.509,11.788]],"o":[[-16.737,-0.347],[-4.241,8.325],[0,0],[0,0],[-6.509,-11.788]],"v":[[266.953,203.361],[239.192,224.352],[231.398,251.757],[320.169,284.732],[304.709,236.885]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[8.691,0.368],[2.251,-8.436],[0,0],[0,0],[2.817,13.796]],"o":[[-8.197,-0.347],[-2.251,8.436],[0,0],[0,0],[-2.817,-13.796]],"v":[[268.056,203.598],[254.994,222.223],[251.276,250.581],[295.322,287.805],[287.494,239.724]],"c":true}]},{"t":85.833984375,"s":[{"i":[[2.773,0.368],[0.492,-8.596],[0,0],[0,0],[0.699,12.45]],"o":[[-2.615,-0.347],[-0.492,8.596],[0,0],[0,0],[-0.699,-12.45]],"v":[[269.236,203.598],[265.068,222.223],[263.435,250.823],[278.044,286.855],[275.437,239.724]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.105865478516,0.450958251953,0.901947021484,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Body","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[403.855,266.954],[134.263,267.281],[133.746,34.114],[403.983,33.534]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[407.68,272.93],[141.435,262.261],[141.874,38.656],[408.046,28.992]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72.5,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[405.196,278.275],[150.215,255.604],[150.652,44.48],[407.113,22.696]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[395.085,285.526],[168.051,250.854],[169.441,49.35],[394.738,15.446]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[372.271,290.107],[194.58,245.643],[195.101,57.011],[373.426,8.258]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[351.971,291.685],[212.112,242.326],[211.844,59.063],[352.098,8.576]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[310.1,293.583],[241.912,240.199],[241.881,60.48],[309.976,6.207]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[279.372,300.675],[263.178,242.092],[263.608,59.062],[278.826,1.479]],"c":true}]}],"ix":2},"nm":"Mask","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.815673828125,0.88232421875,0.980377197266,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Background","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":87,"st":60,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"CAMERA_CASING 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0,0],[0,13.689],[2.562,0.203],[0,0],[0.235,-13.941],[0,0],[-0.817,-3.47],[0,3.644],[0,21.647],[0,11.447]],"o":[[0,0],[0,-11.515],[-0.278,-0.022],[0,0],[-0.157,9.303],[0,0],[3.866,-2.97],[0,-3.644],[0,-5.657],[0,-17.18]],"v":[[355.812,-582.217],[355.812,-651.939],[349.438,-666.703],[349.561,-458.372],[349.68,-455.309],[349.716,-452.543],[349.634,-442.155],[355.812,-452.231],[355.812,-527.897],[355.812,-557.197]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84,"s":[{"i":[[0,0],[0.181,13.689],[3.692,-3.049],[0,0],[0.235,-13.941],[0,0],[-0.817,-3.47],[0.115,3.644],[0.08,21.647],[0.011,11.447]],"o":[[0,0],[-0.152,-11.515],[-0.215,0.151],[0,0],[-0.157,9.303],[0,0],[3.866,-2.97],[-0.115,-3.644],[-0.021,-5.657],[-0.017,-17.18]],"v":[[291.635,-587.821],[292.523,-742.756],[283.027,-757.519],[282.695,-373.749],[282.814,-370.686],[282.85,-362.418],[282.768,-352.029],[291.612,-362.105],[291.605,-533.5],[291.592,-562.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85,"s":[{"i":[[0,0],[0.355,13.689],[6.542,-0.922],[0,0],[0.37,-13.941],[0,0],[0.057,-3.845],[-0.362,3.644],[-0.253,21.647],[-0.035,11.447]],"o":[[0,0],[-0.298,-11.515],[-0.366,0.052],[0,0],[-0.247,9.303],[0,0],[5.807,-4.595],[0.362,-3.644],[0.066,-5.657],[0.053,-17.18]],"v":[[193.824,-593.342],[194.689,-728.224],[178.533,-742.988],[177.933,-357.043],[178.12,-353.042],[178.176,-343.402],[177.975,-332.388],[193.487,-347.215],[193.916,-523.397],[193.959,-552.697]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86,"s":[{"i":[[0,0],[0.484,13.689],[8.523,-3.859],[0,0],[0.505,-13.941],[0,0],[-1.753,-3.47],[-0.867,3.644],[-0.607,21.647],[-0.085,11.447]],"o":[[0,0],[-0.407,-11.515],[-0.429,0.194],[0,0],[-0.337,9.303],[0,0],[5.997,-7.47],[0.867,-3.644],[0.159,-5.657],[0.128,-17.18]],"v":[[75.679,-597.849],[75.758,-718.949],[53.719,-733.713],[53.693,-344.334],[53.947,-321.896],[54.024,-312.256],[53.705,-300.617],[75.218,-335.193],[75.9,-512.279],[76.004,-541.578]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87,"s":[{"i":[[0,0],[3.485,11.583],[8.955,-4.433],[-0.062,-3.091],[-3.221,-11.86],[0,0],[-2.544,-3.333],[-1.095,5.54],[-0.607,21.647],[3.44,10.582]],"o":[[0,0],[-3.366,-10.6],[-0.589,2.321],[0.062,3.09],[2.943,8.704],[0,0],[8.347,0.703],[0.794,-8.444],[0.159,-5.657],[-3.981,-16.052]],"v":[[0.007,-686.694],[-13.977,-729.743],[-42.282,-733.704],[-42.419,-351.824],[-35.506,-318.32],[-18.172,-282.143],[-17.207,-281.536],[4.303,-291.511],[7.882,-626.94],[5.009,-672.742]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88,"s":[{"i":[[0,0],[7.233,7.337],[10.246,-6.152],[-0.246,-12.344],[-9.136,-8.623],[0,0],[-4.913,-2.921],[-1.776,11.215],[-0.607,21.647],[9.871,10.62]],"o":[[0,0],[-7.601,-8.337],[-1.067,8.69],[0.246,12.344],[9.086,7.021],[0,0],[8.647,1.223],[0.576,-22.816],[0.159,-5.657],[-9.656,-13.055]],"v":[[-96.036,-712.675],[-118.855,-734.697],[-147.972,-735.599],[-146.863,-351.098],[-130.294,-314.045],[-110.979,-299.591],[-90.581,-288.487],[-69.43,-302.648],[-68.325,-642.738],[-74.855,-686.091]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,0],[9.484,3.793],[8.601,-12.274],[-0.441,-20.809],[-28.549,-9.001],[0,0],[-12.63,-1.571],[-1.485,15.967],[0.068,21.914],[19.53,9.541]],"o":[[0,0],[-23.711,-9.556],[-2.126,56.648],[0.441,20.809],[21.714,6.2],[0,0],[16.655,-5.885],[0.585,-24.671],[-0.31,-11.754],[-24.908,-11.845]],"v":[[-256.274,-730.569],[-286.913,-744.674],[-328.463,-731.93],[-328.345,-350.477],[-292.838,-292.848],[-262.739,-284.553],[-227.494,-275.913],[-199.734,-301.739],[-198.453,-680.107],[-223.793,-715.645]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93,"s":[{"i":[[0,0],[10.869,1.69],[0.025,-8.377],[-0.642,-28.87],[-23.311,-3.439],[0,0],[-22.891,-2.452],[-0.707,20.252],[1.081,22.315],[30.895,7.44]],"o":[[0,0],[-26.095,-4.058],[-0.004,1.43],[0.642,28.87],[18.067,2.665],[0,0],[13.227,-9.056],[0.707,-20.252],[-1.012,-20.899],[-21.169,-6.833]],"v":[[-441.081,-726.063],[-476.791,-733.581],[-512.894,-713.271],[-514.67,-343.981],[-483.267,-295.903],[-442.821,-290.271],[-385.72,-282.768],[-366.552,-319.504],[-367.271,-675.98],[-402.702,-717.321]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,0],[10.926,1.148],[0.925,-11.98],[-0.642,-28.87],[-25.802,-3.644],[0,0],[-22.916,-2.187],[-0.707,20.252],[1.081,22.315],[31.032,6.862]],"o":[[0,0],[-23.328,-2.596],[-0.085,1.425],[0.642,28.87],[22.11,3.111],[0,0],[17.386,-3.626],[0.707,-20.252],[-1.012,-20.899],[-22.091,-5.737]],"v":[[-499.893,-697.032],[-539.989,-704.551],[-576.438,-678.34],[-576.108,-326.385],[-541.208,-279.351],[-496.322,-272.988],[-445.018,-267.521],[-419.062,-304.309],[-419.608,-647.001],[-459.425,-688.969]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.293,-3.849],[0,0],[-22.941,-1.921],[-0.707,20.252],[1.081,22.315],[31.169,6.285]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[26.153,3.558],[0,0],[21.546,1.805],[0.707,-20.252],[-1.012,-20.899],[-23.012,-4.64]],"v":[[-551.186,-680.532],[-595.667,-688.051],[-632.464,-655.94],[-635.041,-321.32],[-596.641,-275.331],[-547.318,-268.237],[-501.809,-264.806],[-469.065,-301.646],[-464.426,-630.554],[-508.629,-673.148]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.293,-3.849],[0,0],[-22.942,-1.921],[-0.707,20.252],[1.081,22.315],[31.169,6.285]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[26.153,3.558],[0,0],[21.546,1.805],[0.707,-20.252],[-1.012,-20.899],[-23.012,-4.64]],"v":[[-580.634,-669.881],[-625.743,-675.52],[-662.539,-643.409],[-665.116,-312.548],[-626.717,-266.559],[-578.646,-260.091],[-531.884,-256.034],[-499.14,-292.875],[-493.875,-620.529],[-536.825,-663.123]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.293,-3.849],[0,0],[-22.941,-1.921],[-0.707,20.252],[1.081,22.315],[31.169,6.285]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[26.153,3.558],[0,0],[21.546,1.805],[0.707,-20.252],[-1.012,-20.899],[-23.012,-4.64]],"v":[[-607.577,-656.723],[-648.299,-660.483],[-685.096,-628.371],[-687.672,-308.789],[-649.273,-262.799],[-602.456,-256.958],[-554.441,-252.274],[-521.697,-289.115],[-523.323,-607.997],[-565.02,-650.592]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.293,-3.849],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[26.153,3.558],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-621.361,-629.154],[-663.337,-631.66],[-700.133,-599.549],[-700.203,-298.764],[-661.804,-252.774],[-612.481,-248.187],[-565.719,-246.009],[-535.481,-286.609],[-538.361,-582.935],[-580.058,-625.529]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.293,-3.849],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[26.153,3.558],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-620.108,-617.876],[-663.337,-619.129],[-700.133,-587.018],[-702.71,-292.498],[-664.311,-246.509],[-614.987,-244.427],[-570.731,-243.502],[-537.987,-280.343],[-535.855,-572.91],[-577.551,-615.504]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.457,-2.338],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[28.045,2.304],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-620.108,-605.344],[-663.337,-605.345],[-700.133,-573.233],[-699.589,-281.22],[-661.19,-235.231],[-614.372,-231.896],[-570.117,-230.971],[-537.373,-267.812],[-542.12,-561.631],[-576.298,-602.973]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.524,-1.302],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[23.032,1.051],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-615.095,-592.813],[-658.324,-592.813],[-695.121,-560.702],[-694.577,-276.207],[-656.177,-230.218],[-610.613,-229.39],[-568.864,-229.718],[-536.12,-266.559],[-535.855,-549.1],[-572.539,-591.695]],"c":true}]},{"t":126,"s":[{"i":[[0,0],[10.983,0.606],[1.825,-15.582],[-0.642,-28.87],[-28.524,-1.302],[0,0],[-21.06,0.551],[-0.707,20.252],[1.081,22.315],[29.62,2.879]],"o":[[0,0],[-20.561,-1.134],[-0.166,1.42],[0.642,28.87],[23.032,1.051],[0,0],[21.614,-0.566],[0.707,-20.252],[-1.012,-20.899],[-23.365,-2.271]],"v":[[-605.07,-581.535],[-648.299,-581.535],[-685.096,-549.424],[-685.805,-263.676],[-647.406,-217.687],[-601.841,-216.858],[-560.092,-217.187],[-527.348,-259.04],[-528.336,-541.582],[-562.514,-580.417]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294118524,0.250980407,0.262745112181,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":84,"op":342,"st":60,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"FRONT_SCREEN 4","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.25,-9.25],[0,0],[0.25,-0.5],[0,0],[0.5,9.25],[0,0],[-2,-2.5],[0,0]],"o":[[0,0],[0.25,3],[0,0],[-2,2.75],[0,0],[0,-16.016],[0,0],[1.75,3.5]],"v":[[-308,-465.5],[-308,477.25],[-308.25,483],[-356.25,568.25],[-364,573.75],[-364,-560.5],[-354.5,-565],[-310.5,-483]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[1,-13],[0,0],[5,-5],[0,0],[0,16.016],[0,0],[-18,-17],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-11.25,9],[0,0],[0,-16.016],[0,0],[5.5,4.5]],"v":[[-257,-464],[-257,468],[-270.5,493.062],[-345.5,568.5],[-358.25,553.75],[-357,-542.5],[-338,-559],[-265.5,-485.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[0.667,-14.005],[0,0],[8.672,-3.333],[0,0],[0,16.016],[0,0],[-17.339,-11.333],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-12.839,6],[0,0],[0,-16.016],[0,0],[9.005,3]],"v":[[-210.667,-462.333],[-210.667,466],[-229.333,495.708],[-334.833,568.667],[-353,549.167],[-352.167,-541.5],[-329.833,-562.167],[-226,-489.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0.333,-15.011],[0,0],[12.344,-1.667],[0,0],[0,16.016],[0,0],[-16.677,-5.667],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-14.427,3],[0,0],[0,-16.016],[0,0],[12.511,1.5]],"v":[[-167.833,-460.667],[-167.833,464],[-191.667,498.354],[-324.167,568.833],[-347.75,544.583],[-347.333,-540.5],[-321.667,-565.333],[-190,-493.833]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-127,-459],[-127,462],[-156,501],[-313.5,569],[-342.5,540],[-342.5,-539.5],[-313.5,-568.5],[-156,-498]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-89.667,-464.667],[-89.667,467.333],[-118.667,503],[-308.667,571.333],[-337.667,542.333],[-337.667,-541.333],[-308.667,-570.333],[-118.667,-500.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-54.833,-470.333],[-54.833,472.667],[-83.833,505],[-303.833,573.667],[-332.833,544.667],[-332.833,-543.167],[-303.833,-572.167],[-83.833,-502.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-24,-476],[-24,478],[-53,507],[-299,576],[-328,547],[-328,-545],[-299,-574],[-53,-505]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[6.073,-479.4],[6.073,481.5],[-22.927,510.5],[-297.12,577.1],[-326.12,548.1],[-326.12,-546.2],[-297.12,-575.2],[-22.927,-508.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[32.647,-482.8],[32.647,485],[3.647,514],[-295.24,578.2],[-324.24,549.2],[-324.24,-547.4],[-295.24,-576.4],[3.647,-511.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[57.22,-486.2],[57.22,488.5],[28.22,517.5],[-293.36,579.3],[-322.36,550.3],[-322.36,-548.6],[-293.36,-577.6],[28.22,-515.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[79.46,-489.6],[79.46,492],[50.46,521],[-291.48,580.4],[-320.48,551.4],[-320.48,-549.8],[-291.48,-578.8],[50.46,-518.6]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[99.7,-493],[99.7,495.5],[70.7,524.5],[-289.6,581.5],[-318.6,552.5],[-318.6,-551],[-289.6,-580],[70.7,-522]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[136.58,-499.8],[136.58,502.5],[107.58,531.5],[-285.84,583.7],[-314.84,554.7],[-314.84,-553.4],[-285.84,-582.4],[107.58,-528.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[179.4,-510],[179.4,513],[150.4,542],[-280.2,587],[-309.2,558],[-309.2,-557],[-280.2,-586],[150.4,-539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[201.28,-516],[201.28,518.8],[172.28,547.8],[-279.44,588.2],[-308.44,559.2],[-308.44,-558],[-279.44,-587],[172.28,-545]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[212.053,-519],[212.053,521.7],[183.053,550.7],[-279.06,588.8],[-308.06,559.8],[-308.06,-558.5],[-279.06,-587.5],[183.053,-548]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[220.827,-522],[220.827,524.6],[191.827,553.6],[-278.68,589.4],[-307.68,560.4],[-307.68,-559],[-278.68,-588],[191.827,-551]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[229.1,-525],[229.1,527.5],[200.1,556.5],[-278.3,590],[-307.3,561],[-307.3,-559.5],[-278.3,-588.5],[200.1,-554]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[256.46,-535.4],[256.46,537.5],[227.46,566.5],[-276.78,592.4],[-305.78,563.4],[-305.78,-561.5],[-276.78,-590.5],[227.46,-564.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[260.8,-538],[260.8,540],[231.8,569],[-276.4,593],[-305.4,564],[-305.4,-562],[-276.4,-591],[231.8,-567]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[273.82,-543.7],[273.82,546],[244.82,575],[-276.16,594.2],[-305.16,565.2],[-305.16,-562.6],[-276.16,-591.6],[244.82,-572.7]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[280.833,-547.5],[280.833,550],[251.833,579],[-276,595],[-305,566],[-305,-563],[-276,-592],[251.833,-576.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[294.867,-555],[294.867,558],[265.867,587],[-275.6,597],[-304.6,568],[-304.6,-564],[-275.6,-593],[265.867,-584]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":122.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[301.9,-559.5],[301.9,563],[272.9,592],[-275.7,597],[-304.7,568],[-304.7,-565],[-275.7,-594],[272.9,-588.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[306.933,-564],[306.933,568],[277.933,597],[-275.8,597],[-304.8,568],[-304.8,-566],[-275.8,-595],[277.933,-593]],"c":true}]},{"t":135,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[310,-568],[310,570],[281,599],[-276,599],[-305,570],[-305,-568],[-276,-597],[281,-597]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.815686285496,0.882352948189,0.980392158031,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[384,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[102,102],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":88,"op":342,"st":60,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Figure 7","tt":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":93.334,"s":[0]},{"t":101.666015625,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[207.459,144.79,0],"to":[0.312,0,0],"ti":[-1.146,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":62.779,"s":[209.334,144.79,0],"to":[1.146,0,0],"ti":[-2.125,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65.555,"s":[214.334,144.79,0],"to":[2.125,0,0],"ti":[-2.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[222.084,144.79,0],"to":[2.812,0,0],"ti":[-4.396,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":71.111,"s":[231.209,144.79,0],"to":[4.396,0,0],"ti":[-5.104,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[248.459,144.79,0],"to":[5.104,0,0],"ti":[-3.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.223,"s":[261.834,144.79,0],"to":[3.812,0,0],"ti":[-2.667,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.779,"s":[271.334,144.79,0],"to":[2.667,0,0],"ti":[-1.812,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[277.834,144.79,0],"to":[1.812,0,0],"ti":[-1.083,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98.889,"s":[282.209,144.79,0],"to":[1.083,0,0],"ti":[-0.458,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.445,"s":[284.334,144.79,0],"to":[0.458,0,0],"ti":[-0.104,0,0]},{"t":110,"s":[284.959,144.79,0]}],"ix":2,"l":2},"a":{"a":0,"k":[270.209,145.54,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.55,0.55,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.001]},"t":60,"s":[108,108,100]},{"t":110,"s":[106.5,106.5,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0,0],[-0.095,-1.387],[0.299,-1.3],[0.045,1.308]],"o":[[0.052,0.711],[0.119,1.734],[-0.239,1.04],[0,0]],"v":[[-57.799,-6.034],[-57.522,-2.167],[-57.671,6.182],[-58.104,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0,0],[-0.232,-1.387],[0.725,-1.3],[0.109,1.308]],"o":[[0.127,0.711],[0.29,1.734],[-0.58,1.04],[0,0]],"v":[[-53.069,-6.034],[-52.397,-2.167],[-52.759,6.182],[-53.809,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,0],[-0.59,-1.387],[1.846,-1.3],[0.276,1.308]],"o":[[0.325,0.711],[0.738,1.734],[-1.477,1.04],[0,0]],"v":[[-41.097,-6.034],[-39.386,-2.167],[-40.307,6.182],[-42.981,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,0],[-0.953,-1.387],[2.979,-1.3],[0.446,1.308]],"o":[[0.524,0.711],[1.191,1.734],[-2.384,1.04],[0,0]],"v":[[-29.746,-6.034],[-26.986,-2.167],[-28.473,6.182],[-32.788,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,0],[-1.277,-1.387],[3.993,-1.3],[0.598,1.308]],"o":[[0.702,0.711],[1.596,1.734],[-3.195,1.04],[0,0]],"v":[[-19.728,-6.034],[-16.028,-2.167],[-18.021,6.182],[-23.805,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[0,0],[-1.471,-1.387],[4.599,-1.3],[0.688,1.308]],"o":[[0.809,0.711],[1.838,1.734],[-3.68,1.04],[0,0]],"v":[[-13.958,-6.034],[-9.695,-2.167],[-11.991,6.182],[-18.654,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,0],[-1.612,-1.387],[5.042,-1.3],[0.754,1.308]],"o":[[0.886,0.711],[2.015,1.734],[-4.034,1.04],[0,0]],"v":[[-9.542,-6.034],[-4.87,-2.167],[-7.386,6.182],[-14.689,3.557]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[0,0],[-1.826,-1.387],[5.71,-1.3],[0.854,1.308]],"o":[[1.004,0.711],[2.282,1.734],[-4.569,1.04],[0,0]],"v":[[-8.492,-6.268],[-3.201,-2.402],[-6.051,5.947],[-14.321,3.322]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[0,0],[-1.802,-1.387],[5.635,-1.3],[0.843,1.308]],"o":[[0.991,0.711],[2.252,1.734],[-4.509,1.04],[0,0]],"v":[[-5.336,-6.268],[-0.114,-2.402],[-2.926,5.947],[-11.089,3.322]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,0],[-1.899,-1.387],[5.939,-1.3],[0.889,1.308]],"o":[[1.044,0.711],[2.374,1.734],[-4.752,1.04],[0,0]],"v":[[-2.334,-6.268],[3.169,-2.402],[0.205,5.947],[-8.397,3.322]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,0],[-1.939,-1.387],[6.061,-1.3],[0.907,1.308]],"o":[[1.066,0.711],[2.423,1.734],[-4.85,1.04],[0,0]],"v":[[-0.934,-6.268],[4.683,-2.402],[1.658,5.947],[-7.122,3.322]],"c":false}]},{"t":135,"s":[{"i":[[0,0],[-1.939,-1.387],[6.061,-1.3],[0.907,1.308]],"o":[[1.066,0.711],[2.423,1.734],[-4.85,1.04],[0,0]],"v":[[-0.582,-6.237],[5.035,-2.37],[2.01,5.979],[-6.77,3.354]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.596078455448,0.321568638086,0.239215686917,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.12,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[272.026,152.086],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Nose","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[-0.193,7.277],[-0.436,0],[-0.204,3.66],[0.902,-0.059],[0.071,-14.068],[-0.242,0]],"o":[[0.15,8.062],[0.245,0],[-0.171,-14.63],[-0.9,0.059],[0.185,2.604],[0.436,0]],"v":[[-56.249,5.695],[-55.215,18.856],[-54.713,13.904],[-56.412,-10.293],[-57.945,12.606],[-57.189,17.449]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[-0.47,7.277],[-1.059,0],[-0.496,3.66],[2.191,-0.059],[0.174,-14.068],[-0.588,0]],"o":[[0.364,8.062],[0.596,0],[-0.415,-14.63],[-2.185,0.059],[0.449,2.604],[1.059,0]],"v":[[-51.53,5.695],[-49.016,18.856],[-47.797,13.904],[-51.926,-10.293],[-55.649,12.606],[-53.813,17.449]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[-1.196,7.277],[-2.695,0],[-1.262,3.66],[5.577,-0.059],[0.442,-14.068],[-1.497,0]],"o":[[0.928,8.062],[1.517,0],[-1.057,-14.63],[-5.564,0.059],[1.144,2.604],[2.695,0]],"v":[[-39.584,5.695],[-33.184,18.856],[-30.08,13.904],[-40.592,-10.293],[-50.072,12.606],[-45.396,17.449]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[-1.93,7.277],[-4.349,0],[-1.859,2.669],[8.999,-0.059],[0.713,-14.068],[-2.416,0]],"o":[[1.497,8.062],[2.449,0],[-1.705,-14.63],[-8.978,0.059],[1.845,2.604],[4.349,0]],"v":[[-28.26,5.461],[-18.311,18.388],[-12.925,13.904],[-29.887,-10.293],[-45.183,12.606],[-38.394,17.215]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[-2.586,7.277],[-5.829,0],[-2.492,2.669],[12.062,-0.059],[0.956,-14.068],[-3.238,0]],"o":[[2.007,8.062],[3.282,0],[-0.557,-15.213],[-12.034,0.059],[2.474,2.604],[5.829,0]],"v":[[-18.266,5.461],[-4.931,18.388],[2.289,13.904],[-20.446,-11.464],[-40.949,12.606],[-31.849,17.215]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[-2.979,7.277],[-6.715,0],[-2.871,2.669],[13.894,-0.059],[0.615,-15.17],[-3.73,0]],"o":[[2.311,8.062],[3.781,0],[-0.642,-15.213],[-13.862,0.059],[2.849,2.604],[6.715,0]],"v":[[-12.51,4.992],[2.851,17.685],[11.168,13.904],[-15.292,-12.168],[-38.639,12.606],[-28.157,16.746]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[-3.266,7.277],[-7.36,0],[-3.147,2.669],[15.23,-0.059],[0.674,-15.17],[-4.089,0]],"o":[[2.534,8.062],[4.144,0],[-0.703,-15.213],[-15.194,0.059],[3.123,2.604],[7.36,0]],"v":[[-8.876,4.523],[7.962,17.215],[17.592,12.496],[-10.382,-13.106],[-37.517,11.903],[-26.284,16.511]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[-3.478,7.277],[-7.839,0],[-3.352,2.669],[16.221,-0.059],[0.718,-15.17],[-4.355,0]],"o":[[3.478,7.277],[4.414,0],[-0.749,-15.213],[-16.184,0.059],[3.327,2.604],[7.839,0]],"v":[[-5.981,4.288],[11.703,16.746],[21.96,12.496],[-7.336,-13.576],[-36.487,11.903],[-24.772,16.511]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[-3.65,7.277],[-8.226,0],[-3.517,2.669],[17.022,-0.059],[0.753,-15.17],[-4.57,0]],"o":[[3.65,7.277],[4.632,0],[-0.786,-15.213],[-16.983,0.059],[3.491,2.604],[8.226,0]],"v":[[-3.376,3.819],[15.182,16.511],[26.438,12.496],[-4.797,-14.514],[-35.635,11.668],[-23.342,16.511]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[-3.847,7.277],[-8.67,0],[-3.707,2.669],[17.939,-0.059],[0.794,-15.17],[-4.816,0]],"o":[[3.847,7.277],[4.881,0],[-0.828,-15.213],[-17.898,0.059],[3.679,2.604],[8.67,0]],"v":[[-1.836,3.584],[18.215,15.807],[31.315,11.557],[-1.85,-15.923],[-34.843,11.668],[-21.888,15.807]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[-3.926,7.277],[-8.848,0],[-3.783,2.669],[18.309,-0.059],[0.81,-15.17],[-4.916,0]],"o":[[3.926,7.277],[4.982,0],[-0.845,-15.213],[-18.267,0.059],[3.755,2.604],[8.848,0]],"v":[[-0.458,3.584],[20.007,15.807],[33.377,11.557],[-0.472,-15.923],[-34.145,11.668],[-20.923,15.807]],"c":true}]},{"t":135,"s":[{"i":[[-3.926,7.277],[-8.848,0],[-3.783,2.669],[18.309,-0.059],[0.81,-15.17],[-4.916,0]],"o":[[3.926,7.277],[4.982,0],[-0.845,-15.213],[-18.267,0.059],[3.755,2.604],[8.848,0]],"v":[[-0.106,3.616],[20.359,15.839],[33.729,11.589],[-0.12,-15.891],[-33.793,11.7],[-20.571,15.839]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0,9.283],[0.481,0],[0,-9.283],[-0.481,0]],"o":[[0,-9.283],[-0.481,0],[0,9.283],[0.481,0]],"v":[[-56.552,-17.322],[-57.474,-33.907],[-58.358,-17.994],[-57.435,-0.961]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0,9.283],[1.168,0],[0,-9.283],[-1.168,0]],"o":[[0,-9.283],[-1.168,0],[0,9.283],[1.168,0]],"v":[[-52.264,-17.322],[-54.505,-33.907],[-56.652,-17.994],[-54.41,-0.961]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,9.283],[2.973,0],[0,-9.283],[-2.973,0]],"o":[[0,-9.283],[-2.973,0],[0,9.283],[2.973,0]],"v":[[-41.453,-17.322],[-47.159,-33.907],[-52.623,-17.994],[-46.917,-0.961]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,9.283],[4.797,0],[0,-9.283],[-4.797,0]],"o":[[0,-9.283],[-4.797,0],[0,9.283],[4.797,0]],"v":[[-31.654,-18.258],[-40.861,-34.843],[-49.678,-18.931],[-40.471,-1.898]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,9.532],[6.43,0],[0,-9.532],[-6.43,0]],"o":[[0,-9.532],[-6.43,0],[0,9.532],[6.43,0]],"v":[[-22.815,-18.985],[-35.156,-36.015],[-46.974,-19.676],[-34.633,-2.186]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[0,9.532],[7.407,0],[0,-9.532],[-7.407,0]],"o":[[0,-9.532],[-7.407,0],[0,9.532],[7.407,0]],"v":[[-18.02,-19.689],[-32.236,-36.718],[-45.85,-20.379],[-31.633,-2.89]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,9.723],[8.119,0],[0,-9.723],[-8.119,0]],"o":[[0,-9.723],[-8.119,0],[0,9.723],[8.119,0]],"v":[[-14.659,-20.051],[-30.241,-37.422],[-45.163,-20.755],[-29.58,-2.915]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[0,9.723],[8.648,0],[0,-9.723],[-8.648,0]],"o":[[0,-9.723],[-8.648,0],[0,9.723],[8.648,0]],"v":[[-11.89,-20.755],[-28.488,-38.126],[-44.381,-21.459],[-27.784,-3.619]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[0,9.723],[9.075,0],[0,-9.723],[-9.075,0]],"o":[[0,-9.723],[-9.075,0],[0,9.723],[9.075,0]],"v":[[-10.316,-21.459],[-26.748,-39.065],[-43.18,-21.459],[-26.748,-3.854]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,9.805],[9.607,0],[0,-9.805],[-9.607,0]],"o":[[0,-9.805],[-9.607,0],[0,9.805],[9.607,0]],"v":[[-8.19,-22.558],[-25.585,-40.312],[-42.981,-22.558],[-25.585,-4.804]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,9.805],[9.805,0],[0,-9.805],[-9.805,0]],"o":[[0,-9.805],[-9.805,0],[0,9.805],[9.805,0]],"v":[[-6.942,-22.558],[-24.697,-40.312],[-42.451,-22.558],[-24.697,-4.804]],"c":true}]},{"t":135,"s":[{"i":[[0,9.805],[9.805,0],[0,-9.805],[-9.805,0]],"o":[[0,-9.805],[-9.805,0],[0,9.805],[9.805,0]],"v":[[-6.59,-22.526],[-24.345,-40.281],[-42.099,-22.526],[-24.345,-4.772]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.337254911661,0.23137255013,0.129411771894,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.47,108.276],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Hair","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[-0.312,13.512],[0,0],[0,0],[-0.015,2.62],[0.001,4.351],[0,0],[-0.927,0.06],[-0.139,-15.255],[0,0],[-0.024,-2.669],[-0.092,-4.353],[-0.044,-3.988],[0,-4.307],[0.295,-3.002],[0.482,-0.349],[0.391,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.06,-4.479],[0.015,-2.598],[0,0],[-0.003,-15.806],[0.927,-0.06],[0,0],[0.001,4.276],[0.024,2.702],[0.178,8.387],[0.044,3.962],[0,7.459],[-0.49,4.981],[-0.608,0.441],[-0.407,-5.108],[0,0],[0,-6.072]],"v":[[-58.689,-3.691],[-58.726,-4.5],[-58.724,-4.616],[-58.676,-14.567],[-58.471,-23.153],[-58.471,-23.222],[-56.937,-47.531],[-55.247,-22.97],[-55.247,-22.876],[-55.237,-14.204],[-55.068,-5.188],[-54.763,11.754],[-54.743,22.514],[-55.47,41.151],[-56.849,48.639],[-58.387,41.742],[-59.021,24.251],[-59.021,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[-0.757,13.512],[0,0],[0,0],[-0.037,2.62],[0.002,4.351],[0,0],[-2.253,0.06],[-0.337,-15.255],[0,0],[-0.058,-2.669],[-0.224,-4.353],[-0.106,-3.988],[0,-4.307],[0.717,-3.002],[1.17,-0.349],[0.95,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.146,-4.479],[0.037,-2.598],[0,0],[-0.006,-15.806],[2.253,-0.06],[0,0],[0.001,4.276],[0.059,2.702],[0.431,8.387],[0.106,3.962],[0,7.459],[-1.19,4.981],[-1.476,0.441],[-0.989,-5.108],[0,0],[0,-6.072]],"v":[[-56.705,-3.691],[-56.793,-4.5],[-56.789,-4.616],[-56.672,-14.567],[-56.176,-23.153],[-56.176,-23.222],[-52.448,-47.531],[-48.344,-22.97],[-48.344,-22.876],[-48.32,-14.204],[-47.909,-5.188],[-47.168,11.754],[-47.118,22.514],[-48.884,41.151],[-52.234,48.639],[-55.971,41.742],[-57.511,24.251],[-57.511,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[-1.928,13.512],[0,0],[0,0],[-0.095,2.62],[0.005,4.351],[0,0],[-5.736,0.06],[-0.858,-15.255],[0,0],[-0.149,-2.669],[-0.57,-4.353],[-0.271,-3.988],[0,-4.307],[1.826,-3.002],[2.98,-0.349],[2.418,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.372,-4.479],[0.094,-2.598],[0,0],[-0.015,-15.806],[5.736,-0.06],[0,0],[0.004,4.276],[0.151,2.702],[1.098,8.387],[0.269,3.962],[0,7.459],[-3.03,4.981],[-3.758,0.441],[-2.518,-5.108],[0,0],[0,-6.072]],"v":[[-51.946,-3.691],[-52.17,-4.5],[-52.161,-4.616],[-51.862,-14.567],[-50.599,-23.153],[-50.599,-23.222],[-41.108,-47.531],[-30.66,-22.97],[-30.66,-22.876],[-30.598,-14.204],[-29.552,-5.188],[-27.667,11.754],[-27.539,22.514],[-32.036,41.151],[-40.564,48.639],[-50.078,41.742],[-53.999,24.251],[-53.999,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[-3.112,13.512],[0,0],[0,0],[-0.153,2.62],[0.007,4.351],[0,0],[-9.255,0.06],[-1.384,-15.255],[0,0],[-0.24,-2.669],[-0.919,-4.353],[-0.437,-3.988],[0,-4.307],[2.946,-3.002],[4.808,-0.349],[3.902,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.601,-4.479],[0.152,-2.598],[0,0],[-0.025,-15.806],[9.255,-0.06],[0,0],[0.006,4.276],[0.243,2.702],[1.772,8.387],[0.434,3.962],[0,7.459],[-4.889,4.981],[-6.063,0.441],[-4.063,-5.108],[0,0],[0,-6.072]],"v":[[-47.884,-3.691],[-48.247,-4.5],[-48.231,-4.616],[-47.749,-14.567],[-45.712,-23.153],[-45.712,-23.222],[-30.397,-47.531],[-13.538,-22.97],[-13.538,-22.876],[-13.439,-14.204],[-11.75,-5.188],[-8.709,11.754],[-8.502,22.514],[-15.759,41.151],[-29.518,48.639],[-44.87,41.742],[-51.198,24.251],[-51.198,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[-4.171,13.512],[0,0],[0,0],[-0.205,2.62],[0.01,4.351],[0,0],[-12.405,0.06],[-0.034,-15.806],[0,0],[-0.322,-2.669],[-1.232,-4.353],[-0.586,-3.988],[0,-4.307],[5.048,-4.141],[6.445,-0.349],[5.23,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.805,-4.479],[0.203,-2.598],[0,0],[-0.033,-15.806],[12.405,-0.06],[0,0],[0.008,4.276],[0.326,2.702],[2.374,8.387],[0.582,3.962],[0,7.459],[-6.294,5.164],[-8.127,0.441],[-5.446,-5.108],[0,0],[0,-6.072]],"v":[[-44.39,-3.691],[-44.876,-4.5],[-44.856,-4.616],[-44.21,-14.567],[-41.478,-23.153],[-41.478,-23.222],[-20.951,-48.702],[1.646,-22.97],[1.646,-22.876],[1.78,-14.204],[4.043,-5.188],[8.119,11.754],[8.71,22.982],[-1.017,41.619],[-19.46,49.108],[-40.037,42.211],[-48.832,24.251],[-48.832,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[-4.804,13.512],[0,0],[0,0],[-0.236,2.62],[0.011,4.351],[0,0],[-14.289,0.06],[-0.039,-15.806],[0,0],[-0.371,-2.669],[-1.42,-4.353],[-0.675,-3.988],[0,-4.307],[6.374,-5.095],[7.424,-0.349],[6.025,4.905],[0,7.994],[0,0]],"o":[[0,0],[0,0],[0.927,-4.479],[0.234,-2.598],[0,0],[-0.038,-15.806],[14.289,-0.06],[0,0],[0.01,4.276],[0.375,2.702],[2.735,8.387],[0.671,3.962],[0,7.459],[-6.899,5.515],[-9.362,0.441],[-6.274,-5.108],[0,0],[0,-6.072]],"v":[[-42.523,-3.691],[-43.083,-4.5],[-43.059,-4.616],[-42.315,-14.567],[-39.169,-23.153],[-39.169,-23.222],[-15.793,-49.406],[10.507,-22.97],[10.507,-22.876],[10.661,-14.204],[13.268,-5.188],[17.964,11.754],[18.644,22.982],[7.44,42.557],[-13.805,50.046],[-37.508,43.149],[-47.64,24.251],[-47.64,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[-5.266,13.512],[0,0],[0,0],[-0.259,2.62],[0.012,4.351],[0,0],[-15.663,0.06],[-0.042,-15.806],[0,0],[-0.407,-2.669],[-1.556,-4.353],[-0.74,-3.988],[0,-4.307],[6.987,-5.095],[10.261,-0.04],[6.684,5.31],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.016,-4.479],[0.257,-2.598],[0,0],[-0.042,-15.806],[15.663,-0.06],[0,0],[0.011,4.276],[0.411,2.702],[2.998,8.387],[0.735,3.962],[0,7.459],[-7.562,5.515],[-10.273,0.04],[-6.687,-5.313],[0,0],[0,-6.072]],"v":[[-41.467,-3.925],[-42.08,-4.735],[-42.054,-4.851],[-40.724,-14.567],[-38.047,-23.857],[-38.047,-23.925],[-10.882,-50.344],[16.919,-24.377],[16.919,-24.283],[17.344,-14.204],[19.173,-5.188],[24.835,11.754],[25.58,22.982],[14.071,42.792],[-10.245,50.515],[-36.741,43.149],[-46.56,24.251],[-46.56,24.218]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[-5.609,13.512],[0,0],[0,0],[-0.276,2.62],[0.013,4.351],[0,0],[-16.682,0.06],[-0.045,-15.806],[0,0],[-0.433,-2.669],[-1.658,-4.353],[-0.788,-3.988],[0,-4.307],[7.101,-5.507],[10.929,-0.04],[7.119,5.31],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.083,-4.479],[0.273,-2.598],[0,0],[-0.045,-15.806],[16.682,-0.06],[0,0],[0.011,4.276],[0.438,2.702],[3.193,8.387],[0.783,3.962],[0,7.459],[-7.085,5.495],[-10.942,0.04],[-7.123,-5.313],[0,0],[0,-6.072]],"v":[[-40.159,-3.925],[-40.813,-4.735],[-40.785,-4.851],[-38.869,-14.567],[-37.017,-23.857],[-37.017,-23.925],[-7.833,-50.813],[21.277,-24.377],[21.277,-24.283],[21.73,-14.673],[24.928,-5.188],[30.958,11.754],[31.752,22.982],[19.493,43.261],[-7.655,50.984],[-35.376,43.384],[-46.085,24.486],[-46.085,24.453]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[-5.886,13.512],[0,0],[0,0],[-0.289,2.62],[0.014,4.351],[0,0],[-17.506,0.06],[-0.047,-15.806],[0,0],[-0.454,-2.669],[-1.739,-4.353],[-0.827,-3.988],[0,-4.307],[7.452,-5.507],[11.468,-0.04],[7.47,5.31],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.136,-4.479],[0.287,-2.598],[0,0],[-0.047,-15.806],[17.506,-0.06],[0,0],[0.012,4.276],[0.46,2.702],[3.351,8.387],[0.822,3.962],[0,7.459],[-7.435,5.495],[-11.482,0.04],[-7.475,-5.313],[0,0],[0,-6.072]],"v":[[-39.463,-4.63],[-39.41,-4.735],[-39.38,-4.851],[-37.37,-14.567],[-36.165,-24.092],[-36.165,-24.16],[-5.294,-51.752],[25.747,-24.377],[25.747,-24.283],[26.223,-14.673],[29.332,-4.954],[35.168,12.458],[36.001,23.687],[24.122,43.965],[-5.107,51.923],[-34.197,43.853],[-45.681,24.016],[-45.681,23.984]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[-7.295,13.962],[0,0],[0,0],[-0.305,2.62],[0.015,4.351],[0,0],[-18.449,0.06],[-0.05,-15.806],[0,0],[-0.479,-2.669],[-1.833,-4.353],[-0.871,-3.988],[0,-4.307],[7.853,-5.507],[12.086,-0.04],[7.873,5.31],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.197,-4.479],[0.302,-2.598],[0,0],[-0.05,-15.806],[18.449,-0.06],[0,0],[0.012,4.276],[0.485,2.702],[3.532,8.387],[0.866,3.962],[0,7.459],[-7.836,5.495],[-12.101,0.04],[-7.877,-5.313],[0,0],[0,-6.072]],"v":[[-37.86,-4.63],[-37.804,-4.735],[-37.773,-4.851],[-35.654,-14.567],[-35.374,-24.092],[-35.374,-24.16],[-2.345,-53.161],[30.863,-24.377],[30.863,-24.283],[31.364,-14.673],[34.641,-4.954],[40.79,12.458],[41.669,23.687],[29.15,43.965],[-1.653,53.097],[-32.557,44.557],[-45.155,23.978],[-45.155,23.945]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[-7.446,13.962],[0,0],[0,0],[-0.313,2.62],[0.015,4.351],[0,0],[-18.83,0.06],[-0.051,-15.806],[0,0],[-0.49,-2.669],[-1.873,-4.352],[-0.89,-3.988],[0,-4.307],[8.014,-5.508],[12.335,-0.04],[8.035,5.309],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.222,-4.479],[0.309,-2.598],[0,0],[-0.051,-15.806],[18.83,-0.06],[0,0],[0.013,4.276],[0.495,2.702],[3.604,8.387],[0.884,3.962],[0,7.459],[-7.997,5.495],[-12.35,0.04],[-8.04,-5.313],[0,0],[0,-6.072]],"v":[[-37.213,-4.63],[-37.156,-4.735],[-37.125,-4.851],[-34.962,-14.567],[-34.676,-24.092],[-34.676,-24.16],[-0.966,-53.161],[32.926,-24.377],[32.926,-24.283],[33.437,-14.673],[36.782,-4.954],[43.058,12.458],[43.955,23.687],[31.177,43.965],[-0.26,53.097],[-31.801,44.557],[-44.659,23.978],[-44.659,23.945]],"c":true}]},{"t":135,"s":[{"i":[[-7.446,13.962],[0,0],[0,0],[-0.313,2.62],[0.015,4.351],[0,0],[-18.83,0.06],[-0.051,-15.806],[0,0],[-0.49,-2.669],[-1.873,-4.352],[-0.89,-3.988],[0,-4.307],[8.014,-5.508],[12.335,-0.04],[8.035,5.309],[0,7.994],[0,0]],"o":[[0,0],[0,0],[1.222,-4.479],[0.309,-2.598],[0,0],[-0.051,-15.806],[18.83,-0.06],[0,0],[0.013,4.276],[0.495,2.702],[3.604,8.387],[0.884,3.962],[0,7.459],[-7.997,5.495],[-12.35,0.04],[-8.04,-5.313],[0,0],[0,-6.072]],"v":[[-36.861,-4.598],[-36.804,-4.703],[-36.772,-4.819],[-34.61,-14.535],[-34.324,-24.06],[-34.324,-24.128],[-0.614,-53.129],[33.278,-24.345],[33.278,-24.252],[33.789,-14.641],[37.134,-4.922],[43.41,12.489],[44.307,23.718],[31.53,43.997],[0.092,53.129],[-31.449,44.589],[-44.307,24.009],[-44.307,23.977]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0.226,1.656],[-0.13,7.412],[-0.226,-1.656],[0.13,-7.412]],"o":[[-0.227,-1.657],[0.13,-7.412],[0.227,1.657],[-0.13,7.412]],"v":[[-55.466,2.927],[-55.616,-13.258],[-54.921,-22.506],[-54.796,-6.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.55,1.656],[-0.316,7.412],[-0.55,-1.656],[0.316,-7.412]],"o":[[-0.55,-1.657],[0.316,-7.412],[0.55,1.657],[-0.316,7.412]],"v":[[-48.875,2.927],[-49.239,-13.258],[-47.55,-22.506],[-47.247,-6.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[1.401,1.656],[-0.804,7.412],[-1.401,-1.656],[0.804,-7.412]],"o":[[-1.401,-1.657],[0.804,-7.412],[1.401,1.657],[-0.804,7.412]],"v":[[-32.013,2.927],[-32.939,-13.258],[-28.639,-22.506],[-27.867,-6.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[2.26,1.656],[-1.298,7.412],[-2.26,-1.656],[1.298,-7.412]],"o":[[-2.261,-1.657],[1.298,-7.412],[2.261,1.657],[-1.298,7.412]],"v":[[-15.721,2.927],[-17.217,-13.258],[-10.278,-22.506],[-9.031,-6.555]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[3.029,1.656],[-1.74,7.412],[-3.029,-1.656],[1.739,-7.412]],"o":[[-3.031,-1.657],[1.74,-7.412],[3.03,1.657],[-1.739,7.412]],"v":[[-0.652,2.224],[-2.656,-13.961],[6.643,-23.209],[8.315,-7.258]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[3.489,1.656],[-2.004,7.412],[-3.489,-1.656],[2.004,-7.412]],"o":[[-3.491,-1.657],[2.004,-7.412],[3.491,1.657],[-2.004,7.412]],"v":[[7.86,1.52],[5.551,-14.665],[16.264,-23.912],[18.189,-7.962]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[3.825,1.656],[-2.196,7.412],[-3.825,-1.656],[2.196,-7.412]],"o":[[-3.826,-1.657],[2.196,-7.412],[3.826,1.657],[-2.196,7.412]],"v":[[14.017,1.051],[11.486,-15.134],[23.228,-24.381],[25.339,-8.431]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[4.074,1.656],[-2.339,7.412],[-4.074,-1.656],[2.339,-7.412]],"o":[[-4.076,-1.657],[2.339,-7.412],[4.076,1.657],[-2.339,7.412]],"v":[[18.936,-0.122],[16.24,-16.307],[28.747,-25.555],[30.995,-9.604]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[4.275,1.656],[-2.455,7.412],[-4.275,-1.656],[2.455,-7.412]],"o":[[-4.277,-1.657],[2.455,-7.412],[4.277,1.657],[-2.455,7.412]],"v":[[23.29,-0.122],[20.462,-16.307],[33.587,-25.555],[35.946,-9.604]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[4.505,1.656],[-2.587,7.412],[-4.505,-1.656],[2.587,-7.412]],"o":[[-4.507,-1.657],[2.587,-7.412],[4.507,1.657],[-2.587,7.412]],"v":[[28.274,-0.122],[24.798,-16.542],[37.64,-26.963],[41.116,-10.543]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[4.598,1.656],[-2.64,7.412],[-4.598,-1.656],[2.64,-7.412]],"o":[[-4.6,-1.657],[2.64,-7.412],[4.6,1.657],[-2.64,7.412]],"v":[[30.283,-0.122],[26.736,-16.542],[39.843,-26.963],[43.39,-10.543]],"c":true}]},{"t":135,"s":[{"i":[[4.598,1.656],[-2.64,7.412],[-4.598,-1.656],[2.64,-7.412]],"o":[[-4.6,-1.657],[2.64,-7.412],[4.6,1.657],[-2.64,7.412]],"v":[[30.635,-0.09],[27.088,-16.51],[40.195,-26.931],[43.742,-10.511]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0.226,-1.656],[0.13,7.412],[-0.226,1.656],[-0.13,-7.412]],"o":[[-0.227,1.657],[-0.13,-7.412],[0.227,-1.657],[0.13,7.412]],"v":[[-58.448,-0.292],[-59.074,-9.887],[-58.879,-25.49],[-58.273,-15.539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.55,-1.656],[0.316,7.412],[-0.55,1.656],[-0.316,-7.412]],"o":[[-0.55,1.657],[-0.316,-7.412],[0.55,-1.657],[0.316,7.412]],"v":[[-56.118,-0.292],[-57.64,-9.887],[-57.165,-25.49],[-55.694,-15.539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[1.401,-1.656],[0.804,7.412],[-1.401,1.656],[-0.804,-7.412]],"o":[[-1.401,1.657],[-0.804,-7.412],[1.401,-1.657],[0.804,7.412]],"v":[[-50.453,-0.292],[-54.326,-9.887],[-53.117,-25.49],[-49.372,-15.539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[2.26,-1.656],[1.298,7.412],[-2.26,1.656],[-1.298,-7.412]],"o":[[-2.261,1.657],[-1.298,-7.412],[2.261,-1.657],[1.298,7.412]],"v":[[-45.475,-0.292],[-51.726,-9.887],[-49.775,-25.49],[-43.732,-15.539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[3.029,-1.656],[1.739,7.412],[-3.029,1.656],[-1.739,-7.412]],"o":[[-3.03,1.657],[-1.739,-7.412],[3.03,-1.657],[1.739,7.412]],"v":[[-41.476,-0.761],[-49.854,-10.355],[-47.238,-25.959],[-39.139,-16.007]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[3.489,-1.656],[2.004,7.412],[-3.489,1.656],[-2.004,-7.412]],"o":[[-3.491,1.657],[-2.004,-7.412],[3.491,-1.657],[2.004,7.412]],"v":[[-39.166,-0.761],[-48.816,-10.355],[-45.804,-25.959],[-36.474,-16.007]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[3.825,-1.656],[2.196,7.412],[-3.825,1.656],[-2.196,-7.412]],"o":[[-3.826,1.657],[-2.196,-7.412],[3.826,-1.657],[2.196,7.412]],"v":[[-37.786,0.178],[-47.85,-10.355],[-45.32,-25.959],[-34.835,-16.242]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[4.074,-1.656],[2.339,7.412],[-4.074,1.656],[-2.339,-7.412]],"o":[[-4.075,1.657],[-2.339,-7.412],[4.075,-1.657],[2.339,7.412]],"v":[[-36.739,-0.057],[-47.459,-10.59],[-44.763,-26.193],[-33.597,-16.477]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[4.275,-1.656],[2.455,7.412],[-4.275,1.656],[-2.455,-7.412]],"o":[[-4.277,1.657],[-2.455,-7.412],[4.277,-1.657],[2.455,7.412]],"v":[[-35.135,-0.057],[-46.384,-10.59],[-43.555,-26.193],[-31.837,-16.477]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[4.505,-1.656],[2.587,7.412],[-4.505,1.656],[-2.587,-7.412]],"o":[[-4.507,1.657],[-2.587,-7.412],[4.507,-1.657],[2.587,7.412]],"v":[[-33.299,-0.057],[-46.143,-10.478],[-42.667,-26.898],[-29.823,-16.477]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[4.598,-1.656],[2.64,7.412],[-4.598,1.656],[-2.64,-7.412]],"o":[[-4.6,1.657],[-2.64,-7.412],[4.6,-1.657],[2.64,7.412]],"v":[[-32.558,-0.057],[-45.667,-10.478],[-42.12,-26.898],[-29.011,-16.477]],"c":true}]},{"t":135,"s":[{"i":[[4.598,-1.656],[2.64,7.412],[-4.598,1.656],[-2.64,-7.412]],"o":[[-4.6,1.657],[-2.64,-7.412],[4.6,-1.657],[2.64,7.412]],"v":[[-32.206,-0.025],[-45.315,-10.446],[-41.768,-26.866],[-28.659,-16.445]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.678431391716,0.403921574354,0.305882364511,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.996,145.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Head","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[1.662,-1.078],[0.252,-6.5],[0,0],[0,0],[0.006,7.75]],"o":[[-1.809,1.173],[-0.012,11],[0,0],[0,0],[-0.448,-10.219]],"v":[[214.31,197.746],[210.645,231.218],[210.632,265.093],[217.136,244.459],[217.174,217.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[4.038,-1.078],[0.613,-6.5],[0,0],[0,0],[0.015,7.75]],"o":[[-4.394,1.173],[-0.03,11],[0,0],[0,0],[-1.088,-10.219]],"v":[[219.157,197.746],[210.255,231.218],[210.225,265.093],[226.024,244.459],[226.116,217.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[10.281,-1.078],[1.561,-6.5],[0,0],[0,0],[0.038,7.75]],"o":[[-11.186,1.173],[-0.076,11],[0,0],[0,0],[-2.771,-10.219]],"v":[[231.439,197.746],[208.774,231.218],[208.698,265.093],[248.922,244.459],[249.155,217.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[16.589,-1.078],[2.519,-6.5],[0,0],[0,0],[0.061,7.75]],"o":[[-18.049,1.173],[-0.123,11],[0,0],[0,0],[-6.41,-10.783]],"v":[[241.968,198.682],[206.53,231.218],[206.407,265.093],[271.689,247.502],[271.688,217.753]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[22.236,-1.078],[3.376,-6.5],[0,0],[0,0],[0.082,7.75]],"o":[[-24.192,1.173],[-0.165,11],[0,0],[0,0],[-8.592,-10.783]],"v":[[251.88,199.385],[204.38,231.218],[204.215,265.093],[291.799,250.781],[291.744,219.393]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[25.614,-1.078],[3.889,-6.5],[0,0],[0,0],[0.095,7.75]],"o":[[-27.868,1.173],[-0.19,11],[0,0],[0,0],[-9.898,-10.783]],"v":[[257.047,200.557],[202.871,231.218],[202.681,265.093],[303.634,253.83],[303.602,221.269]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[28.076,-1.078],[4.263,-6.5],[0,0],[0,0],[0.104,7.75]],"o":[[-30.546,1.173],[-0.208,11],[0,0],[0,0],[-11.853,-12.5]],"v":[[259.05,201.261],[201.981,231.218],[201.773,265.093],[312.516,256.879],[312.499,222.442]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":107.5,"s":[{"i":[[29.015,0],[4.541,-6.5],[0,0],[0,0],[0.111,7.75]],"o":[[-32.559,0],[-0.221,11],[0,0],[0,0],[-12.625,-12.5]],"v":[[263.029,201.731],[201.994,231.218],[201.773,265.093],[319.85,258.99],[319.818,224.789]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[30.448,0],[4.765,-6.5],[0,0],[0,0],[0.116,7.75]],"o":[[-34.167,0],[-0.232,11],[0,0],[0,0],[-13.249,-12.5]],"v":[[265.316,202.435],[202.005,231.218],[201.773,265.093],[325.671,261.572],[325.765,226.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[32.089,0],[5.021,-6.5],[0,0],[0,0],[0.122,7.75]],"o":[[-36.008,0],[-0.245,11],[0,0],[0,0],[-13.962,-12.5]],"v":[[268.491,203.374],[202.018,231.218],[201.773,265.093],[332.444,263.215],[332.444,228.779]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[32.75,0],[5.125,-6.5],[0,0],[0,0],[0.125,7.75]],"o":[[-36.75,0],[-0.25,11],[0,0],[0,0],[-14.25,-12.5]],"v":[[269.148,203.843],[202.023,231.218],[201.773,265.093],[335.023,265.093],[335.023,229.718]],"c":true}]},{"t":135,"s":[{"i":[[32.75,0],[5.125,-6.5],[0,0],[0,0],[0.125,7.75]],"o":[[-36.75,0],[-0.25,11],[0,0],[0,0],[-14.25,-12.5]],"v":[[269.5,203.875],[202.375,231.25],[202.125,265.125],[335.375,265.125],[335.375,229.75]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.105865478516,0.450958251953,0.901947021484,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Body","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":86,"op":660,"st":60,"bm":0},{"ddd":0,"ind":15,"ty":4,"nm":"FRONT_SCREEN 5","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":93.334,"s":[0]},{"t":101.666015625,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.25,-9.25],[0,0],[0.25,-0.5],[0,0],[0.5,9.25],[0,0],[-2,-2.5],[0,0]],"o":[[0,0],[0.25,3],[0,0],[-2,2.75],[0,0],[0,-16.016],[0,0],[1.75,3.5]],"v":[[-308,-465.5],[-308,477.25],[-308.25,483],[-356.25,568.25],[-364,573.75],[-364,-560.5],[-354.5,-565],[-310.5,-483]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[1,-13],[0,0],[5,-5],[0,0],[0,16.016],[0,0],[-18,-17],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-11.25,9],[0,0],[0,-16.016],[0,0],[5.5,4.5]],"v":[[-257,-464],[-257,468],[-270.5,493.062],[-345.5,568.5],[-358.25,553.75],[-357,-542.5],[-338,-559],[-265.5,-485.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[0.667,-14.005],[0,0],[8.672,-3.333],[0,0],[0,16.016],[0,0],[-17.339,-11.333],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-12.839,6],[0,0],[0,-16.016],[0,0],[9.005,3]],"v":[[-210.667,-462.333],[-210.667,466],[-229.333,495.708],[-334.833,568.667],[-353,549.167],[-352.167,-541.5],[-329.833,-562.167],[-226,-489.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0.333,-15.011],[0,0],[12.344,-1.667],[0,0],[0,16.016],[0,0],[-16.677,-5.667],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-14.427,3],[0,0],[0,-16.016],[0,0],[12.511,1.5]],"v":[[-167.833,-460.667],[-167.833,464],[-191.667,498.354],[-324.167,568.833],[-347.75,544.583],[-347.333,-540.5],[-321.667,-565.333],[-190,-493.833]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-127,-459],[-127,462],[-156,501],[-313.5,569],[-342.5,540],[-342.5,-539.5],[-313.5,-568.5],[-156,-498]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-89.667,-464.667],[-89.667,467.333],[-118.667,503],[-308.667,571.333],[-337.667,542.333],[-337.667,-541.333],[-308.667,-570.333],[-118.667,-500.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-54.833,-470.333],[-54.833,472.667],[-83.833,505],[-303.833,573.667],[-332.833,544.667],[-332.833,-543.167],[-303.833,-572.167],[-83.833,-502.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-24,-476],[-24,478],[-53,507],[-299,576],[-328,547],[-328,-545],[-299,-574],[-53,-505]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[6.073,-479.4],[6.073,481.5],[-22.927,510.5],[-297.12,577.1],[-326.12,548.1],[-326.12,-546.2],[-297.12,-575.2],[-22.927,-508.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[32.647,-482.8],[32.647,485],[3.647,514],[-295.24,578.2],[-324.24,549.2],[-324.24,-547.4],[-295.24,-576.4],[3.647,-511.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[57.22,-486.2],[57.22,488.5],[28.22,517.5],[-293.36,579.3],[-322.36,550.3],[-322.36,-548.6],[-293.36,-577.6],[28.22,-515.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[79.46,-489.6],[79.46,492],[50.46,521],[-291.48,580.4],[-320.48,551.4],[-320.48,-549.8],[-291.48,-578.8],[50.46,-518.6]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[99.7,-493],[99.7,495.5],[70.7,524.5],[-289.6,581.5],[-318.6,552.5],[-318.6,-551],[-289.6,-580],[70.7,-522]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[136.58,-499.8],[136.58,502.5],[107.58,531.5],[-285.84,583.7],[-314.84,554.7],[-314.84,-553.4],[-285.84,-582.4],[107.58,-528.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[179.4,-510],[179.4,513],[150.4,542],[-280.2,587],[-309.2,558],[-309.2,-557],[-280.2,-586],[150.4,-539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[201.28,-516],[201.28,518.8],[172.28,547.8],[-279.44,588.2],[-308.44,559.2],[-308.44,-558],[-279.44,-587],[172.28,-545]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[212.053,-519],[212.053,521.7],[183.053,550.7],[-279.06,588.8],[-308.06,559.8],[-308.06,-558.5],[-279.06,-587.5],[183.053,-548]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[220.827,-522],[220.827,524.6],[191.827,553.6],[-278.68,589.4],[-307.68,560.4],[-307.68,-559],[-278.68,-588],[191.827,-551]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[229.1,-525],[229.1,527.5],[200.1,556.5],[-278.3,590],[-307.3,561],[-307.3,-559.5],[-278.3,-588.5],[200.1,-554]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[256.46,-535.4],[256.46,537.5],[227.46,566.5],[-276.78,592.4],[-305.78,563.4],[-305.78,-561.5],[-276.78,-590.5],[227.46,-564.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[260.8,-538],[260.8,540],[231.8,569],[-276.4,593],[-305.4,564],[-305.4,-562],[-276.4,-591],[231.8,-567]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[273.82,-543.7],[273.82,546],[244.82,575],[-276.16,594.2],[-305.16,565.2],[-305.16,-562.6],[-276.16,-591.6],[244.82,-572.7]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[280.833,-547.5],[280.833,550],[251.833,579],[-276,595],[-305,566],[-305,-563],[-276,-592],[251.833,-576.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[294.867,-555],[294.867,558],[265.867,587],[-275.6,597],[-304.6,568],[-304.6,-564],[-275.6,-593],[265.867,-584]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":122.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[301.9,-559.5],[301.9,563],[272.9,592],[-275.7,597],[-304.7,568],[-304.7,-565],[-275.7,-594],[272.9,-588.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[306.933,-564],[306.933,568],[277.933,597],[-275.8,597],[-304.8,568],[-304.8,-566],[-275.8,-595],[277.933,-593]],"c":true}]},{"t":135,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[310,-568],[310,570],[281,599],[-276,599],[-305,570],[-305,-568],[-276,-597],[281,-597]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.815686285496,0.882352948189,0.980392158031,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[384,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[101.75,101.75],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":87,"op":342,"st":60,"bm":0},{"ddd":0,"ind":16,"ty":4,"nm":"FRONT_SCREEN 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.25,-9.25],[0,0],[0.25,-0.5],[0,0],[0.5,9.25],[0,0],[-2,-2.5],[0,0]],"o":[[0,0],[0.25,3],[0,0],[-2,2.75],[0,0],[0,-16.016],[0,0],[1.75,3.5]],"v":[[-308,-465.5],[-308,477.25],[-308.25,483],[-356.25,568.25],[-364,573.75],[-364,-560.5],[-354.5,-565],[-310.5,-483]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[1,-13],[0,0],[5,-5],[0,0],[0,16.016],[0,0],[-18,-17],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-11.25,9],[0,0],[0,-16.016],[0,0],[5.5,4.5]],"v":[[-257,-464],[-257,468],[-270.5,493.062],[-345.5,568.5],[-358.25,553.75],[-357,-542.5],[-338,-559],[-265.5,-485.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[0.667,-14.005],[0,0],[8.672,-3.333],[0,0],[0,16.016],[0,0],[-17.339,-11.333],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-12.839,6],[0,0],[0,-16.016],[0,0],[9.005,3]],"v":[[-210.667,-462.333],[-210.667,466],[-229.333,495.708],[-334.833,568.667],[-353,549.167],[-352.167,-541.5],[-329.833,-562.167],[-226,-489.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0.333,-15.011],[0,0],[12.344,-1.667],[0,0],[0,16.016],[0,0],[-16.677,-5.667],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-14.427,3],[0,0],[0,-16.016],[0,0],[12.511,1.5]],"v":[[-167.833,-460.667],[-167.833,464],[-191.667,498.354],[-324.167,568.833],[-347.75,544.583],[-347.333,-540.5],[-321.667,-565.333],[-190,-493.833]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-127,-459],[-127,462],[-156,501],[-313.5,569],[-342.5,540],[-342.5,-539.5],[-313.5,-568.5],[-156,-498]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-89.667,-464.667],[-89.667,467.333],[-118.667,503],[-308.667,571.333],[-337.667,542.333],[-337.667,-541.333],[-308.667,-570.333],[-118.667,-500.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-54.833,-470.333],[-54.833,472.667],[-83.833,505],[-303.833,573.667],[-332.833,544.667],[-332.833,-543.167],[-303.833,-572.167],[-83.833,-502.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[-24,-476],[-24,478],[-53,507],[-299,576],[-328,547],[-328,-545],[-299,-574],[-53,-505]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[6.073,-479.4],[6.073,481.5],[-22.927,510.5],[-297.12,577.1],[-326.12,548.1],[-326.12,-546.2],[-297.12,-575.2],[-22.927,-508.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[32.647,-482.8],[32.647,485],[3.647,514],[-295.24,578.2],[-324.24,549.2],[-324.24,-547.4],[-295.24,-576.4],[3.647,-511.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[57.22,-486.2],[57.22,488.5],[28.22,517.5],[-293.36,579.3],[-322.36,550.3],[-322.36,-548.6],[-293.36,-577.6],[28.22,-515.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[79.46,-489.6],[79.46,492],[50.46,521],[-291.48,580.4],[-320.48,551.4],[-320.48,-549.8],[-291.48,-578.8],[50.46,-518.6]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[99.7,-493],[99.7,495.5],[70.7,524.5],[-289.6,581.5],[-318.6,552.5],[-318.6,-551],[-289.6,-580],[70.7,-522]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":99.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[136.58,-499.8],[136.58,502.5],[107.58,531.5],[-285.84,583.7],[-314.84,554.7],[-314.84,-553.4],[-285.84,-582.4],[107.58,-528.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[179.4,-510],[179.4,513],[150.4,542],[-280.2,587],[-309.2,558],[-309.2,-557],[-280.2,-586],[150.4,-539]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[201.28,-516],[201.28,518.8],[172.28,547.8],[-279.44,588.2],[-308.44,559.2],[-308.44,-558],[-279.44,-587],[172.28,-545]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[212.053,-519],[212.053,521.7],[183.053,550.7],[-279.06,588.8],[-308.06,559.8],[-308.06,-558.5],[-279.06,-587.5],[183.053,-548]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[220.827,-522],[220.827,524.6],[191.827,553.6],[-278.68,589.4],[-307.68,560.4],[-307.68,-559],[-278.68,-588],[191.827,-551]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[229.1,-525],[229.1,527.5],[200.1,556.5],[-278.3,590],[-307.3,561],[-307.3,-559.5],[-278.3,-588.5],[200.1,-554]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[256.46,-535.4],[256.46,537.5],[227.46,566.5],[-276.78,592.4],[-305.78,563.4],[-305.78,-561.5],[-276.78,-590.5],[227.46,-564.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[260.8,-538],[260.8,540],[231.8,569],[-276.4,593],[-305.4,564],[-305.4,-562],[-276.4,-591],[231.8,-567]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":112.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[273.82,-543.7],[273.82,546],[244.82,575],[-276.16,594.2],[-305.16,565.2],[-305.16,-562.6],[-276.16,-591.6],[244.82,-572.7]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":114.166,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[280.833,-547.5],[280.833,550],[251.833,579],[-276,595],[-305,566],[-305,-563],[-276,-592],[251.833,-576.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118.334,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[294.867,-555],[294.867,558],[265.867,587],[-275.6,597],[-304.6,568],[-304.6,-564],[-275.6,-593],[265.867,-584]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":122.5,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[301.9,-559.5],[301.9,563],[272.9,592],[-275.7,597],[-304.7,568],[-304.7,-565],[-275.7,-594],[272.9,-588.5]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[306.933,-564],[306.933,568],[277.933,597],[-275.8,597],[-304.8,568],[-304.8,-566],[-275.8,-595],[277.933,-593]],"c":true}]},{"t":135,"s":[{"i":[[0,-16.016],[0,0],[16.016,0],[0,0],[0,16.016],[0,0],[-16.016,0],[0,0]],"o":[[0,0],[0,16.016],[0,0],[-16.016,0],[0,0],[0,-16.016],[0,0],[16.016,0]],"v":[[310,-568],[310,570],[281,599],[-276,599],[-305,570],[-305,-568],[-276,-597],[281,-597]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[384,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[101.75,101.75],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":87,"op":342,"st":60,"bm":0},{"ddd":0,"ind":17,"ty":4,"nm":"CENTER_SCREEN 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[699,-539],[699,539],[653,585],[-0.999,585],[-653,585],[-699,539],[-699,-539],[-653,-585],[1,-585],[653,-585]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":64.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[707,-547],[707,547],[661,593],[-0.999,585],[-639.273,577],[-685.273,531],[-685.273,-527],[-639.273,-573],[1,-585],[661,-593]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":68.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[715,-563],[715,567],[669,613],[-0.999,585],[-617.545,561],[-663.545,515],[-663.545,-511],[-617.545,-557],[1,-585],[669,-609]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[709,-577.4],[709,579],[663,625],[-0.999,585],[-587.709,551.4],[-636.709,505.4],[-636.709,-501.4],[-587.709,-547.4],[1,-585],[663,-623.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":72.5,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[705,-587],[705,587],[659,633],[-0.999,585],[-567.818,545],[-613.818,499],[-613.818,-495],[-567.818,-541],[1,-585],[659,-633]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[697.4,-593.4],[697.4,595],[651.4,641],[-0.999,585],[-553.873,539.4],[-599.873,493.4],[-599.873,-490.2],[-553.873,-536.2],[1,-585],[651.4,-639.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":74.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[693.8,-599.8],[693.8,603],[647.8,649],[-0.999,585],[-537.927,533.8],[-583.927,487.8],[-583.927,-485.4],[-537.927,-531.4],[1,-585],[647.8,-645.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[678.2,-606.2],[678.2,611],[632.2,657],[-0.999,585],[-519.982,528.2],[-565.982,482.2],[-565.982,-480.6],[-519.982,-526.6],[1,-585],[632.2,-652.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[666.6,-612.6],[666.6,619],[620.6,665],[-0.999,585],[-499.036,522.6],[-545.036,476.6],[-545.036,-475.8],[-499.036,-521.8],[1,-585],[620.6,-658.6]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.405,0],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-25.405,0],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[647,-619],[647,627],[601,673],[-0.999,585],[-478.091,517],[-524.091,471],[-524.091,-471],[-478.091,-517],[1,-585],[601,-665]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":78.334,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.223,2.5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-22.441,-1.8],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[600.891,-638.2],[600.891,649.545],[561.618,691.182],[-5.908,586.273],[-428.856,507],[-467.174,466.273],[-467.174,-463],[-420.674,-508.5],[1,-585.6],[554.891,-684.2]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.132,3.75],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-20.96,-2.7],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[561.836,-647.8],[561.836,660.818],[525.927,700.273],[-8.363,586.909],[-398.739,502],[-433.216,463.909],[-433.216,-459],[-386.466,-504.25],[1,-585.9],[515.836,-693.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-25.041,5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-19.478,-3.6],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[522.782,-657.4],[522.782,672.091],[490.236,709.364],[-10.817,587.546],[-368.621,497],[-399.258,461.546],[-399.258,-455],[-352.258,-500],[1,-586.2],[476.782,-703.4]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[0,-25.405],[0,0],[25.405,0],[0,0],[0,0],[0,25.405],[0,0],[-24.95,6.25],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-17.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[25.405,0]],"v":[[471.727,-667],[471.727,683.364],[442.545,718.455],[-13.272,588.182],[-332.504,492],[-359.3,459.182],[-359.3,-451],[-312.05,-495.75],[1,-586.5],[425.727,-713]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[0.129,-24.588],[0,0],[21.663,1.508],[0,0],[0,0],[0.008,23.224],[0,0],[-21.2,5.875],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-15.329,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[21.8,-2.083]],"v":[[412.352,-679.333],[412.352,694.197],[387.295,726.538],[-16.188,588.015],[-289.754,488.25],[-312.675,457.182],[-312.675,-449],[-272.55,-491.375],[-4.583,-587],[372.894,-720.667]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[0.258,-23.77],[0,0],[17.922,3.015],[0,0],[0,0],[0.017,21.043],[0,0],[-17.45,5.5],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-12.663,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[18.194,-4.167]],"v":[[344.977,-691.667],[344.977,705.03],[324.045,734.621],[-19.105,587.849],[-247.004,484.5],[-266.05,455.182],[-266.05,-447],[-233.05,-487],[-10.166,-587.5],[312.06,-728.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[0.386,-22.953],[0,0],[14.18,4.523],[0,0],[0,0],[0.025,18.862],[0,0],[-13.7,5.125],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-9.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[14.589,-6.25]],"v":[[269.602,-704],[269.602,715.864],[252.795,742.705],[-22.022,587.682],[-204.254,480.75],[-219.425,453.182],[-219.425,-445],[-193.55,-482.625],[-15.75,-588],[243.227,-736]],"c":true}]},{"t":85.833984375,"s":[{"i":[[0.773,-20.5],[0,0],[2.955,9.045],[0,0],[0,0],[0.05,12.318],[0,0],[-2.45,4],[0,0],[0,0]],"o":[[0,0],[0,25.405],[0,0],[0,0],[-1.996,-4.5],[0,0],[0,-25.405],[0,0],[0,0],[3.773,-12.5]],"v":[[13.477,-707],[13.477,713.364],[9.045,731.955],[-30.772,587.182],[-58.004,481.5],[-61.55,459.182],[-61.55,-451],[-57.05,-481.5],[-32.5,-581.5],[6.727,-725]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":87,"st":60,"bm":0},{"ddd":0,"ind":18,"ty":4,"nm":"FOLDABLE_BODY 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206.312,149.969,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[19.95,19.95,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,-40.869],[0,0],[40.869,0],[0,0],[0,0],[0,40.869],[0,0],[-40.869,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[-40.869,0],[0,0],[0,-40.869],[0,0],[0,0],[40.869,0]],"v":[[733,-567],[733,567],[659,641],[5,643],[-659,641],[-733,567],[-733,-567],[-659,-641],[3.001,-641],[659,-641]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":65,"s":[{"i":[[0,-40.869],[0,0],[40.869,0],[0,0],[0,0],[-0.091,49.495],[0,0],[-40.869,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[-46.341,0.245],[0,0],[-1.795,-49.895],[0,0],[0,0],[40.869,0]],"v":[[748.332,-581.845],[747.391,584.136],[673.391,658.136],[5,644.125],[-640.409,628.505],[-714.409,554.505],[-714.455,-550.855],[-640.455,-627.105],[3.001,-643.25],[674.332,-655.845]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":69.166,"s":[{"i":[[0,-40.869],[0,0],[40.869,0],[0,0],[0,0],[0,40.869],[0,0],[-40.869,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[-40.869,0],[0,0],[0,-40.869],[0,0],[0,0],[40.869,0]],"v":[[755.15,-598.8],[754.8,603],[680.8,677],[5,643],[-610.25,613.05],[-684.25,539.05],[-685.25,-537.4],[-611.25,-611.4],[3.001,-641],[681.15,-672.8]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":70.834,"s":[{"i":[[0,-40.869],[0,0],[42.179,0.978],[0,0],[0,0],[0,40.869],[0,0],[-43.221,1.138],[0,0],[0,0]],"o":[[0,0],[-0.542,42.899],[0,0],[0,0],[-46.299,-2.698],[0,0],[0,-40.869],[0,0],[0,0],[40.869,0]],"v":[[752.228,-610.767],[752.022,614.022],[675.022,688.422],[5,642.2],[-587.372,604.128],[-663.797,530.128],[-665.175,-528.378],[-588.75,-602.378],[3.001,-641],[678.228,-684.767]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":73.334,"s":[{"i":[[0,-40.869],[0,0],[44.144,2.444],[0,0],[0,0],[0,40.869],[0,0],[-46.75,2.844],[0,0],[0,0]],"o":[[0,0],[-1.356,45.944],[0,0],[0,0],[-54.444,-6.745],[0,0],[0,-40.869],[0,0],[0,0],[40.869,0]],"v":[[747.844,-628.717],[747.856,630.556],[666.356,705.556],[5,641],[-553.056,590.745],[-625.618,516.745],[-627.562,-514.844],[-555,-588.844],[3.001,-641],[673.844,-702.717]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":75,"s":[{"i":[[0,-40.869],[0,0],[42.507,1.222],[0,0],[0,0],[0,40.869],[0,0],[-50.375,3.922],[0,0],[0,0]],"o":[[0,0],[-0.678,43.407],[0,0],[0,0],[-54.222,-7.622],[0,0],[0,-40.869],[0,0],[0,0],[46.435,-1.875]],"v":[[734.172,-645.858],[734.428,648.778],[650.678,723.278],[5,642.75],[-513.528,581.122],[-590.559,507.372],[-591.531,-504.922],[-514.5,-578.922],[3.001,-641.875],[649.172,-720.233]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":76.666,"s":[{"i":[[0,-40.869],[0,0],[40.869,0],[0,0],[0,0],[0,40.869],[0,0],[-54,5],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[-54,-8.5],[0,0],[0,-40.869],[0,0],[0,0],[52,-3.75]],"v":[[708.5,-663],[709,667],[635,741],[5,644.5],[-474,571.5],[-545.5,498],[-545.5,-495],[-474,-569],[3.001,-642.75],[624.5,-737.75]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":79.166,"s":[{"i":[[0.9,-49.498],[0,0],[45.297,5.4],[0,0],[0,0],[0,40.869],[0,0],[-40.05,6.8],[0,0],[0,0]],"o":[[0,0],[0.3,47.208],[0,0],[0,0],[-39.15,-8.5],[0,0],[-0.3,-34.008],[0,0],[0,0],[52.45,-11.925]],"v":[[636.05,-692.05],[634.9,699.675],[545.45,770.9],[-7.675,640.525],[-400.05,552.55],[-455.15,479.65],[-453.6,-480.1],[-404,-549.2],[-14.199,-644.325],[537.55,-767.925]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":80.834,"s":[{"i":[[1.5,-55.25],[0,0],[48.25,9],[0,0],[0,0],[0,40.869],[0,0],[-30.75,8],[0,0],[0,0]],"o":[[0,0],[0.5,51.435],[0,0],[0,0],[-29.25,-8.5],[0,0],[-0.5,-29.435],[0,0],[0,0],[52.75,-17.375]],"v":[[549,-717.25],[549.25,723.125],[453.25,792.5],[-16.125,637.875],[-330.75,540.75],[-370.75,480.75],[-372.75,-481],[-331.5,-538.5],[-21.499,-639.125],[448.75,-787.625]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":81.666,"s":[{"i":[[2.15,-55.283],[0,0],[43.55,12.75],[0,0],[0,0],[-0.25,36.729],[0,0],[-24.85,7.933],[0,0],[0,0]],"o":[[0,0],[1.8,55.206],[0,0],[0,0],[-23.033,-8.15],[0,0],[-0.6,-27.148],[0,0],[0,0],[50.633,-18.367]],"v":[[493.267,-729.3],[493.367,733.5],[388.433,800.9],[-21.017,638.133],[-291.55,534.233],[-324,473.767],[-324.933,-478.317],[-290.983,-532.1],[-25.783,-636.917],[386.317,-797.433]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":82.5,"s":[{"i":[[2.8,-55.317],[0,0],[38.85,16.5],[0,0],[0,0],[-0.5,32.59],[0,0],[-18.95,7.867],[0,0],[0,0]],"o":[[0,0],[3.1,58.978],[0,0],[0,0],[-16.817,-7.8],[0,0],[-0.7,-24.861],[0,0],[0,0],[48.517,-19.358]],"v":[[427.533,-741.35],[427.483,743.875],[323.617,809.3],[-25.908,638.392],[-252.35,527.717],[-277.25,466.783],[-277.117,-475.633],[-250.467,-525.7],[-30.066,-634.708],[323.883,-807.242]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83.334,"s":[{"i":[[3.45,-55.35],[0,0],[34.15,20.25],[0,0],[0,0],[-0.75,28.45],[0,0],[-13.05,7.8],[0,0],[0,0]],"o":[[0,0],[4.4,62.75],[0,0],[0,0],[-10.6,-7.45],[0,0],[-0.8,-22.574],[0,0],[0,0],[46.4,-20.35]],"v":[[351.8,-753.4],[351.6,754.25],[258.8,817.7],[-30.8,638.65],[-213.15,521.2],[-230.5,459.8],[-229.3,-472.95],[-209.95,-519.3],[-34.349,-632.5],[261.45,-817.05]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":84.166,"s":[{"i":[[3.225,-52.425],[0,0],[29.45,24],[0,0],[0,0],[-0.375,34.66],[0,0],[-10.275,9.4],[0,0],[0,0]],"o":[[0,0],[2.7,62.375],[0,0],[0,0],[-7.55,-7.975],[0,0],[-0.9,-20.287],[0,0],[0,0],[45.7,-22.425]],"v":[[269.9,-765.45],[269.55,764.625],[176.9,819.85],[-34.025,634.95],[-163.325,517.6],[-176.25,463.65],[-175.65,-472.975],[-160.475,-516.65],[-39.674,-626.75],[180.475,-821.025]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85,"s":[{"i":[[3,-49.5],[0,0],[24.75,27.75],[0,0],[0,0],[0,40.869],[0,0],[-7.5,11],[0,0],[0,0]],"o":[[0,0],[1,62],[0,0],[0,0],[-4.5,-8.5],[0,0],[-1,-18],[0,0],[0,0],[45,-24.5]],"v":[[182,-777.5],[181.5,775],[95,822],[-37.25,631.25],[-113.5,514],[-122,467.5],[-122,-473],[-111,-514],[-44.999,-621],[99.5,-825]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":85.834,"s":[{"i":[[2.115,-52.053],[0,0],[14.076,24.655],[0,0],[0,0],[0.15,39.565],[0,0],[-4.448,8.548],[0,0],[0,0]],"o":[[0,0],[-2.465,27.536],[0,0],[0,0],[-2.458,-8.114],[0,0],[-0.9,-26.528],[0,0],[0,0],[26.131,-22.782]],"v":[[89.385,-789.28],[89.465,814.464],[14.257,825.512],[-39.14,629.206],[-64.364,505.574],[-68.567,459.152],[-66.85,-455.222],[-64.968,-503.548],[-33.506,-639.074],[16.369,-824.885]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86.666,"s":[{"i":[[0.346,-57.159],[0,0],[-7.272,18.464],[0,0],[0,0],[0.451,36.956],[0,0],[1.656,3.645],[0,0],[0,0]],"o":[[0,0],[0.605,52.109],[0,0],[0,0],[1.626,-7.341],[0,0],[-0.7,-43.583],[0,0],[0,0],[-11.608,-19.345]],"v":[[-77.846,-794.841],[-76.605,801.391],[5.772,824.536],[14.08,628.118],[25.909,512.723],[30.299,466.457],[32.45,-470.667],[16.094,-533.645],[13.482,-630.223],[3.108,-827.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87.5,"s":[{"i":[[0.346,-57.159],[0,0],[-7.272,18.464],[0,0],[0,0],[0.451,36.956],[0,0],[1.656,3.645],[0,0],[0,0]],"o":[[0,0],[0.605,52.109],[0,0],[0,0],[1.626,-7.341],[0,0],[-0.7,-43.583],[0,0],[0,0],[-11.608,-19.345]],"v":[[-161.846,-788.841],[-160.605,797.391],[-78.228,820.536],[14.08,628.118],[75.909,512.723],[80.299,466.457],[82.45,-470.667],[66.094,-533.645],[13.482,-630.223],[-80.892,-821.655]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":88.334,"s":[{"i":[[0.679,-54.879],[0,0],[-13.362,17.618],[0,0],[0,0],[0.601,35.652],[0,0],[2.96,5.097],[0,0],[0,0]],"o":[[0,0],[0.139,54.646],[0,0],[0,0],[3.668,-6.954],[0,0],[-0.709,-37.134],[0,0],[0,0],[-28.144,-20.461]],"v":[[-243.346,-774.288],[-241.973,784.854],[-150.304,818.382],[12.523,630.074],[120.379,515.297],[132.565,465.957],[134.267,-469.042],[115.459,-527.361],[14.143,-629.964],[-152.023,-818.206]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89.166,"s":[{"i":[[1.012,-52.598],[0,0],[-19.453,16.773],[0,0],[0,0],[0.752,34.347],[0,0],[4.265,6.548],[0,0],[0,0]],"o":[[0,0],[-0.326,57.182],[0,0],[0,0],[5.71,-6.568],[0,0],[-0.718,-30.686],[0,0],[0,0],[-44.68,-21.576]],"v":[[-317.846,-759.735],[-316.341,772.318],[-229.381,816.227],[10.967,632.03],[164.848,517.871],[180.831,465.457],[182.083,-467.416],[164.824,-521.076],[14.803,-629.705],[-230.153,-814.758]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[1.346,-50.318],[0,0],[-25.543,15.927],[0,0],[0,0],[0.902,33.043],[0,0],[5.57,8],[0,0],[0,0]],"o":[[0,0],[-0.791,59.718],[0,0],[0,0],[7.752,-6.182],[0,0],[-0.727,-24.237],[0,0],[0,0],[-61.216,-22.691]],"v":[[-385.346,-745.182],[-383.709,759.782],[-294.457,814.073],[9.41,633.986],[209.318,520.445],[226.598,464.957],[227.4,-465.791],[214.189,-514.791],[15.464,-629.445],[-294.284,-811.309]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90.834,"s":[{"i":[[1.418,-49.133],[0,0],[-31.384,13.082],[0,0],[0,0],[0.677,35],[0,0],[7.748,7.5],[0,0],[0,0]],"o":[[0,0],[-0.457,57.887],[0,0],[0,0],[9.794,-5.795],[0,0],[-0.682,-25.276],[0,0],[0,0],[-56.377,-19.098]],"v":[[-443.237,-737.295],[-442.077,748.496],[-351.866,806.918],[10.729,633.609],[244.163,523.811],[267.781,467.033],[268.3,-467.923],[248.074,-518.673],[13.874,-629.811],[-352.623,-804.027]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":91.666,"s":[{"i":[[1.491,-47.948],[0,0],[-37.224,10.236],[0,0],[0,0],[0.451,36.956],[0,0],[9.927,7],[0,0],[0,0]],"o":[[0,0],[-0.123,56.057],[0,0],[0,0],[11.836,-5.409],[0,0],[-0.636,-26.316],[0,0],[0,0],[-51.538,-15.505]],"v":[[-496.127,-729.409],[-495.445,737.209],[-404.276,799.764],[12.047,633.232],[279.007,527.177],[306.964,469.109],[307.2,-470.055],[281.96,-522.555],[12.285,-630.177],[-405.962,-796.745]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":92.5,"s":[{"i":[[1.564,-46.762],[0,0],[-43.065,7.391],[0,0],[0,0],[0.226,38.913],[0,0],[12.105,6.5],[0,0],[0,0]],"o":[[0,0],[0.211,54.226],[0,0],[0,0],[13.878,-5.023],[0,0],[-0.591,-27.356],[0,0],[0,0],[-46.699,-11.911]],"v":[[-539.018,-721.523],[-538.814,725.923],[-446.685,792.609],[13.365,632.855],[313.852,530.543],[342.647,471.185],[342.6,-472.186],[315.845,-526.436],[10.695,-630.543],[-449.301,-789.464]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":93.334,"s":[{"i":[[1.636,-45.577],[0,0],[-48.906,4.545],[0,0],[0,0],[0,40.869],[0,0],[14.283,6],[0,0],[0,0]],"o":[[0,0],[0.545,52.395],[0,0],[0,0],[15.919,-4.636],[0,0],[-0.545,-28.395],[0,0],[0,0],[-41.86,-8.318]],"v":[[-576.909,-713.636],[-577.182,714.636],[-484.094,785.455],[14.684,632.477],[348.697,533.909],[377.33,473.261],[377,-474.318],[349.731,-530.318],[9.106,-630.909],[-487.64,-782.182]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":94.166,"s":[{"i":[[1.5,-45.185],[0,0],[-48.199,4.167],[0,0],[0,0],[0,40.869],[0,0],[16.461,5.5],[0,0],[0,0]],"o":[[0,0],[0.5,51.435],[0,0],[0,0],[17.961,-4.25],[0,0],[-0.5,-29.435],[0,0],[0,0],[-41.74,-7.625]],"v":[[-609.5,-705.25],[-609.25,706.583],[-516.07,777.667],[13.048,633.104],[372.373,539.146],[405.552,476.99],[405.333,-478.125],[373.571,-535.625],[8.099,-632],[-520.404,-774.333]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95.834,"s":[{"i":[[1.227,-44.4],[0,0],[-46.785,3.409],[0,0],[0,0],[0,40.869],[0,0],[20.818,4.5],[0,0],[0,0]],"o":[[0,0],[0.409,49.514],[0,0],[0,0],[22.045,-3.477],[0,0],[-0.409,-31.514],[0,0],[0,0],[-41.501,-6.239]],"v":[[-659.682,-688.477],[-658.386,690.477],[-565.023,762.091],[9.776,634.358],[419.724,549.619],[461.997,484.446],[462,-485.739],[421.25,-546.239],[6.087,-634.182],[-570.932,-758.636]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":97.5,"s":[{"i":[[0.955,-43.615],[0,0],[-45.371,2.652],[0,0],[0,0],[0,40.869],[0,0],[25.175,3.5],[0,0],[0,0]],"o":[[0,0],[0.318,47.593],[0,0],[0,0],[26.129,-2.705],[0,0],[-0.318,-33.592],[0,0],[0,0],[-41.261,-4.852]],"v":[[-693.197,-672.705],[-692.189,674.705],[-601.975,746.515],[6.505,635.612],[457.076,558.093],[507.109,489.902],[506.667,-490.686],[457.596,-554.186],[4.075,-633.03],[-608.126,-744.273]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98.334,"s":[{"i":[[0.818,-43.223],[0,0],[-44.664,2.273],[0,0],[0,0],[0,40.869],[0,0],[27.353,3],[0,0],[0,0]],"o":[[0,0],[0.273,46.632],[0,0],[0,0],[28.171,-2.318],[0,0],[-0.273,-34.632],[0,0],[0,0],[-41.142,-4.159]],"v":[[-705.455,-664.818],[-704.591,666.818],[-615.951,738.727],[4.869,636.239],[475.752,562.33],[526.665,492.631],[526,-493.159],[475.769,-558.159],[3.069,-632.455],[-622.224,-737.091]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0.545,-42.438],[0,0],[-43.251,1.515],[0,0],[0,0],[0,40.869],[0,0],[31.71,2],[0,0],[0,0]],"o":[[0,0],[0.182,44.711],[0,0],[0,0],[32.255,-1.545],[0,0],[-0.182,-36.711],[0,0],[0,0],[-40.902,-2.773]],"v":[[-723.303,-651.879],[-722.727,654.212],[-635.57,726.485],[1.598,637.492],[498.104,569.803],[562.443,499.754],[561.667,-498.773],[497.782,-566.773],[1.056,-632.97],[-643.418,-724.727]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100.834,"s":[{"i":[[0.409,-42.046],[0,0],[-42.544,1.136],[0,0],[0,0],[0,40.869],[0,0],[33.888,1.5],[0,0],[0,0]],"o":[[0,0],[0.136,43.75],[0,0],[0,0],[34.297,-1.159],[0,0],[-0.136,-37.751],[0,0],[0,0],[-40.782,-2.08]],"v":[[-731.727,-645.409],[-731.295,647.909],[-644.879,720.364],[-0.038,638.119],[509.28,573.54],[577.332,503.315],[576.5,-501.58],[508.788,-571.08],[0.05,-633.227],[-653.516,-718.545]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":101.666,"s":[{"i":[[0.273,-41.654],[0,0],[-41.837,0.758],[0,0],[0,0],[0,40.869],[0,0],[36.066,1],[0,0],[0,0]],"o":[[0,0],[0.091,42.79],[0,0],[0,0],[36.339,-0.773],[0,0],[-0.091,-38.79],[0,0],[0,0],[-40.663,-1.386]],"v":[[-737.152,-638.939],[-736.864,641.606],[-651.189,714.242],[-1.674,638.746],[520.456,577.277],[590.222,506.877],[589.333,-504.386],[519.795,-575.386],[-0.956,-633.485],[-660.613,-712.364]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":103.334,"s":[{"i":[[0,-40.869],[0,0],[-40.423,0],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[40.423,0],[0,0],[0,-40.869],[0,0],[0,0],[-40.423,0]],"v":[[-744,-626],[-744,629],[-659.808,702],[-4.946,640],[542.808,584.75],[616,514],[615,-510],[541.808,-584],[-2.968,-634],[-670.808,-700]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":105.834,"s":[{"i":[[0,-40.869],[0,0],[-40.423,0],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[40.423,0],[0,0],[0,-40.869],[0,0],[0,0],[-40.423,0]],"v":[[-744.842,-614.959],[-744.6,619.497],[-661.426,691.301],[-2.779,639.354],[564.646,592.557],[643.41,522.301],[642.956,-517.867],[564.192,-591.867],[-2.968,-634.219],[-671.649,-688.959]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":109.166,"s":[{"i":[[0,-40.869],[0,0],[-40.423,0],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[40.423,0],[0,0],[0,-40.869],[0,0],[0,0],[-40.423,0]],"v":[[-745.964,-600.237],[-745.4,606.826],[-663.584,677.035],[0.11,638.493],[593.764,602.967],[672.957,533.369],[673.23,-528.356],[594.037,-602.356],[-2.968,-634.512],[-672.771,-674.237]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":111.666,"s":[{"i":[[0,-40.869],[0,0],[-40.423,0],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[40.423,0],[0,0],[0,-40.869],[0,0],[0,0],[-40.423,0]],"v":[[-746.806,-589.196],[-746,597.323],[-665.202,666.336],[2.276,637.847],[615.603,610.775],[688.795,541.67],[689.614,-536.222],[616.422,-610.222],[-2.968,-634.731],[-673.613,-663.196]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":118,"s":[{"i":[[0,-40.869],[0,0],[-46.482,1.895],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[-1,44.868],[0,0],[0,0],[40.423,0],[0,0],[-0.105,-46.5],[0,0],[0,0],[-40.423,0]],"v":[[-736,-575.053],[-735,579.632],[-656.018,651.105],[8.054,637.474],[642.439,625.645],[714.632,557.105],[715.105,-550],[641.913,-624],[-2.968,-635.316],[-662.808,-649.053]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":126,"s":[{"i":[[0,-40.869],[0,0],[-44.048,1],[0,0],[0,0],[3,43.078],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[43.733,1],[0,0],[1.328,-42.15],[0,0],[0,0],[-40.423,0]],"v":[[-725.575,-563.749],[-725.75,566.434],[-649.952,640.197],[0.904,637.851],[657.767,633.903],[730.959,560.672],[730.922,-559.6],[657.73,-633.6],[-2.968,-638.442],[-656.133,-637.749]],"c":true}]},{"t":135,"s":[{"i":[[0,-40.869],[0,0],[-40.423,0],[0,0],[0,0],[0,40.869],[0,0],[40.423,0],[0,0],[0,0]],"o":[[0,0],[0,40.869],[0,0],[0,0],[40.423,0],[0,0],[0,-40.869],[0,0],[0,0],[-40.423,0]],"v":[[-725,-571],[-725.945,569],[-652.753,640],[-5.891,640],[660.862,640],[734.055,569],[735,-571],[661.808,-639],[-2.968,-639],[-651.808,-639]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.372549027205,0.388235300779,0.407843142748,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-3,-1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":386,"st":60,"bm":10}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 5e82f6d..15423af 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Stembystand"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-kodeskandeerder"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Ontsluit"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Toestel is gesluit"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Skandeer tans gesig"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Stuur"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Kan nie gesig herken nie"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gebruik eerder vingerafdruk"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth gekoppel."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batterypersentasie is onbekend."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Vliegtuigmodus."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN aan."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> persent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$s</xliff:g> persent, ongeveer <xliff:g id="TIME">%2$s</xliff:g> oor gegrond op jou gebruik"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Battery laai tans, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Sien alle kennisgewings"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter geaktiveer."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Luitoestel-vibreer."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Wanneer jy ’n program deel, opneem of uitsaai, het <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot enigiets wat in daardie program sigbaar is of daarin gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Gaan voort"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deel of neem ’n program op"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Laat hierdie app toe om te deel of op te neem?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Wanneer jy deel, opneem of uitsaai, het hierdie app toegang tot enigiets wat op jou skerm sigbaar is of op jou toestel gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Wanneer jy ’n app deel, opneem of uitsaai, het hierdie app toegang tot enigiets wat in daardie program sigbaar is of daarin gespeel word. Wees dus versigtig met wagwoorde, betalingbesonderhede, boodskappe of ander sensitiewe inligting."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Deur jou IT-admin geblokkeer"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skermskote is deur toestelbeleid gedeaktiveer"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vee alles uit"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Bestuur"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Geskiedenis"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ontsluit om te gebruik"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Kon nie jou kaarte kry nie; probeer later weer"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Sluitskerminstellings"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skandeer QR-kode"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Werkprofiel"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Vliegtuigmodus"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Jy sal nie jou volgende wekker <xliff:g id="WHEN">%1$s</xliff:g> hoor nie"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Skakel mobiele data af?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Jy sal nie deur <xliff:g id="CARRIER">%s</xliff:g> toegang tot data of die internet hê nie. Internet sal net deur Wi-Fi beskikbaar wees."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"jou diensverskaffer"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Skakel weer oor na <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiele data sal nie outomaties op grond van beskikbaarheid oorskakel nie"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nee, dankie"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, skakel oor"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Instellings kan nie jou antwoord verifieer nie omdat \'n program \'n toestemmingversoek verberg."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Laat <xliff:g id="APP_0">%1$s</xliff:g> toe om <xliff:g id="APP_2">%2$s</xliff:g>-skyfies te wys?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Dit kan inligting van <xliff:g id="APP">%1$s</xliff:g> af lees"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiele data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Gekoppel"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tydelik gekoppel"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Swak verbinding"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiele data sal nie outomaties koppel nie"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Geen verbinding nie"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Geen ander netwerke beskikbaar nie"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index c0a23d9..6c9b07f 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"የድምጽ እርዳታ"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"የQR ኮድ መቃኛ"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"ክፈት"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"መሣሪያ ተቆልፏል"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"የቅኝት ፊት"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ላክ"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"መልክን መለየት አልተቻለም"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"በምትኩ የጣት አሻራን ይጠቀሙ"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ብሉቱዝ ተያይዟል።"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"የባትሪ መቶኛ አይታወቅም።"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"የአውሮፕላን ሁነታ።"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"ቪፒኤን በርቷል።"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"የባትሪ <xliff:g id="NUMBER">%d</xliff:g> መቶኛ።"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ባትሪ <xliff:g id="PERCENTAGE">%1$s</xliff:g> በመቶ፣ በአጠቃቀምዎ ላይ በመመስረት <xliff:g id="TIME">%2$s</xliff:g> ገደማ ይቀራል"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ባትሪ ኃይል በመሙላት ላይ፣ <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"ሁሉንም ማሳወቂያዎች ይመልከቱ"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter ነቅቷል።"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"የስልክ ጥሪ ይንዘር።"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"አንድን መተግበሪያ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ቀጥል"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"መተግበሪያ ያጋሩ ወይም ይቅረጹ"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ይህ መተግበሪያ እንዲያጋራ ወይም እንዲቀርጽ ይፈቀድለት?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ ይህ መተግበሪያ በማያ ገጽዎ ላይ ለሚታይ ወይም በመሣሪያዎ ላይ ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"አንድን መተግበሪያ ሲያጋሩ፣ ሲቀርጹ ወይም cast ሲያደርጉ ይህ መተግበሪያ በዚያ መተግበሪያ ላይ ለሚታይ ወይም ለሚጫወት ማንኛውም ነገር መዳረሻ አለው። ስለዚህ በይለፍ ቃላት፣ በክፍያ ዝርዝሮች፣ በመልዕክቶች ወይም በሌሎች ልዩ ጥንቃቄ የሚያስፈልጋቸው መረጃዎች ላይ ጥንቃቄ ያድርጉ።"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"በእርስዎ የአይቲ አስተዳዳሪ ታግዷል"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"የማያ ገጽ ቀረጻ በመሣሪያ መመሪያ ተሰናክሏል"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ሁሉንም አጽዳ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ያቀናብሩ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ታሪክ"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ለማየት ይክፈቱ"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"የእርስዎን ካርዶች ማግኘት ላይ ችግር ነበር፣ እባክዎ ቆይተው እንደገና ይሞክሩ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"የገጽ መቆለፊያ ቅንብሮች"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR ኮድ ቃኝ"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"የስራ መገለጫ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"የአውሮፕላን ሁነታ"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"የእርስዎን ቀጣይ ማንቂያ <xliff:g id="WHEN">%1$s</xliff:g> አይሰሙም"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"የተንቀሳቃሽ ስልክ ውሂብ ይጥፋ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"በ<xliff:g id="CARRIER">%s</xliff:g> በኩል የውሂብ ወይም የበይነመረቡ መዳረሻ አይኖረዎትም። በይነመረብ በWi-Fi በኩል ብቻ ነው የሚገኝ የሚሆነው።"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"የእርስዎ አገልግሎት አቅራቢ"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ወደ <xliff:g id="CARRIER">%s</xliff:g> ተመልሶ ይቀየር?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"የተንቀሳቃሽ ስልክ ውሂብ በተገኝነት መሰረት በራስ ሰር አይቀይርም"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"አይ አመሰግናለሁ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"አዎ፣ ቀይር"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"አንድ መተግበሪያ የፍቃድ ጥያቄ እያገደ ስለሆነ ቅንብሮች ጥያቄዎን ማረጋገጥ አይችሉም።"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> የ<xliff:g id="APP_2">%2$s</xliff:g> ቁራጮችን እንዲያሳይ ይፈቀድለት?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ከ<xliff:g id="APP">%1$s</xliff:g> የመጣ መረጃን ማንበብ ይችላል"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"የተንቀሳቃሽ ስልክ ውሂብ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ተገናኝቷል"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"በጊዜያዊነት ተገናኝቷል"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ደካማ ግንኙነት"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"የተንቀሳቃሽ ስልክ ውሂብ በራስ-ሰር አይገናኝም"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ግንኙነት የለም"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ሌላ አውታረ መረብ የሉም"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 7f1b3c9..384087b 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"المساعد الصوتي"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"محفظة"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"الماسح الضوئي لرمز الاستجابة السريعة"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"فتح القفل"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"الجهاز مُقفل."</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"مسح الوجه"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"إرسال"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"يتعذّر التعرّف على الوجه."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"يمكنك استخدام بصمة إصبعك."</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"تم توصيل البلوتوث."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"نسبة شحن البطارية غير معروفة."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"وضع الطيران."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"الشبكة الافتراضية الخاصة (VPN) قيد التفعيل."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"مستوى البطارية <xliff:g id="NUMBER">%d</xliff:g> في المائة."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"نسبة الشحن بالبطارية <xliff:g id="PERCENTAGE">%1$s</xliff:g> بالمائة، ويتبقى <xliff:g id="TIME">%2$s</xliff:g> تقريبًا بناءً على استخدامك."</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"جارٍ شحن البطارية، <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"الاطّلاع على جميع الإشعارات"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"تم تفعيل المبرقة الكاتبة."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"رنين مع الاهتزاز."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"أثناء مشاركة محتوى تطبيق أو تسجيله أو بثه، يمكن لتطبيق <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"متابعة"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"مشاركة محتوى تطبيق أو تسجيله"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"هل تريد السماح لهذا التطبيق بمشاركة المحتوى أو تسجيله؟"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"أثناء مشاركة المحتوى أو تسجيله أو بثه، يمكن لهذا التطبيق الوصول إلى كل العناصر المرئية على شاشتك أو التي يتم تشغيلها على جهازك، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"أثناء مشاركة محتوى تطبيق أو تسجيله أو بثه، يمكن لهذا التطبيق الوصول إلى كل العناصر المعروضة أو التي يتم تشغيلها في ذلك التطبيق، لذا يُرجى توخي الحذر بشأن كلمات المرور أو تفاصيل الدفع أو الرسائل أو المعلومات الحساسة الأخرى."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"حظر مشرف تكنولوجيا المعلومات هذه الميزة"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ميزة \"تصوير الشاشة\" غير مفعَّلة بسبب سياسة الجهاز."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"محو الكل"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"إدارة"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"السجلّ"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"فتح القفل للاستخدام"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"حدثت مشكلة أثناء الحصول على البطاقات، يُرجى إعادة المحاولة لاحقًا."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"إعدادات شاشة القفل"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"مسح رمز الاستجابة السريعة ضوئيًا"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"الملف الشخصي للعمل"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"وضع الطيران"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"لن تسمع المنبّه القادم في <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"هل تريد إيقاف بيانات الجوّال؟"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"لن تتمكّن من استخدام البيانات أو الإنترنت من خلال <xliff:g id="CARRIER">%s</xliff:g>. ولن يتوفر اتصال الإنترنت إلا عبر Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"مشغّل شبكة الجوّال"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"هل تريد التبديل مرة أخرى إلى \"<xliff:g id="CARRIER">%s</xliff:g>\"؟"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"لن يتم تلقائيًا تبديل بيانات الجوّال بناءً على التوفّر."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"لا، شكرًا"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"نعم، أريد التبديل"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"لا يمكن للإعدادات التحقق من ردك لأن هناك تطبيقًا يحجب طلب الإذن."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"هل تريد السماح لتطبيق <xliff:g id="APP_0">%1$s</xliff:g> بعرض شرائح <xliff:g id="APP_2">%2$s</xliff:g>؟"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- يستطيع قراءة المعلومات من <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"بيانات الجوّال"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"متصلة بالإنترنت"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"متصلة مؤقتًا"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"الاتصال ضعيف"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"لن يتم تلقائيًا الاتصال ببيانات الجوّال."</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"لا يتوفّر اتصال بالإنترنت"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"لا تتوفّر شبكات أخرى."</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 5dfe17d..411419c 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"কণ্ঠধ্বনিৰে সহায়"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"ৱালেট"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"কিউআৰ ক’ড স্কেনাৰ"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"আনলক কৰক"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"ডিভাইচটো লক হৈ আছে"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"চেহেৰা স্কেন কৰি থকা হৈছে"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"পঠিয়াওক"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"মুখাৱয়ব চিনিব নোৱাৰি"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ইয়াৰ সলনি ফিংগাৰপ্ৰিণ্ট ব্যৱহাৰ কৰক"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযোগ হ’ল।"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"বেটাৰীৰ চাৰ্জৰ শতাংশ অজ্ঞাত।"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"এয়াৰপ্লে’ন ম’ড।"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"ভিপিএন অন অৱস্থাত আছে।"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> শতাংশ বেটাৰী।"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"আপোনাৰ ব্যৱহাৰৰ ওপৰত ভিত্তি কৰি বেটাৰী <xliff:g id="PERCENTAGE">%1$s</xliff:g> শতাংশ, প্ৰায় <xliff:g id="TIME">%2$s</xliff:g> বাকী আছে"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"বেটাৰী চাৰ্জ হৈ আছে, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> শতাংশ।"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"আটাইবোৰ জাননী চাওক"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter সক্ষম কৰা হ\'ল৷"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ৰিংগাৰ কম্পন অৱস্থাত আছে৷"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>ৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"অব্যাহত ৰাখক"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"এটা এপ্ শ্বেয়াৰ অথবা ৰেকৰ্ড কৰক"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"এই এপ্টোক শ্বেয়াৰ অথবা ৰেকৰ্ড কৰিবলৈ অনুমতি দিবনে?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, আপোনাৰ স্ক্ৰীনখনত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ এই এপ্টোৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"আপুনি শ্বেয়াৰ কৰা, ৰেকৰ্ড কৰা অথবা কাষ্ট কৰাৰ সময়ত, সেইটো এপত দৃশ্যমান যিকোনো বস্তু অথবা আপোনাৰ ডিভাইচত প্লে’ কৰা যিকোনো সমললৈ এই এপ্টোৰ এক্সেছ থাকে। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা অথবা অন্য সংবেদনশীল তথ্যৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপোনাৰ আইটি প্ৰশাসকে অৱৰোধ কৰিছে"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইচ সম্পৰ্কীয় নীতিয়ে স্ক্ৰীন কেপশ্বাৰ কৰাটো অক্ষম কৰিছে"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ব্যৱহাৰ কৰিবলৈ আনলক কৰক"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"আপোনাৰ কাৰ্ড লাভ কৰোঁতে এটা সমস্যা হৈছে, অনুগ্ৰহ কৰি পাছত পুনৰ চেষ্টা কৰক"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"লক স্ক্ৰীনৰ ছেটিং"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"কিউআৰ ক’ড স্কেন কৰক"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"কৰ্মস্থানৰ প্ৰ\'ফাইল"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"এয়াৰপ্লে’ন ম’ড"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"আপুনি আপোনাৰ পিছৰটো এলাৰ্ম <xliff:g id="WHEN">%1$s</xliff:g> বজাত শুনা নাপাব"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ম’বাইল ডেটা অফ কৰিবনে?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"আপুনি <xliff:g id="CARRIER">%s</xliff:g>ৰ জৰিয়তে ডেটা সংযোগ বা ইণ্টাৰনেট সংযোগ নাপাব। কেৱল ৱাই-ফাইৰ যোগেৰে ইণ্টাৰনেট উপলব্ধ হ\'ব।"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"আপোনাৰ বাহক"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"আকৌ <xliff:g id="CARRIER">%s</xliff:g>লৈ সলনি কৰিবনে?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ম’বাইলৰ ডেটা উপলব্ধতাৰ ওপৰত ভিত্তি কৰি স্বয়ংক্ৰিয়ভাৱে সলনি কৰা নহ’ব"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"নালাগে, ধন্যবাদ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"হয়, সলনি কৰক"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"এটা এপে অনুমতি বিচাৰি কৰা অনুৰোধ এটা ঢাকি ধৰা বাবে ছেটিঙৰ পৰা আপোনাৰ উত্তৰ সত্যাপন কৰিব পৰা নাই।"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>ক <xliff:g id="APP_2">%2$s</xliff:g>ৰ অংশ দেখুওৱাবলৈ অনুমতি দিবনে?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ই <xliff:g id="APP">%1$s</xliff:g>ৰ তথ্য পঢ়িব পাৰে"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ম’বাইল ডেটা"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"সংযোজিত হৈ আছে"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"অস্থায়ীভাৱে সংযোগ কৰা হৈছে"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"বেয়া সংযোগ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ম’বাইল ডেটা স্বয়ংক্ৰিয়ভাৱে সংযুক্ত নহ’ব"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"সংযোগ নাই"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"অন্য কোনো নেটৱৰ্ক উপলব্ধ নহয়"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index feab7f0..f3e281f 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Səs Yardımçısı"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Pulqabı"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR Kodu Skaneri"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Kiliddən çıxarın"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Cihaz kilidlənib"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Üzün skan edilməsi"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Göndərin"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Üzü tanımaq olmur"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Barmaq izi istifadə edin"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth qoşulub."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batareyanın faizi naməlumdur."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Uçuş rejimi"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN aktivdir."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Batareya <xliff:g id="NUMBER">%d</xliff:g> faizdir."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batareya <xliff:g id="PERCENTAGE">%1$s</xliff:g> faizdir, istifadəyə əsasən təxminən <xliff:g id="TIME">%2$s</xliff:g> qalıb"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batareya doldurulur, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%% faiz."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Bütün bildirişlərə baxın"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter aktivləşdirilib."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Zəng vibrasiyası"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Paylaşdığınız, qeydə aldığınız və ya yayımladığınız zaman <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tətbiqi həmin tətbiqdə göstərilən və ya oxudulan hər şeyə giriş edə bilir. Odur ki, parollar, ödəniş detalları, mesajlar və ya digər həssas məlumatlarla bağlı diqqətli olun."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Davam edin"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Tətbiqi paylaşın və ya qeydə alın"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Bu tətbiqə paylaşmağa və ya yazmağa icazə verilsin?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Paylaşdığınız, yazdığınız və ya yayımladığınız zaman bu tətbiq ekranınızda görünən və ya cihazınızda oxudulan hər şeyə giriş edə bilir. Odur ki, parollar, ödəniş detalları, mesajlar və ya digər həssas məlumatlarla bağlı diqqətli olun."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Paylaşdığınız, qeydə aldığınız və ya yayımladığınız zaman bu tətbiq həmin tətbiqdə göstərilən və ya oxudulan hər şeyə giriş edə bilir. Odur ki, parollar, ödəniş detalları, mesajlar və ya digər həssas məlumatlarla bağlı diqqətli olun."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"İT admininiz tərəfindən bloklanıb"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran çəkimi cihaz siyasəti ilə deaktiv edilib"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hamısını silin"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"İdarə edin"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Tarixçə"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"İstifadə etmək üçün kiliddən çıxarın"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Kartların əldə edilməsində problem oldu, sonra yenidən cəhd edin"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Kilid ekranı ayarları"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR kodu skanlayın"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Təyyarə rejimi"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> zaman növbəti xəbərdarlığınızı eşitməyəcəksiniz"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil data söndürülsün?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> ilə data və ya internetə daxil ola bilməyəcəksiniz. İnternet yalnız Wi-Fi ilə əlçatan olacaq."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorunuz"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> operatoruna keçirilsin?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobil data əlçatımlıq əsasında avtomatik olaraq keçirilməyəcək"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Xeyr, təşəkkürlər"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Bəli, keçirin"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Tətbiq icazə sorğusunu gizlətdiyi üçün Ayarlar cavabınızı doğrulaya bilməz."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> tətbiqinə <xliff:g id="APP_2">%2$s</xliff:g> hissələrini göstərmək üçün icazə verilsin?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> tətbiqindən məlumat oxuya bilər"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Qoşulub"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Müvəqqəti qoşulub"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Zəif bağlantı"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil data avtomatik qoşulmayacaq"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Bağlantı yoxdur"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Heç bir başqa şəbəkə ə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 efed08f..0fc6331 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Glasovna pomoć"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Novčanik"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Skener QR koda"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Otključajte"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Uređaj je zaključan"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Skeniranje lica"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Pošalji"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Lice nije prepoznato"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Koristite otisak prsta"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je priključen."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procenat napunjenosti baterije nije poznat."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Režim rada u avionu."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN je uključen."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Baterija je na <xliff:g id="NUMBER">%d</xliff:g> posto."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterija je na <xliff:g id="PERCENTAGE">%1$s</xliff:g> posto, preostalo vreme na osnovu korišćenja je <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Baterija se puni, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> posto."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Pogledajte sva obaveštenja"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter je omogućen."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibracija zvona."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kada delite, snimate ili prebacujete aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Budite pažljivi sa lozinkama, informacijama o plaćanju, porukama ili drugim osetljivim informacijama."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Delite ili snimite aplikaciju"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Želite da dozvolite ovoj aplikaciji da deli ili snima?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kada delite, snimate ili prebacujete, ova aplikacija ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Budite pažljivi sa lozinkama, informacijama o plaćanju, porukama ili drugim osetljivim informacijama."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kada delite, snimate ili prebacujete aplikaciju, ova aplikacija ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Budite pažljivi sa lozinkama, informacijama o plaćanju, porukama ili drugim osetljivim informacijama."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokira IT administrator"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno smernicama za uređaj"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključaj radi korišćenja"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Došlo je do problema pri preuzimanju kartica. Probajte ponovo kasnije"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Podešavanja zaključanog ekrana"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skenirajte QR kôd"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Poslovni profil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Režim rada u avionu"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sledeći alarm u <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Želite da isključite mobilne podatke?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup podacima ili internetu preko mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo preko WiFi veze."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"mobilni operater"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Želite da se vratite na mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilni podaci se neće automatski promeniti na osnovu dostupnosti"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, pređi"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Podešavanja ne mogu da verifikuju vaš odgovor jer aplikacija skriva zahtev za dozvolu."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Želite li da dozvolite aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Može da čita podatke iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilni podaci"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Privremeno povezano"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Veza je loša"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Nije uspelo autom. povezivanje preko mob. podataka"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Veza nije uspostavljena"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nije dostupna nijedna druga mreža"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index cc37204..85458bf 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Галасавая дапамога"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Кашалёк"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Сканер QR-кодаў"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Разблакiраваць"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Прылада заблакіравана"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Сканіраванне твару"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Адправіць"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Твар не распазнаны"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Скарыстайце адбітак пальца"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-сувязь."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Працэнт зараду акумулятара невядомы."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Рэжым палёту."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN уключана."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Працэнт зараду акумулятара: <xliff:g id="NUMBER">%d</xliff:g>."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Зарад акумулятара ў працэнтах: <xliff:g id="PERCENTAGE">%1$s</xliff:g>. Пры такім выкарыстанні яго хопіць прыблізна на <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Акумулятар зараджаецца. Бягучы зарад: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Паказаць усе апавяшчэнні"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter уключаны."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Выклік з вібрацыяй."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Калі пачынаецца абагульванне, запіс ці трансляцыя змесціва праграмы, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> атрымлівае доступ да ўсяго змесціва, якое паказваецца ці прайграецца ў праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў і іншай канфідэнцыяльнай інфармацыі."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Далей"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Абагульванне або запіс праграмы"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Дазволіць гэтай праграме абагульваць або запісваць змесціва?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Калі пачынаецца абагульванне, запіс ці трансляцыя, гэта праграма атрымлівае доступ да ўсяго змесціва, якое паказваецца на экране ці прайграецца на прыладзе. Таму прадухіліце паказ пароляў, звестак пра плацяжы, паведамленняў і іншай канфідэнцыяльнай інфармацыі."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Калі пачынаецца абагульванне, запіс ці трансляцыя змесціва праграмы, гэта праграма атрымлівае доступ да ўсяго змесціва, якое ў ёй паказваецца ці прайграецца. Таму прадухіліце паказ пароляў, звестак пра плацяжы, паведамленняў і іншай канфідэнцыяльнай інфармацыі."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблакіравана вашым ІТ-адміністратарам"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Здыманне экрана адключана згодна з палітыкай прылады"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ачысціць усё"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Кіраваць"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Гісторыя"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Разблакіраваць для выкарыстання"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Узнікла праблема з загрузкай вашых карт. Паўтарыце спробу пазней"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Налады экрана блакіроўкі"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Сканіраванне QR-кода"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Працоўны профіль"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Рэжым палёту"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Вы не пачуеце наступны будзільнік <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Выключыць мабільную перадачу даных?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"У вас не будзе доступу да даных ці інтэрнэту праз аператара <xliff:g id="CARRIER">%s</xliff:g>. Інтэрнэт будзе даступны толькі праз Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш аператар"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Зноў пераключыцца на аператара \"<xliff:g id="CARRIER">%s</xliff:g>\"?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мабільны інтэрнэт не будзе аўтаматычна пераключацца ў залежнасці ад даступнасці"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, дзякуй"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Так, пераключыцца"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Праграма хавае запыт на дазвол, таму ваш адказ немагчыма спраўдзіць у Наладах."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Дазволіць праграме <xliff:g id="APP_0">%1$s</xliff:g> паказваць зрэзы праграмы <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Можа счытваць інфармацыю з праграмы <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мабільная перадача даных"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Падключана"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Падключана часова"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Нестабільнае падключэнне"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мабільная перадача даных не ўключаецца аўтаматычна"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Няма падключэння"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Больш няма даступных сетак"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 5a6ad73..44ee85c 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Гласова помощ"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Портфейл"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Скенер за QR кодове"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Отключване"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Устройството е заключено"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Извършва се сканиране на лице"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Изпращане"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Лицето не е разпознато"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Използвайте отпечатък"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е включен."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Процентът на батерията е неизвестен."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Самолетен режим."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"Функцията за виртуална частна мрежа (VPN) е включена."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> процента батерия."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батерията е на <xliff:g id="PERCENTAGE">%1$s</xliff:g> процента. Още около <xliff:g id="TIME">%2$s</xliff:g> въз основа на използването"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Батерията се зарежда, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Вижте всички известия"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter бе активиран."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Вибрира при звънене."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когато споделяте, записвате или предавате, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има достъп до всичко, което се показва или възпроизвежда в това приложение, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Напред"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделяне или записване на приложение"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Да се разреши ли на това приложение да споделя или записва?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Когато споделяте, записвате или предавате, това приложение има достъп до всичко, което се вижда на екрана ви или се възпроизвежда на устройството ви, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Когато споделяте, записвате или предавате, това приложение има достъп до всичко, което се показва или възпроизвежда в него, затова бъдете внимателни с пароли, подробности за начини на плащане, съобщения или друга поверителна информация."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано от системния ви администратор"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Заснемането на екрана е деактивирано от правило за устройството"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Изчистване на всички"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Управление"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Отключване с цел използване"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"При извличането на картите ви възникна проблем. Моля, опитайте отново по-късно"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Настройки за заключения екран"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR код: сканиране"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Потребителски профил в Work"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Самолетен режим"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Няма да чуете следващия си будилник в <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Да се изключат ли мобилните данни?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Няма да можете да използвате данни или интернет чрез <xliff:g id="CARRIER">%s</xliff:g>. Ще имате достъп до интернет само през Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"оператора си"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Искате ли да се върнете към <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мрежата за мобилни данни няма да се превключва автоматично въз основа на наличността"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, благодаря"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да, превключване"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"От Настройки не може да се получи потвърждение за отговора ви, защото заявката за разрешение се прикрива от приложение."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Искате ли да разрешите на <xliff:g id="APP_0">%1$s</xliff:g> да показва части от <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Може да чете информация от <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилни данни"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Свързано"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Установена е временна връзка"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Слаба връзка"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Връзката за мобилни данни няма да е автоматична"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Няма връзка"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Няма други налични мрежи"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 9533963..5c4013d 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ভয়েস সহায়তা"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR কোড স্ক্যানার"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"আনলক করুন"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"ডিভাইস লক করা আছে"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"ফেস স্ক্যান করা হচ্ছে"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"পাঠান"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ফেস শনাক্ত করা যায়নি"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"পরিবর্তে ফিঙ্গারপ্রিন্ট ব্যবহার করুন"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ব্লুটুথ সংযুক্ত হয়েছে৷"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ব্যাটারি কত শতাংশ আছে তা জানা যায়নি।"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"বিমান মোড৷"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN চালু আছে।"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> শতাংশ ব্যাটারি রয়েছে৷"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ব্যাটারি <xliff:g id="PERCENTAGE">%1$s</xliff:g> শতাংশ, বর্তমান ব্যবহারের উপর ভিত্তি করে আর <xliff:g id="TIME">%2$s</xliff:g> চলবে"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ব্যাটারি চার্জ হচ্ছে, এখন <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> শতাংশ চার্জ আছে৷"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"সমস্ত বিজ্ঞপ্তি দেখুন"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"টেলি টাইপরাইটার সক্ষম করা আছে৷"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"রিং বাজার সাথে স্পন্দিত করুন৷"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"কোনও অ্যাপ আপনার শেয়ার করা, রেকর্ড করা বা কাস্ট করার সময়, সেই অ্যাপে দেখা যায় বা খেলা হয় এমন সব কিছু অ্যাক্সেস করার অনুমতি <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>-এর আছে। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"চালিয়ে যান"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"অ্যাপ শেয়ার বা রেকর্ড করা"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"এই অ্যাপকে শেয়ার বা রেকর্ড করার অনুমতি দেবেন?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"আপনি শেয়ার, রেকর্ড বা কাস্ট করার সময় স্ক্রিনে দৃশ্যমান বা ডিভাইসে চালানো হয়েছে এমন সব কিছুই এই অ্যাপ অ্যাক্সেস করতে পারবে। সেই জন্য পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"কোনও অ্যাপের মাধ্যমে শেয়ার, রেকর্ড বা কাস্ট করার সময়, অ্যাপে দৃশ্যমান বা তাতে চালানো হয়েছে এমন সব কিছুই এই অ্যাপ অ্যাক্সেস করতে পারবে। সেই জন্য পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ বা অন্য সংবেদনশীল তথ্য সম্পর্কে সতর্ক থাকুন।"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"আপনার আইটি অ্যাডমিন ব্লক করেছেন"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ডিভাইস নীতির কারণে স্ক্রিন ক্যাপচার করার প্রসেস বন্ধ করা আছে"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"সবকিছু সাফ করুন"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"পরিচালনা করুন"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ব্যবহার করতে আনলক করুন"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"আপনার কার্ড সংক্রান্ত তথ্য পেতে সমস্যা হয়েছে, পরে আবার চেষ্টা করুন"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"লক স্ক্রিন সেটিংস"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR কোড স্ক্যান করুন"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"কাজের প্রোফাইল"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"বিমান মোড"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"আপনি আপনার পরবর্তী <xliff:g id="WHEN">%1$s</xliff:g> অ্যালার্ম শুনতে পাবেন না"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"মোবাইল ডেটা বন্ধ করবেন?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"আপনি \'<xliff:g id="CARRIER">%s</xliff:g>\'-এর মাধ্যমে ডেটা অথবা ইন্টারনেট অ্যাক্সেস করতে পারবেন না। শুধুমাত্র ওয়াই-ফাইয়ের মাধ্যমেই ইন্টারনেট অ্যাক্সেস করা যাবে।"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"আপনার পরিষেবা প্রদানকারী"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"আবার <xliff:g id="CARRIER">%s</xliff:g>-এর ডেটায় পরিবর্তন করবেন?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"উপলভ্যতার উপরে ভিত্তি করে অটোমেটিক মোবাইল ডেটায় পরিবর্তন করা হবে না"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"না থাক"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"হ্যাঁ, পাল্টান"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"একটি অ্যাপ কোনও অনুমোদনের অনুরোধকে ঢেকে দিচ্ছে, তাই সেটিংস থেকে আপনার প্রতিক্রিয়া যাচাই করা যাচ্ছে না।"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> অ্যাপটিকে <xliff:g id="APP_2">%2$s</xliff:g> এর অংশ দেখানোর অনুমতি দেবেন?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- এটি <xliff:g id="APP">%1$s</xliff:g> এর তথ্য অ্যাক্সেস করতে পারবে"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"মোবাইল ডেটা"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"কানেক্ট করা আছে"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"সাময়িকভাবে কানেক্ট করা হয়েছে"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"খারাপ কানেকশন"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"মোবাইল ডেটা নিজে থেকে কানেক্ট হবে না"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"কানেকশন নেই"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"অন্য কোনও নেটওয়ার্ক উপলভ্য নেই"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 69b09393..6b3c895 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Glasovna pomoć"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Novčanik"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Skener QR koda"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Otključaj"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Uređaj je zaključan"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Skeniranje lica"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Pošalji"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Nije moguće prepoznati lice"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Koristite otisak prsta"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth je povezan."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Postotak napunjenosti baterije nije poznat"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Način rada u avionu."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN uključen."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Baterija na <xliff:g id="NUMBER">%d</xliff:g> posto."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterija je na <xliff:g id="PERCENTAGE">%1$s</xliff:g> posto. Na osnovu vaše potrošnje preostalo vam je otprilike <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Punjenje baterije, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Vidite sva obavještenja"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Omogućena opcija TeleTypewriter."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Zvuk zvona na vibraciji."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kada aplikaciju dijelite, snimate ili emitirate, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijelite ili snimite aplikaciju"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Dozvoliti aplikaciji da dijeli ili snima?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kada dijelite, snimate ili emitirate, aplikacija ima pristup svemu što je vidljivo na ekranu ili što se reproducira na uređaju. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kada dijelite, snimate ili emitirate aplikaciju, ona ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Zato budite oprezni s lozinkama, detaljima o plaćanju, porukama i drugim osjetljivim informacijama."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao je vaš IT administrator"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje ekrana je onemogućeno pravilima uređaja"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Očisti sve"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historija"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključajte da koristite"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Došlo je do problema prilikom preuzimanja vaših kartica. Pokušajte ponovo kasnije"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Postavke zaključavanja ekrana"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skenirajte QR kôd"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"Čitač QR koda"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profil za posao"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Način rada u avionu"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sljedeći alarm u <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Isključiti prijenos podataka na mobilnoj mreži?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup podacima ni internetu putem mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo putem WiFi-ja."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vaš operater"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vratiti na operatera <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Prijenos podataka na mobilnoj mreži se neće automatski promijeniti na osnovu dostupnosti"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, promijeni"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Postavke ne mogu potvrditi vaš odgovor jer aplikacija zaklanja zahtjev za odobrenje."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Dozvoliti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Može čitati informacije iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Prijenos podataka na mobilnoj mreži"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Privremeno povezano"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slaba veza"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Prijenos podataka se neće automatski povezati"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Niste povezani s mrežom"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Druge mreže nisu dostupne"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index e992615..9892d26 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistència per veu"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Escàner de codis QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloqueja"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositiu bloquejat"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"S\'està escanejant la cara"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Envia"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"No es reconeix la cara"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utilitza l\'empremta digital"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connectat."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Es desconeix el percentatge de bateria."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mode d\'avió."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN activada"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> per cent de bateria."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> per cent de bateria amb aproximadament <xliff:g id="TIME">%2$s</xliff:g> de temps restant segons l\'ús que en fas"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"La bateria s\'està carregant (<xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%)."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Mostra totes les notificacions"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletip activat."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Mode vibració."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quan estàs compartint, gravant o emetent, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Comparteix o grava una aplicació"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vols permetre que aquesta aplicació comparteixi o gravi contingut?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Quan estàs compartint, gravant o emetent, aquesta aplicació té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi al dispositiu. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Quan estàs compartint, gravant o emetent, aquesta aplicació té accés a qualsevol cosa que es vegi a la pantalla o que es reprodueixi a l\'aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges o altra informació sensible."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquejat per l\'administrador de TI"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Les captures de pantalla estan desactivades per la política de dispositius"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Esborra-ho tot"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestiona"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
@@ -430,8 +431,8 @@
<string name="monitoring_description_management_network_logging" msgid="216983105036994771">"L\'administrador ha activat el registre de xarxa, que supervisa el trànsit del teu dispositiu."</string>
<string name="monitoring_description_managed_profile_network_logging" msgid="6932303843097006037">"L\'administrador ha activat el registre de xarxa, que monitora el trànsit al teu perfil de treball, però no al personal."</string>
<string name="monitoring_description_named_vpn" msgid="7502657784155456414">"Aquest dispositiu es connecta a Internet a través de <xliff:g id="VPN_APP">%1$s</xliff:g>. El teu administrador de TI pot veure l\'activitat de la teva xarxa, inclosos els correus electrònics i les dades de navegació."</string>
- <string name="monitoring_description_two_named_vpns" msgid="6726394451199620634">"Aquest dispositiu es connecta a Internet a través de <xliff:g id="VPN_APP_0">%1$s</xliff:g> i <xliff:g id="VPN_APP_1">%2$s</xliff:g>. El teu administrador de TI pot veure la teva activitat a la xarxa, inclosos els correus electrònics i les dades de navegació."</string>
- <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"Les aplicacions de treball es connecten a Internet a través de <xliff:g id="VPN_APP">%1$s</xliff:g>. El teu administrador de TI i el teu proveïdor de VPN poden veure la teva activitat a la xarxa en aplicacions de treball, inclosos els correus electrònics i les dades de navegació."</string>
+ <string name="monitoring_description_two_named_vpns" msgid="6726394451199620634">"Aquest dispositiu es connecta a Internet a través de <xliff:g id="VPN_APP_0">%1$s</xliff:g> i <xliff:g id="VPN_APP_1">%2$s</xliff:g>. El teu administrador de TI pot veure la teva activitat de xarxa, inclosos els correus electrònics i les dades de navegació."</string>
+ <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"Les aplicacions de treball es connecten a Internet a través de <xliff:g id="VPN_APP">%1$s</xliff:g>. El teu administrador de TI i el teu proveïdor de VPN poden veure la teva activitat de xarxa en aplicacions de treball, inclosos els correus electrònics i les dades de navegació."</string>
<string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"Les aplicacions personals es connecten a Internet a través de <xliff:g id="VPN_APP">%1$s</xliff:g>. El teu proveïdor de VPN pot veure l\'activitat de la teva xarxa, inclosos els correus electrònics i les dades de navegació."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
<string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Obre la configuració de la VPN"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloqueja per utilitzar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Hi ha hagut un problema en obtenir les teves targetes; torna-ho a provar més tard"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuració de la pantalla de bloqueig"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Escaneja un codi QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de treball"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mode d\'avió"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> no sentiràs la pròxima alarma"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vols desactivar les dades mòbils?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"No tindràs accés a dades ni a Internet mitjançant <xliff:g id="CARRIER">%s</xliff:g>. Internet només estarà disponible per Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"el teu operador de telefonia mòbil"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vols tornar a <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Les dades mòbils no canviaran automàticament en funció de la disponibilitat"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, gràcies"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sí, fes el canvi"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Com que hi ha una aplicació que oculta una sol·licitud de permís, no es pot verificar la teva resposta des de la configuració."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vols permetre que <xliff:g id="APP_0">%1$s</xliff:g> mostri porcions de l\'aplicació <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Pot llegir informació de l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dades mòbils"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connectat"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connexió temporal"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connexió feble"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Les dades mòbils no es connectaran automàticament"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sense connexió"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hi ha cap altra xarxa disponible"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 56b9185..18894d2 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Hlasová asistence"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Peněženka"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Čtečka QR kódů"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Odemknout"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Zařízení uzamčeno"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Skenování obličeje"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Odeslat"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Obličej nelze rozpoznat"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Použijte otisk prstu"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Rozhraní Bluetooth je připojeno."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procento baterie není známé."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Režim Letadlo."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN je zapnuto."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Stav baterie: <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterie je nabitá na <xliff:g id="PERCENTAGE">%1$s</xliff:g> procent, při vašem používání vydrží ještě <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Baterie se nabíjí. Nabito: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Zobrazit všechna oznámení"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Rozhraní TeleTypewriter zapnuto."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibrační vyzvánění."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Když sdílíte, nahráváte nebo odesíláte aplikaci, aplikace <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> má přístup k veškerému obsahu, který je v této aplikaci zobrazen nebo přehráván. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovat"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Sdílení nebo nahrání aplikace"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Povolit této aplikaci sdílet nebo nahrávat?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Když sdílíte, nahráváte nebo odesíláte obsah, aplikace má přístup ke všemu, co je viditelné na obrazovce nebo se přehrává v zařízení. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Když sdílíte, nahráváte nebo odesíláte aplikaci, aplikace má přístup ke všemu, co je v této aplikaci zobrazeno nebo se přehrává. Dejte proto pozor na hesla, platební údaje, zprávy nebo jiné citlivé informace."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokováno administrátorem IT"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Záznam obrazovky je zakázán zásadami zařízení"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Smazat vše"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Spravovat"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historie"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odemknout a použít"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Při načítání karet došlo k problému, zkuste to později"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavení obrazovky uzamčení"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Naskenovat QR kód"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Pracovní profil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Režim Letadlo"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Svůj další budík <xliff:g id="WHEN">%1$s</xliff:g> neuslyšíte"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vypnout mobilní data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Prostřednictvím <xliff:g id="CARRIER">%s</xliff:g> nebudete moci používat data ani internet. Internet bude dostupný pouze přes Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vašeho operátora"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Přepnout zpět na operátora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilní data se nebudou automaticky přepínat podle dostupnosti"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, díky"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ano, přepnout"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Žádost o oprávnění je blokována jinou aplikací. Nastavení proto vaši odpověď nemůže ověřit."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Povolit aplikaci <xliff:g id="APP_0">%1$s</xliff:g> zobrazovat ukázky z aplikace <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Může číst informace z aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilní data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Připojeno"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Dočasně připojeno"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Nekvalitní připojení"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilní data se nebudou připojovat automaticky"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Žádné připojení"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Žádné další sítě nejsou k dispozici"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index c977058..8dbe978 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Taleassistent"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-kodescanner"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Lås op"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Enheden er låst"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanner ansigt"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Ansigt kan ikke genkendes"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Brug fingeraftryk i stedet"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tilsluttet."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batteriniveauet er ukendt."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Flytilstand."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN er slået til."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Batteri <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batteriniveauet er på <xliff:g id="PERCENTAGE">%1$s</xliff:g> procent, så du har ca. <xliff:g id="TIME">%2$s</xliff:g> tilbage, alt efter hvordan du bruger enheden"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batteriet oplades. <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Se alle notifikationer"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter aktiveret."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ringervibration."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, optager eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsæt"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller optag en app"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vil du tillade, at denne app deler eller optager?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Når du deler, optager eller caster, har denne app adgang til alt, der vises på din skærm eller afspilles på din enhed. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Når du deler, optager eller caster en app, har denne app adgang til alt, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med adgangskoder, betalingsoplysninger, beskeder og andre følsomme oplysninger."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeret af din it-administrator"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screenshots er deaktiveret af enhedspolitikken"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås op for at bruge"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Dine kort kunne ikke hentes. Prøv igen senere."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lås skærmindstillinger"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scan QR-kode"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Arbejdsprofil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Flytilstand"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Du vil ikke kunne høre din næste alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vil du deaktivere mobildata?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Du vil ikke have data- eller internetadgang via <xliff:g id="CARRIER">%s</xliff:g>. Der vil kun være adgang til internettet via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"dit mobilselskab"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vil du skifte tilbage til <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobildata skifter ikke automatisk på baggrund af tilgængelighed"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nej tak"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, skift"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Indstillinger kan ikke bekræfte dit svar, da en app dækker for en anmodning om tilladelse."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vil du give <xliff:g id="APP_0">%1$s</xliff:g> tilladelse til at vise eksempler fra <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Den kan læse oplysninger fra <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Forbundet"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Midlertidigt forbundet"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Dårlig forbindelse"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Ingen automatisk mobildataforbindelse"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Der er ingen forbindelse"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Der er ingen andre tilgængelige netværk"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 74f9c5f..5191b47 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Sprachassistent"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-Code-Scanner"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Entsperren"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Gerät gesperrt"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Gesicht wird gescannt"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Senden"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Gesicht nicht erkannt"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Fingerabdruck verwenden"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Mit Bluetooth verbunden"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akkustand unbekannt."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Flugmodus"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN an."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Akku bei <xliff:g id="NUMBER">%d</xliff:g> Prozent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Akku bei <xliff:g id="PERCENTAGE">%1$s</xliff:g> Prozent. Bei deinem Nutzungsmuster hast du noch Strom für etwa <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Akku wird aufgeladen, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> Prozent."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Alle Benachrichtigungen ansehen"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Schreibtelefonie aktiviert"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Klingeltonmodus \"Vibration\""</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Beim Teilen, Aufnehmen oder Übertragen einer App hat <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Weiter"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App teilen oder aufnehmen"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Dieser App das Teilen oder Aufnehmen erlauben?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Beim Teilen, Aufnehmen oder Übertragen hat diese App Zugriff auf alle Inhalte, die auf dem Bildschirm sichtbar sind oder auf dem Gerät wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Beim Teilen, Aufnehmen oder Übertragen einer App hat diese App Zugriff auf alle Inhalte, die in dieser App sichtbar sind oder wiedergegeben werden. Sei daher mit Passwörtern, Zahlungsdetails, Nachrichten oder anderen vertraulichen Informationen vorsichtig."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Vom IT-Administrator blockiert"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Bildschirmaufnahme ist durch die Geräterichtlinien deaktiviert"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alle löschen"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Verwalten"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Verlauf"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Zum Verwenden entsperren"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Beim Abrufen deiner Karten ist ein Fehler aufgetreten – bitte versuch es später noch einmal"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Einstellungen für den Sperrbildschirm"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR-Code scannen"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Arbeitsprofil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Flugmodus"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Lautloser Weckruf <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobile Daten deaktivieren?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Du kannst dann nicht mehr über <xliff:g id="CARRIER">%s</xliff:g> auf Daten und das Internet zugreifen. Das Internet ist nur noch über WLAN verfügbar."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"deinen Mobilfunkanbieter"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Zurück zu <xliff:g id="CARRIER">%s</xliff:g> wechseln?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile Daten werden nicht je nach Verfügbarkeit automatisch gewechselt"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nein danke"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, wechseln"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Deine Eingabe wird von \"Einstellungen\" nicht erkannt, weil die Berechtigungsanfrage von einer App verdeckt wird."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> erlauben, Teile von <xliff:g id="APP_2">%2$s</xliff:g> anzuzeigen?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Darf Informationen aus <xliff:g id="APP">%1$s</xliff:g> lesen"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile Daten"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Verbunden"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Vorübergehend verbunden"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Schwache Verbindung"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Keine automatische Verbindung über mobile Daten"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Keine Verbindung"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Keine anderen Netzwerke verfügbar"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 59f645d..448e1e0 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Φωνητική υποβοήθηση"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Πορτοφόλι"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Σάρωση κωδικών QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Ξεκλείδωμα"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Η συσκευή κλειδώθηκε"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Σάρωση προσώπου"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Αποστολή"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Αδύνατη η αναγν. προσώπου"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Χρησιμ. δακτυλ. αποτύπ."</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Το Bluetooth είναι συνδεδεμένο."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Άγνωστο ποσοστό μπαταρίας."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Λειτουργία πτήσης."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ενεργό."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Μπαταρία <xliff:g id="NUMBER">%d</xliff:g> τοις εκατό."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Μπαταρία στο <xliff:g id="PERCENTAGE">%1$s</xliff:g> τοις εκατό. Περίπου <xliff:g id="TIME">%2$s</xliff:g> ακόμη, βάσει της χρήσης σας"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Φόρτιση μπαταρίας: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Δείτε όλες τις ειδοποιήσεις"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Το TeleTypewriter ενεργοποιήθηκε."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Δόνηση ειδοποίησης ήχου."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Όταν κάνετε κοινοποίηση, εγγραφή ή μετάδοση μιας εφαρμογής, η εφαρμογή <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα ή άλλες ευαίσθητες πληροφορίες."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Συνέχεια"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Κοινοποίηση ή εγγραφή εφαρμογής"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Να επιτρέπεται σε αυτήν την εφαρμογή η κοινή χρήση ή εγγραφή;"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Όταν κάνετε κοινοποίηση, εγγραφή ή μετάδοση, αυτή η εφαρμογή έχει πρόσβαση σε οτιδήποτε είναι ορατό στην οθόνη σας ή αναπαράγεται στη συσκευή σας. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα ή άλλες ευαίσθητες πληροφορίες."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Όταν κάνετε κοινοποίηση, εγγραφή ή μετάδοση μιας εφαρμογής, αυτή η εφαρμογή έχει πρόσβαση σε οτιδήποτε είναι ορατό ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα ή άλλες ευαίσθητες πληροφορίες."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Αποκλείστηκε από τον διαχειριστή IT"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Η καταγραφή οθόνης έχει απενεργοποιηθεί από την πολιτική χρήσης συσκευής."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Διαγραφή όλων"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Διαχείριση"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Ιστορικό"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ξεκλείδωμα για χρήση"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Παρουσιάστηκε πρόβλημα με τη λήψη των καρτών σας. Δοκιμάστε ξανά αργότερα"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ρυθμίσεις κλειδώματος οθόνης"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Σάρωση κωδικού QR"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"Σάρωση κωδικών QR"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Προφίλ εργασίας"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Λειτουργία πτήσης"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Δεν θα ακούσετε το επόμενο ξυπνητήρι σας <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Απενεργοποίηση δεδομένων κινητής τηλεφωνίας;"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Δεν θα έχετε πρόσβαση σε δεδομένα ή στο διαδίκτυο μέσω της εταιρείας κινητής τηλεφωνίας <xliff:g id="CARRIER">%s</xliff:g>. Θα έχετε πρόσβαση στο διαδίκτυο μόνο μέσω Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"η εταιρεία κινητής τηλεφωνίας"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Επιστροφή σε <xliff:g id="CARRIER">%s</xliff:g>;"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Δεν θα γίνεται αυτόματα εναλλαγή των δεδομένων κινητής τηλεφωνίας βάσει της διαθεσιμότητας"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Όχι, ευχαριστώ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ναι, εναλλαγή"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Επειδή μια εφαρμογή αποκρύπτει ένα αίτημα άδειας, δεν είναι δυνατή η επαλήθευση της απάντησής σας από τις Ρυθμίσεις."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Να επιτρέπεται στο <xliff:g id="APP_0">%1$s</xliff:g> να εμφανίζει τμήματα της εφαρμογής <xliff:g id="APP_2">%2$s</xliff:g>;"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Μπορεί να διαβάζει πληροφορίες από την εφαρμογή <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Δεδομένα κινητής τηλεφωνίας"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Συνδέθηκε"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Προσωρινή σύνδεση"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Ασθενής σύνδεση"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Χωρίς αυτόματη σύνδεση δεδομένων κινητ. τηλεφωνίας"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Χωρίς σύνδεση"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Δεν υπάρχουν άλλα διαθέσιμα δίκτυα"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 19845f4..bfa63f6 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -125,7 +125,7 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR code scanner"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Unlock"</string>
+ <string name="accessibility_unlock_button" msgid="3613812140816244310">"Unlocked"</string>
<string name="accessibility_lock_icon" msgid="661492842417875775">"Device locked"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanning face"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -168,7 +168,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
@@ -180,8 +180,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Aeroplane mode"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN on."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> per cent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$s</xliff:g> percentage, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Battery charging, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> percent."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"See all notifications"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter enabled."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ringer vibrate."</string>
@@ -495,7 +500,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scan QR code"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR code scanner"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -734,6 +739,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Switch back to <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile data won\'t automatically switch based on availability"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No thanks"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yes, switch"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -924,6 +933,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporarily connected"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Poor connection"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 615e8b6..879d88b 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -21,8 +21,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4811759950673118541">"System UI"</string>
<string name="battery_low_title" msgid="5319680173344341779">"Turn on Battery Saver?"</string>
- <string name="battery_low_description" msgid="3282977755476423966">"You have <xliff:g id="PERCENTAGE">%s</xliff:g> battery left. Battery Saver turns on Dark theme, restricts background activity and delays notifications."</string>
- <string name="battery_low_intro" msgid="5148725009653088790">"Battery Saver turns on Dark theme, restricts background activity and delays notifications."</string>
+ <string name="battery_low_description" msgid="3282977755476423966">"You have <xliff:g id="PERCENTAGE">%s</xliff:g> battery left. Battery Saver turns on Dark theme, restricts background activity, and delays notifications."</string>
+ <string name="battery_low_intro" msgid="5148725009653088790">"Battery Saver turns on Dark theme, restricts background activity, and delays notifications."</string>
<string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> remaining"</string>
<string name="invalid_charger_title" msgid="938685362320735167">"Can\'t charge via USB"</string>
<string name="invalid_charger_text" msgid="2339310107232691577">"Use the charger that came with your device"</string>
@@ -30,7 +30,7 @@
<string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"About Battery Saver"</string>
<string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Turn on"</string>
<string name="battery_saver_start_action" msgid="8353766979886287140">"Turn on"</string>
- <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No, thanks"</string>
+ <string name="battery_saver_dismiss_action" msgid="7199342621040014738">"No thanks"</string>
<string name="status_bar_settings_auto_rotation" msgid="8329080442278431708">"Auto-rotate screen"</string>
<string name="usb_device_permission_prompt" msgid="4414719028369181772">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?"</string>
<string name="usb_device_permission_prompt_warn" msgid="2309129784984063656">"Allow <xliff:g id="APPLICATION">%1$s</xliff:g> to access <xliff:g id="USB_DEVICE">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device."</string>
@@ -64,7 +64,7 @@
<string name="wifi_debugging_secondary_user_title" msgid="2493201475880517725">"Wireless debugging not allowed"</string>
<string name="wifi_debugging_secondary_user_message" msgid="4492383073970079751">"The user currently signed in to this device can’t turn on wireless debugging. To use this feature, switch to the primary user."</string>
<string name="usb_contaminant_title" msgid="894052515034594113">"USB port disabled"</string>
- <string name="usb_contaminant_message" msgid="7730476585174719805">"To protect your device from liquid or debris, the USB port is disabled and won’t detect any accessories.\n\nYou’ll be notified when it’s OK to use the USB port again."</string>
+ <string name="usb_contaminant_message" msgid="7730476585174719805">"To protect your device from liquid or debris, the USB port is disabled and won’t detect any accessories.\n\nYou’ll be notified when it’s okay to use the USB port again."</string>
<string name="usb_port_enabled" msgid="531823867664717018">"USB port enabled to detect chargers and accessories"</string>
<string name="usb_disable_contaminant_detection" msgid="3827082183595978641">"Enable USB"</string>
<string name="learn_more" msgid="4690632085667273811">"Learn more"</string>
@@ -85,23 +85,23 @@
<string name="screenshot_scroll_label" msgid="2930198809899329367">"Capture more"</string>
<string name="screenshot_dismiss_description" msgid="4702341245899508786">"Dismiss screenshot"</string>
<string name="screenshot_preview_description" msgid="7606510140714080474">"Screenshot preview"</string>
- <string name="screenshot_top_boundary_pct" msgid="2520148599096479332">"Top boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
- <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
- <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
- <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string>
+ <string name="screenshot_top_boundary_pct" msgid="2520148599096479332">"Top boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
+ <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"Bottom boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
+ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
+ <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string>
<string name="screenrecord_name" msgid="2596401223859996572">"Screen Recorder"</string>
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Processing screen recording"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Ongoing notification for a screen record session"</string>
- <string name="screenrecord_start_label" msgid="1750350278888217473">"Start recording?"</string>
- <string name="screenrecord_description" msgid="1123231719680353736">"While recording, the Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages and audio."</string>
+ <string name="screenrecord_start_label" msgid="1750350278888217473">"Start Recording?"</string>
+ <string name="screenrecord_description" msgid="1123231719680353736">"While recording, Android System can capture any sensitive information that’s visible on your screen or played on your device. This includes passwords, payment info, photos, messages, and audio."</string>
<string name="screenrecord_option_entire_screen" msgid="1732437834603426934">"Record entire screen"</string>
<string name="screenrecord_option_single_app" msgid="5954863081500035825">"Record a single app"</string>
- <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
- <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"While you\'re recording an app, Android has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="screenrecord_warning_entire_screen" msgid="8141407178104195610">"While you\'re recording, Android has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+ <string name="screenrecord_warning_single_app" msgid="7760723997065948283">"While you\'re recording an app, Android has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information."</string>
<string name="screenrecord_start_recording" msgid="348286842544768740">"Start recording"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Record audio"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Device audio"</string>
- <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls and ringtones"</string>
+ <string name="screenrecord_device_audio_description" msgid="4922694220572186193">"Sound from your device, like music, calls, and ringtones"</string>
<string name="screenrecord_mic_label" msgid="2111264835791332350">"Microphone"</string>
<string name="screenrecord_device_audio_and_mic_label" msgid="1831323771978646841">"Device audio and microphone"</string>
<string name="screenrecord_start" msgid="330991441575775004">"Start"</string>
@@ -124,8 +124,8 @@
<string name="accessibility_phone_button" msgid="4256353121703100427">"Phone"</string>
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
- <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR code scanner"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Unlock"</string>
+ <string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR Code Scanner"</string>
+ <string name="accessibility_unlock_button" msgid="3613812140816244310">"Unlocked"</string>
<string name="accessibility_lock_icon" msgid="661492842417875775">"Device locked"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanning face"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -140,8 +140,8 @@
<string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"Tap Confirm to complete"</string>
<string name="biometric_dialog_tap_confirm_with_face" msgid="1092050545851021991">"Unlocked by face. Press the unlock icon to continue."</string>
<string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"Unlocked by face. Press to continue."</string>
- <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Face recognised. Press to continue."</string>
- <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"Face recognised. Press the unlock icon to continue."</string>
+ <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Face recognized. Press to continue."</string>
+ <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"Face recognized. Press the unlock icon to continue."</string>
<string name="biometric_dialog_authenticated" msgid="7337147327545272484">"Authenticated"</string>
<string name="biometric_dialog_use_pin" msgid="8385294115283000709">"Use PIN"</string>
<string name="biometric_dialog_use_pattern" msgid="2315593393167211194">"Use pattern"</string>
@@ -163,12 +163,12 @@
<string name="biometric_dialog_last_password_attempt_before_wipe_profile" msgid="8538032972389729253">"If you enter an incorrect password on the next attempt, your work profile and its data will be deleted."</string>
<string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"Touch the fingerprint sensor"</string>
<string name="accessibility_fingerprint_dialog_fingerprint_icon" msgid="4465698996175640549">"Fingerprint icon"</string>
- <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognise face. Use fingerprint instead."</string>
+ <string name="fingerprint_dialog_use_fingerprint_instead" msgid="6178228876763024452">"Can’t recognize face. Use fingerprint instead."</string>
<!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) -->
<skip />
- <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
+ <string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognize face"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
@@ -179,9 +179,14 @@
<string name="cell_data_off" msgid="4886198950247099526">"Off"</string>
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Airplane mode."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN on."</string>
- <string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> per cent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$s</xliff:g> percentage, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
+ <string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> percent."</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Battery charging, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> percent."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"See all notifications"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter enabled."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ringer vibrate."</string>
@@ -196,7 +201,7 @@
<string name="accessibility_quick_settings_dnd_none_on" msgid="3235552940146035383">"total silence"</string>
<string name="accessibility_quick_settings_dnd_alarms_on" msgid="3375848309132140014">"alarms only"</string>
<string name="accessibility_quick_settings_dnd" msgid="2415967452264206047">"Do Not Disturb."</string>
- <string name="accessibility_quick_settings_bluetooth" msgid="8250942386687551283">"Bluetooth"</string>
+ <string name="accessibility_quick_settings_bluetooth" msgid="8250942386687551283">"Bluetooth."</string>
<string name="accessibility_quick_settings_bluetooth_on" msgid="3819082137684078013">"Bluetooth on."</string>
<string name="accessibility_quick_settings_alarm" msgid="558094529584082090">"Alarm set for <xliff:g id="TIME">%s</xliff:g>."</string>
<string name="accessibility_quick_settings_more_time" msgid="7646479831704665284">"More time."</string>
@@ -205,12 +210,12 @@
<string name="accessibility_brightness" msgid="5391187016177823721">"Display brightness"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="2286843518689837719">"Mobile data is paused"</string>
<string name="data_usage_disabled_dialog_title" msgid="9131615296036724838">"Data is paused"</string>
- <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"The data limit that you set has been reached. You are no longer using mobile data.\n\nIf you resume, charges may apply for data usage."</string>
+ <string name="data_usage_disabled_dialog" msgid="7933201635215099780">"The data limit you set has been reached. You are no longer using mobile data.\n\nIf you resume, charges may apply for data usage."</string>
<string name="data_usage_disabled_dialog_enable" msgid="2796648546086408937">"Resume"</string>
<string name="accessibility_location_active" msgid="2845747916764660369">"Location requests active"</string>
<string name="accessibility_sensors_off_active" msgid="2619725434618911551">"Sensors off active"</string>
<string name="accessibility_clear_all" msgid="970525598287244592">"Clear all notifications."</string>
- <string name="notification_group_overflow_indicator" msgid="7605120293801012648">"+<xliff:g id="NUMBER">%s</xliff:g>"</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{# more notification inside.}other{# more notifications inside.}}"</string>
<string name="accessibility_rotation_lock_on_landscape" msgid="936972553861524360">"Screen is locked in landscape orientation."</string>
<string name="accessibility_rotation_lock_on_portrait" msgid="2356633398683813837">"Screen is locked in portrait orientation."</string>
@@ -229,7 +234,7 @@
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"Auto-rotate"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"Auto-rotate screen"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"Location"</string>
- <string name="quick_settings_screensaver_label" msgid="1495003469366524120">"Screensaver"</string>
+ <string name="quick_settings_screensaver_label" msgid="1495003469366524120">"Screen saver"</string>
<string name="quick_settings_camera_label" msgid="5612076679385269339">"Camera access"</string>
<string name="quick_settings_mic_label" msgid="8392773746295266375">"Mic access"</string>
<string name="quick_settings_camera_mic_available" msgid="1453719768420394314">"Available"</string>
@@ -248,8 +253,8 @@
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
<string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
- <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
- <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
+ <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Color inversion"</string>
+ <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Color correction"</string>
<string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Manage users"</string>
<string name="quick_settings_done" msgid="2163641301648855793">"Done"</string>
<string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Close"</string>
@@ -307,11 +312,11 @@
<string name="sensor_privacy_mic_camera_unblocked_toast_content" msgid="7339355093282661115">"Microphone and camera available"</string>
<string name="media_seamless_other_device" msgid="4654849800789196737">"Other device"</string>
<string name="quick_step_accessibility_toggle_overview" msgid="7908949976727578403">"Toggle Overview"</string>
- <string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events and callers you specify. You\'ll still hear anything you choose to play including music, videos and games."</string>
- <string name="zen_alarms_introduction" msgid="3987266042682300470">"You won\'t be disturbed by sounds and vibrations, except from alarms. You\'ll still hear anything you choose to play including music, videos and games."</string>
- <string name="zen_priority_customize_button" msgid="4119213187257195047">"Customise"</string>
+ <string name="zen_priority_introduction" msgid="3159291973383796646">"You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events, and callers you specify. You\'ll still hear anything you choose to play including music, videos, and games."</string>
+ <string name="zen_alarms_introduction" msgid="3987266042682300470">"You won\'t be disturbed by sounds and vibrations, except from alarms. You\'ll still hear anything you choose to play including music, videos, and games."</string>
+ <string name="zen_priority_customize_button" msgid="4119213187257195047">"Customize"</string>
<string name="zen_silence_introduction_voice" msgid="853573681302712348">"This blocks ALL sounds and vibrations, including from alarms, music, videos, and games. You\'ll still be able to make phone calls."</string>
- <string name="zen_silence_introduction" msgid="6117517737057344014">"This blocks ALL sounds and vibrations, including from alarms, music, videos and games."</string>
+ <string name="zen_silence_introduction" msgid="6117517737057344014">"This blocks ALL sounds and vibrations, including from alarms, music, videos, and games."</string>
<string name="notification_tap_again" msgid="4477318164947497249">"Tap again to open"</string>
<string name="tap_again" msgid="1315420114387908655">"Tap again"</string>
<string name="keyguard_unlock" msgid="8031975796351361601">"Swipe up to open"</string>
@@ -319,10 +324,10 @@
<string name="keyguard_face_successful_unlock_swipe" msgid="6180997591385846073">"Unlocked by face. Swipe up to open."</string>
<string name="keyguard_face_successful_unlock_press" msgid="25520941264602588">"Unlocked by face. Press the unlock icon to open."</string>
<string name="keyguard_face_successful_unlock_press_alt_1" msgid="5715461103913071474">"Unlocked by face. Press to open."</string>
- <string name="keyguard_face_successful_unlock_press_alt_2" msgid="8310787946357120406">"Face recognised. Press to open."</string>
- <string name="keyguard_face_successful_unlock_press_alt_3" msgid="7219030481255573962">"Face recognised. Press the unlock icon to open."</string>
+ <string name="keyguard_face_successful_unlock_press_alt_2" msgid="8310787946357120406">"Face recognized. Press to open."</string>
+ <string name="keyguard_face_successful_unlock_press_alt_3" msgid="7219030481255573962">"Face recognized. Press the unlock icon to open."</string>
<string name="keyguard_face_successful_unlock" msgid="4203999851465708287">"Unlocked by face"</string>
- <string name="keyguard_face_successful_unlock_alt1" msgid="5853906076353839628">"Face recognised"</string>
+ <string name="keyguard_face_successful_unlock_alt1" msgid="5853906076353839628">"Face recognized"</string>
<string-array name="udfps_accessibility_touch_hints">
<item msgid="1901953991150295169">"Move left"</item>
<item msgid="5558598599408514296">"Move down"</item>
@@ -354,7 +359,7 @@
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
<string name="guest_wipe_session_title" msgid="7147965814683990944">"Welcome back, guest!"</string>
<string name="guest_wipe_session_message" msgid="3393823610257065457">"Do you want to continue your session?"</string>
- <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Start again"</string>
+ <string name="guest_wipe_session_wipe" msgid="8056836584445473309">"Start over"</string>
<string name="guest_wipe_session_dontwipe" msgid="3211052048269304205">"Yes, continue"</string>
<string name="guest_notification_app_name" msgid="2110425506754205509">"Guest mode"</string>
<string name="guest_notification_session_active" msgid="5567273684713471450">"You are in guest mode"</string>
@@ -364,20 +369,20 @@
<string name="user_remove_user_title" msgid="9124124694835811874">"Remove user?"</string>
<string name="user_remove_user_message" msgid="6702834122128031833">"All apps and data of this user will be deleted."</string>
<string name="user_remove_user_remove" msgid="8387386066949061256">"Remove"</string>
- <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
- <string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages and audio that you play."</string>
+ <string name="media_projection_dialog_text" msgid="1755705274910034772">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages, and audio that you play."</string>
+ <string name="media_projection_dialog_service_text" msgid="958000992162214611">"The service providing this function will have access to all of the information that is visible on your screen or played from your device while recording or casting. This includes information such as passwords, payment details, photos, messages, and audio that you play."</string>
<string name="media_projection_dialog_service_title" msgid="2888507074107884040">"Start recording or casting?"</string>
<string name="media_projection_dialog_title" msgid="3316063622495360646">"Start recording or casting with <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
<string name="media_projection_permission_dialog_title" msgid="7130975432309482596">"Allow <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> to share or record?"</string>
<string name="media_projection_permission_dialog_option_entire_screen" msgid="392086473225692983">"Entire screen"</string>
<string name="media_projection_permission_dialog_option_single_app" msgid="1591110238124910521">"A single app"</string>
- <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"When you\'re sharing, recording or casting, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
- <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So, be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_warning_entire_screen" msgid="3989078820637452717">"When you\'re sharing, recording, or casting, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"When you\'re sharing, recording, or casting an app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continue"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Share or record an app"</string>
<string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Allow this app to share or record?"</string>
- <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"When you\'re sharing, recording or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages or other sensitive information."</string>
- <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"When you\'re sharing, recording or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"When you\'re sharing, recording, or casting, this app has access to anything visible on your screen or played on your device. So be careful with passwords, payment details, messages, or other sensitive information."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"When you\'re sharing, recording, or casting an app, this app has access to anything shown or played on that app. So be careful with passwords, payment details, messages, or other sensitive information."</string>
<string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocked by your IT admin"</string>
<string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Screen capturing is disabled by device policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Clear all"</string>
@@ -396,19 +401,19 @@
<string name="quick_settings_disclosure_named_management_monitoring" msgid="2831423806103479812">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> owns this device and may monitor network traffic"</string>
<string name="quick_settings_financed_disclosure_named_management" msgid="2307703784594859524">"This device is provided by <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
<string name="quick_settings_disclosure_management_named_vpn" msgid="4137564460025113168">"This device belongs to your organization and is connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
- <string name="quick_settings_disclosure_named_management_named_vpn" msgid="2169227918166358741">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> and is connected to the Internet through <xliff:g id="VPN_APP">%2$s</xliff:g>"</string>
+ <string name="quick_settings_disclosure_named_management_named_vpn" msgid="2169227918166358741">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> and is connected to the internet through <xliff:g id="VPN_APP">%2$s</xliff:g>"</string>
<string name="quick_settings_disclosure_management" msgid="5515296598440684962">"This device belongs to your organization"</string>
<string name="quick_settings_disclosure_named_management" msgid="3476472755775165827">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>"</string>
<string name="quick_settings_disclosure_management_vpns" msgid="929181757984262902">"This device belongs to your organization and is connected to the internet through VPNs"</string>
- <string name="quick_settings_disclosure_named_management_vpns" msgid="3312645578322079185">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> and is connected to the Internet through VPNs"</string>
+ <string name="quick_settings_disclosure_named_management_vpns" msgid="3312645578322079185">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> and is connected to the internet through VPNs"</string>
<string name="quick_settings_disclosure_managed_profile_monitoring" msgid="1423899084754272514">"Your organization may monitor network traffic in your work profile"</string>
<string name="quick_settings_disclosure_named_managed_profile_monitoring" msgid="8321469176706219860">"<xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g> may monitor network traffic in your work profile"</string>
<string name="quick_settings_disclosure_managed_profile_network_activity" msgid="2636594621387832827">"Work profile network activity is visible to your IT admin"</string>
<string name="quick_settings_disclosure_monitoring" msgid="8548019955631378680">"Network may be monitored"</string>
- <string name="quick_settings_disclosure_vpns" msgid="3586175303518266301">"This device is connected to the Internet through VPNs"</string>
- <string name="quick_settings_disclosure_managed_profile_named_vpn" msgid="153393105176944100">"Your work apps are connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
- <string name="quick_settings_disclosure_personal_profile_named_vpn" msgid="451254750289172191">"Your personal apps are connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
- <string name="quick_settings_disclosure_named_vpn" msgid="6191822916936028208">"This device is connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
+ <string name="quick_settings_disclosure_vpns" msgid="3586175303518266301">"This device is connected to the internet through VPNs"</string>
+ <string name="quick_settings_disclosure_managed_profile_named_vpn" msgid="153393105176944100">"Your work apps are connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
+ <string name="quick_settings_disclosure_personal_profile_named_vpn" msgid="451254750289172191">"Your personal apps are connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
+ <string name="quick_settings_disclosure_named_vpn" msgid="6191822916936028208">"This device is connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
<string name="monitoring_title_financed_device" msgid="3659962357973919387">"This device is provided by <xliff:g id="ORGANIZATION_NAME">%s</xliff:g>"</string>
<string name="monitoring_title_device_owned" msgid="7029691083837606324">"Device management"</string>
<string name="monitoring_subtitle_vpn" msgid="800485258004629079">"VPN"</string>
@@ -417,22 +422,22 @@
<string name="monitoring_button_view_policies" msgid="3869724835853502410">"View Policies"</string>
<string name="monitoring_button_view_controls" msgid="8316440345340701117">"View controls"</string>
<string name="monitoring_description_named_management" msgid="505833016545056036">"This device belongs to <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>.\n\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your IT admin."</string>
- <string name="monitoring_financed_description_named_management" msgid="6108439201399938668">"<xliff:g id="ORGANIZATION_NAME_0">%1$s</xliff:g> may be able to access data associated with this device, manage apps and change this device\'s settings.\n\nIf you have questions, contact <xliff:g id="ORGANIZATION_NAME_1">%2$s</xliff:g>."</string>
+ <string name="monitoring_financed_description_named_management" msgid="6108439201399938668">"<xliff:g id="ORGANIZATION_NAME_0">%1$s</xliff:g> may be able to access data associated with this device, manage apps, and change this devices settings.\n\nIf you have questions, contact <xliff:g id="ORGANIZATION_NAME_1">%2$s</xliff:g>."</string>
<string name="monitoring_description_management" msgid="4308879039175729014">"This device belongs to your organization.\n\nYour IT admin can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nFor more information, contact your IT admin."</string>
<string name="monitoring_description_management_ca_certificate" msgid="7785013130658110130">"Your organization installed a certificate authority on this device. Your secure network traffic may be monitored or modified."</string>
<string name="monitoring_description_managed_profile_ca_certificate" msgid="7904323416598435647">"Your organization installed a certificate authority in your work profile. Your secure network traffic may be monitored or modified."</string>
<string name="monitoring_description_ca_certificate" msgid="448923057059097497">"A certificate authority is installed on this device. Your secure network traffic may be monitored or modified."</string>
<string name="monitoring_description_management_network_logging" msgid="216983105036994771">"Your admin has turned on network logging, which monitors traffic on your device."</string>
<string name="monitoring_description_managed_profile_network_logging" msgid="6932303843097006037">"Your admin has turned on network logging, which monitors traffic in your work profile but not in your personal profile."</string>
- <string name="monitoring_description_named_vpn" msgid="7502657784155456414">"This device is connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin."</string>
- <string name="monitoring_description_two_named_vpns" msgid="6726394451199620634">"This device is connected to the Internet through <xliff:g id="VPN_APP_0">%1$s</xliff:g> and <xliff:g id="VPN_APP_1">%2$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin."</string>
- <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"Your work apps are connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity in work apps, including emails and browsing data, is visible to your IT admin and VPN provider."</string>
- <string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"Your personal apps are connected to the Internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your VPN provider."</string>
+ <string name="monitoring_description_named_vpn" msgid="7502657784155456414">"This device is connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin."</string>
+ <string name="monitoring_description_two_named_vpns" msgid="6726394451199620634">"This device is connected to the internet through <xliff:g id="VPN_APP_0">%1$s</xliff:g> and <xliff:g id="VPN_APP_1">%2$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your IT admin."</string>
+ <string name="monitoring_description_managed_profile_named_vpn" msgid="7254359257263069766">"Your work apps are connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity in work apps, including emails and browsing data, is visible to your IT admin and VPN provider."</string>
+ <string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"Your personal apps are connected to the internet through <xliff:g id="VPN_APP">%1$s</xliff:g>. Your network activity, including emails and browsing data, is visible to your VPN provider."</string>
<string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
<string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Open VPN settings"</string>
- <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"This device is managed by your parent. Your parent can see and manage information such as the apps that you use, your location and your screen time."</string>
+ <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"This device is managed by your parent. Your parent can see and manage information such as the apps you use, your location, and your screen time."</string>
<string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string>
- <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Kept unlocked by trust agent"</string>
+ <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Kept unlocked by TrustAgent"</string>
<string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
<string name="accessibility_volume_settings" msgid="1458961116951564784">"Sound settings"</string>
<string name="volume_odi_captions_tip" msgid="8825655463280990941">"Automatically caption media"</string>
@@ -440,21 +445,21 @@
<string name="volume_odi_captions_content_description" msgid="4172765742046013630">"Captions overlay"</string>
<string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"enable"</string>
<string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"disable"</string>
- <string name="sound_settings" msgid="8874581353127418308">"Sound and vibration"</string>
+ <string name="sound_settings" msgid="8874581353127418308">"Sound & vibration"</string>
<string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"Settings"</string>
<string name="screen_pinning_title" msgid="9058007390337841305">"App is pinned"</string>
<string name="screen_pinning_description" msgid="8699395373875667743">"This keeps it in view until you unpin. Touch & hold Back and Overview to unpin."</string>
<string name="screen_pinning_description_recents_invisible" msgid="4564466648700390037">"This keeps it in view until you unpin. Touch & hold Back and Home to unpin."</string>
- <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up and hold to unpin."</string>
+ <string name="screen_pinning_description_gestural" msgid="7246323931831232068">"This keeps it in view until you unpin. Swipe up & hold to unpin."</string>
<string name="screen_pinning_description_accessible" msgid="7386449191953535332">"This keeps it in view until you unpin. Touch & hold Overview to unpin."</string>
<string name="screen_pinning_description_recents_invisible_accessible" msgid="2857071808674481986">"This keeps it in view until you unpin. Touch & hold Home to unpin."</string>
- <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Personal data may be accessible, such as contacts and email content."</string>
+ <string name="screen_pinning_exposes_personal_data" msgid="8189852022981524789">"Personal data may be accessible (such as contacts and email content)."</string>
<string name="screen_pinning_can_open_other_apps" msgid="7529756813231421455">"Pinned app may open other apps."</string>
- <string name="screen_pinning_toast" msgid="8177286912533744328">"To unpin this app, touch and hold Back and Overview buttons"</string>
- <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"To unpin this app, touch and hold Back and Home buttons"</string>
- <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"To unpin this app, swipe up and hold"</string>
+ <string name="screen_pinning_toast" msgid="8177286912533744328">"To unpin this app, touch & hold Back and Overview buttons"</string>
+ <string name="screen_pinning_toast_recents_invisible" msgid="6850978077443052594">"To unpin this app, touch & hold Back and Home buttons"</string>
+ <string name="screen_pinning_toast_gesture_nav" msgid="170699893395336705">"To unpin this app, swipe up & hold"</string>
<string name="screen_pinning_positive" msgid="3285785989665266984">"Got it"</string>
- <string name="screen_pinning_negative" msgid="6882816864569211666">"No, thanks"</string>
+ <string name="screen_pinning_negative" msgid="6882816864569211666">"No thanks"</string>
<string name="screen_pinning_start" msgid="7483998671383371313">"App pinned"</string>
<string name="screen_pinning_exit" msgid="4553787518387346893">"App unpinned"</string>
<string name="stream_voice_call" msgid="7468348170702375660">"Call"</string>
@@ -464,7 +469,7 @@
<string name="stream_alarm" msgid="16058075093011694">"Alarm"</string>
<string name="stream_notification" msgid="7930294049046243939">"Notification"</string>
<string name="stream_bluetooth_sco" msgid="6234562365528664331">"Bluetooth"</string>
- <string name="stream_dtmf" msgid="7322536356554673067">"Dual multi-tone frequency"</string>
+ <string name="stream_dtmf" msgid="7322536356554673067">"Dual multi tone frequency"</string>
<string name="stream_accessibility" msgid="3873610336741987152">"Accessibility"</string>
<string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ring"</string>
<string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Vibrate"</string>
@@ -493,9 +498,9 @@
<string name="wallet_secondary_label_no_card" msgid="8488069304491125713">"Tap to open"</string>
<string name="wallet_secondary_label_updating" msgid="5726130686114928551">"Updating"</string>
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string>
- <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string>
+ <string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards, please try again later"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scan QR code"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR code scanner"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -504,17 +509,17 @@
<string name="accessibility_status_bar_hotspot" msgid="2888479317489131669">"Hotspot"</string>
<string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string>
<string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string>
- <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customise the Android user interface. These experimental features may change, break or disappear in future releases. Proceed with caution."</string>
- <string name="tuner_persistent_warning" msgid="230466285569307806">"These experimental features may change, break or disappear in future releases. Proceed with caution."</string>
+ <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customize the Android user interface. These experimental features may change, break, or disappear in future releases. Proceed with caution."</string>
+ <string name="tuner_persistent_warning" msgid="230466285569307806">"These experimental features may change, break, or disappear in future releases. Proceed with caution."</string>
<string name="got_it" msgid="477119182261892069">"Got it"</string>
<string name="tuner_toast" msgid="3812684836514766951">"Congrats! System UI Tuner has been added to Settings"</string>
- <string name="remove_from_settings" msgid="633775561782209994">"Remove from settings"</string>
+ <string name="remove_from_settings" msgid="633775561782209994">"Remove from Settings"</string>
<string name="remove_from_settings_prompt" msgid="551565437265615426">"Remove System UI Tuner from Settings and stop using all of its features?"</string>
<string name="enable_bluetooth_title" msgid="866883307336662596">"Turn on Bluetooth?"</string>
<string name="enable_bluetooth_message" msgid="6740938333772779717">"To connect your keyboard with your tablet, you first have to turn on Bluetooth."</string>
<string name="enable_bluetooth_confirmation_ok" msgid="2866408183324184876">"Turn on"</string>
<string name="tuner_full_importance_settings" msgid="1388025816553459059">"Power notification controls"</string>
- <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On – Face-based"</string>
+ <string name="rotation_lock_camera_rotation_on" msgid="789434807790534274">"On - Face-based"</string>
<string name="power_notification_controls_description" msgid="1334963837572708952">"With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications. \n\n"<b>"Level 5"</b>" \n- Show at the top of the notification list \n- Allow full screen interruption \n- Always peek \n\n"<b>"Level 4"</b>" \n- Prevent full screen interruption \n- Always peek \n\n"<b>"Level 3"</b>" \n- Prevent full screen interruption \n- Never peek \n\n"<b>"Level 2"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound and vibration \n\n"<b>"Level 1"</b>" \n- Prevent full screen interruption \n- Never peek \n- Never make sound or vibrate \n- Hide from lock screen and status bar \n- Show at the bottom of the notification list \n\n"<b>"Level 0"</b>" \n- Block all notifications from the app"</string>
<string name="inline_done_button" msgid="6043094985588909584">"Done"</string>
<string name="inline_ok_button" msgid="603075490581280343">"Apply"</string>
@@ -527,10 +532,10 @@
<string name="notification_channel_summary_default" msgid="3282930979307248890">"May ring or vibrate based on phone settings"</string>
<string name="notification_channel_summary_default_with_bubbles" msgid="1782419896613644568">"May ring or vibrate based on phone settings. Conversations from <xliff:g id="APP_NAME">%1$s</xliff:g> bubble by default."</string>
<string name="notification_channel_summary_automatic" msgid="5813109268050235275">"Have the system determine if this notification should make sound or vibration"</string>
- <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> promoted to default"</string>
- <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> demoted to silent"</string>
- <string name="notification_channel_summary_automatic_promoted" msgid="1301710305149590426">"<b>Status:</b> ranked higher"</string>
- <string name="notification_channel_summary_automatic_demoted" msgid="1831303964660807700">"<b>Status:</b> ranked lower"</string>
+ <string name="notification_channel_summary_automatic_alerted" msgid="954166812246932240">"<b>Status:</b> Promoted to Default"</string>
+ <string name="notification_channel_summary_automatic_silenced" msgid="7403004439649872047">"<b>Status:</b> Demoted to Silent"</string>
+ <string name="notification_channel_summary_automatic_promoted" msgid="1301710305149590426">"<b>Status:</b> Ranked Higher"</string>
+ <string name="notification_channel_summary_automatic_demoted" msgid="1831303964660807700">"<b>Status:</b> Ranked Lower"</string>
<string name="notification_channel_summary_priority_baseline" msgid="46674690072551234">"Shows at the top of conversation notifications and as a profile picture on lock screen"</string>
<string name="notification_channel_summary_priority_bubble" msgid="1275413109619074576">"Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble"</string>
<string name="notification_channel_summary_priority_dnd" msgid="6665395023264154361">"Shows at the top of conversation notifications and as a profile picture on lock screen, interrupts Do Not Disturb"</string>
@@ -543,15 +548,15 @@
<string name="notification_delegate_header" msgid="1264510071031479920">"Proxied notification"</string>
<string name="notification_channel_dialog_title" msgid="6856514143093200019">"All <xliff:g id="APP_NAME">%1$s</xliff:g> notifications"</string>
<string name="see_more_title" msgid="7409317011708185729">"See more"</string>
- <string name="feedback_alerted" msgid="5192459808484271208">"This notification was automatically <b>promoted to default</b> by the system."</string>
- <string name="feedback_silenced" msgid="9116540317466126457">"This notification was automatically <b>demoted to silent</b> by the system."</string>
+ <string name="feedback_alerted" msgid="5192459808484271208">"This notification was automatically <b>promoted to Default</b> by the system."</string>
+ <string name="feedback_silenced" msgid="9116540317466126457">"This notification was automatically <b>demoted to Silent</b> by the system."</string>
<string name="feedback_promoted" msgid="2125562787759780807">"This notification was automatically <b>ranked higher</b> in your shade."</string>
<string name="feedback_demoted" msgid="951884763467110604">"This notification was automatically <b>ranked lower</b> in your shade."</string>
<string name="feedback_prompt" msgid="3656728972307896379">"Let the developer know your feedback. Was this correct?"</string>
<string name="notification_channel_controls_opened_accessibility" msgid="6111817750774381094">"Notification controls for <xliff:g id="APP_NAME">%1$s</xliff:g> opened"</string>
<string name="notification_channel_controls_closed_accessibility" msgid="1561909368876911701">"Notification controls for <xliff:g id="APP_NAME">%1$s</xliff:g> closed"</string>
<string name="notification_more_settings" msgid="4936228656989201793">"More settings"</string>
- <string name="notification_app_settings" msgid="8963648463858039377">"Customise"</string>
+ <string name="notification_app_settings" msgid="8963648463858039377">"Customize"</string>
<string name="notification_conversation_bubble" msgid="2242180995373949022">"Show bubble"</string>
<string name="notification_conversation_unbubble" msgid="6908427185031099868">"Remove bubbles"</string>
<string name="notification_menu_accessibility" msgid="8984166825879886773">"<xliff:g id="APP_NAME">%1$s</xliff:g> <xliff:g id="MENU_DESCRIPTION">%2$s</xliff:g>"</string>
@@ -570,7 +575,7 @@
<string name="keyboard_key_dpad_down" msgid="2110172278574325796">"Down"</string>
<string name="keyboard_key_dpad_left" msgid="8329738048908755640">"Left"</string>
<string name="keyboard_key_dpad_right" msgid="6282105433822321767">"Right"</string>
- <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"Centre"</string>
+ <string name="keyboard_key_dpad_center" msgid="4079412840715672825">"Center"</string>
<string name="keyboard_key_tab" msgid="4592772350906496730">"Tab"</string>
<string name="keyboard_key_space" msgid="6980847564173394012">"Space"</string>
<string name="keyboard_key_enter" msgid="8633362970109751646">"Enter"</string>
@@ -580,7 +585,7 @@
<string name="keyboard_key_media_next" msgid="8502476691227914952">"Next"</string>
<string name="keyboard_key_media_previous" msgid="5637875709190955351">"Previous"</string>
<string name="keyboard_key_media_rewind" msgid="3450387734224327577">"Rewind"</string>
- <string name="keyboard_key_media_fast_forward" msgid="3572444327046911822">"Fast-Forward"</string>
+ <string name="keyboard_key_media_fast_forward" msgid="3572444327046911822">"Fast Forward"</string>
<string name="keyboard_key_page_up" msgid="173914303254199845">"Page Up"</string>
<string name="keyboard_key_page_down" msgid="9035902490071829731">"Page Down"</string>
<string name="keyboard_key_forward_del" msgid="5325501825762733459">"Delete"</string>
@@ -592,10 +597,10 @@
<string name="notif_inline_reply_remove_attachment_description" msgid="7954075334095405429">"Remove attachment"</string>
<string name="keyboard_shortcut_group_system" msgid="1583416273777875970">"System"</string>
<string name="keyboard_shortcut_group_system_home" msgid="7465138628692109907">"Home"</string>
- <string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"Recent"</string>
+ <string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"Recents"</string>
<string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"Back"</string>
<string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notifications"</string>
- <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Keyboard shortcuts"</string>
+ <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Keyboard Shortcuts"</string>
<string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Switch keyboard layout"</string>
<string name="keyboard_shortcut_group_applications" msgid="7386239431100651266">"Applications"</string>
<string name="keyboard_shortcut_group_applications_assist" msgid="771606231466098742">"Assist"</string>
@@ -637,7 +642,7 @@
<string name="save" msgid="3392754183673848006">"Save"</string>
<string name="reset" msgid="8715144064608810383">"Reset"</string>
<string name="clipboard" msgid="8517342737534284617">"Clipboard"</string>
- <string name="accessibility_key" msgid="3471162841552818281">"Customised navigation button"</string>
+ <string name="accessibility_key" msgid="3471162841552818281">"Custom navigation button"</string>
<string name="left_keycode" msgid="8211040899126637342">"Left keycode"</string>
<string name="right_keycode" msgid="2480715509844798438">"Right keycode"</string>
<string name="left_icon" msgid="5036278531966897006">"Left icon"</string>
@@ -649,7 +654,7 @@
<string name="qs_edit" msgid="5583565172803472437">"Edit"</string>
<string name="tuner_time" msgid="2450785840990529997">"Time"</string>
<string-array name="clock_options">
- <item msgid="3986445361435142273">"Show hours, minutes and seconds"</item>
+ <item msgid="3986445361435142273">"Show hours, minutes, and seconds"</item>
<item msgid="1271006222031257266">"Show hours and minutes (default)"</item>
<item msgid="6135970080453877218">"Don\'t show this icon"</item>
</string-array>
@@ -676,7 +681,7 @@
<string name="accessibility_quick_settings_collapse" msgid="4674876336725041982">"Close quick settings."</string>
<string name="accessibility_quick_settings_user" msgid="505821942882668619">"Signed in as <xliff:g id="ID_1">%s</xliff:g>"</string>
<string name="accessibility_quick_settings_choose_user_action" msgid="4554388498186576087">"choose user"</string>
- <string name="data_connection_no_internet" msgid="691058178914184544">"No Internet"</string>
+ <string name="data_connection_no_internet" msgid="691058178914184544">"No internet"</string>
<string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"Open <xliff:g id="ID_1">%s</xliff:g> settings."</string>
<string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"Edit order of settings."</string>
<string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"Power menu"</string>
@@ -684,14 +689,14 @@
<string name="tuner_lock_screen" msgid="2267383813241144544">"Lock screen"</string>
<string name="thermal_shutdown_title" msgid="2702966892682930264">"Phone turned off due to heat"</string>
<string name="thermal_shutdown_message" msgid="6142269839066172984">"Your phone is now running normally.\nTap for more info"</string>
- <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n • Use resource-intensive apps (such as gaming, video or navigation apps)\n • Download or upload large files\n • Use your phone in high temperatures"</string>
+ <string name="thermal_shutdown_dialog_message" msgid="6745684238183492031">"Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n • Use resource-intensive apps (such as gaming, video, or navigation apps)\n • Download or upload large files\n • Use your phone in high temperatures"</string>
<string name="thermal_shutdown_dialog_help_text" msgid="6413474593462902901">"See care steps"</string>
<string name="high_temp_title" msgid="2218333576838496100">"Phone is getting warm"</string>
- <string name="high_temp_notif_message" msgid="1277346543068257549">"Some features are limited while phone cools down.\nTap for more info"</string>
- <string name="high_temp_dialog_message" msgid="3793606072661253968">"Your phone will automatically try to cool down. You can still use your phone, but it may run more slowly.\n\nOnce your phone has cooled down, it will run normally."</string>
+ <string name="high_temp_notif_message" msgid="1277346543068257549">"Some features limited while phone cools down.\nTap for more info"</string>
+ <string name="high_temp_dialog_message" msgid="3793606072661253968">"Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally."</string>
<string name="high_temp_dialog_help_text" msgid="7380171287943345858">"See care steps"</string>
<string name="high_temp_alarm_title" msgid="8654754369605452169">"Unplug your device"</string>
- <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Your device is getting warm near the charging port. If it’s connected to a charger or USB accessory, unplug it and take care as the cable may also be warm."</string>
+ <string name="high_temp_alarm_notify_message" msgid="3917622943609118956">"Your device is getting warm near the charging port. If it’s connected to a charger or USB accessory, unplug it, and take care as the cable may also be warm."</string>
<string name="high_temp_alarm_help_care_steps" msgid="5017002218341329566">"See care steps"</string>
<string name="lockscreen_shortcut_left" msgid="1238765178956067599">"Left shortcut"</string>
<string name="lockscreen_shortcut_right" msgid="4138414674531853719">"Right shortcut"</string>
@@ -717,11 +722,11 @@
<string name="instant_apps" msgid="8337185853050247304">"Instant Apps"</string>
<string name="instant_apps_title" msgid="8942706782103036910">"<xliff:g id="APP">%1$s</xliff:g> running"</string>
<string name="instant_apps_message" msgid="6112428971833011754">"App opened without being installed."</string>
- <string name="instant_apps_message_with_help" msgid="1816952263531203932">"App opened without being installed. Tap to find out more."</string>
+ <string name="instant_apps_message_with_help" msgid="1816952263531203932">"App opened without being installed. Tap to learn more."</string>
<string name="app_info" msgid="5153758994129963243">"App info"</string>
<string name="go_to_web" msgid="636673528981366511">"Go to browser"</string>
<string name="mobile_data" msgid="4564407557775397216">"Mobile data"</string>
- <string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> – <xliff:g id="ID_2">%2$s</xliff:g>"</string>
+ <string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> — <xliff:g id="ID_2">%2$s</xliff:g>"</string>
<string name="mobile_carrier_text_format" msgid="8912204177152950766">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string>
<string name="wifi_is_off" msgid="5389597396308001471">"Wi-Fi is off"</string>
<string name="bt_is_off" msgid="7436344904889461591">"Bluetooth is off"</string>
@@ -732,18 +737,22 @@
<string name="running_foreground_services_title" msgid="5137313173431186685">"Apps running in background"</string>
<string name="running_foreground_services_msg" msgid="3009459259222695385">"Tap for details on battery and data usage"</string>
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
- <string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
+ <string name="mobile_data_disable_message" msgid="8604966027899770415">"You wont have access to data or the internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your carrier"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Switch back to <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile data wont automatically switch based on availability"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No thanks"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yes, switch"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
- <string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
- <string name="slice_permission_text_2" msgid="6758906940360746983">"– It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="slice_permission_text_1" msgid="6675965177075443714">"- It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="slice_permission_text_2" msgid="6758906940360746983">"- It can take actions inside <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="slice_permission_checkbox" msgid="4242888137592298523">"Allow <xliff:g id="APP">%1$s</xliff:g> to show slices from any app"</string>
<string name="slice_permission_allow" msgid="6340449521277951123">"Allow"</string>
<string name="slice_permission_deny" msgid="6870256451658176895">"Deny"</string>
<string name="auto_saver_title" msgid="6873691178754086596">"Tap to schedule Battery Saver"</string>
<string name="auto_saver_text" msgid="3214960308353838764">"Turn on when battery is likely to run out"</string>
- <string name="no_auto_saver_action" msgid="7467924389609773835">"No, thanks"</string>
+ <string name="no_auto_saver_action" msgid="7467924389609773835">"No thanks"</string>
<string name="heap_dump_tile_name" msgid="2464189856478823046">"Dump SysUI Heap"</string>
<string name="ongoing_privacy_dialog_a11y_title" msgid="2205794093673327974">"In use"</string>
<string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Applications are using your <xliff:g id="TYPES_LIST">%s</xliff:g>."</string>
@@ -762,8 +771,8 @@
<string name="privacy_type_media_projection" msgid="8136723828804251547">"screen recording"</string>
<string name="music_controls_no_title" msgid="4166497066552290938">"No title"</string>
<string name="inattentive_sleep_warning_title" msgid="3891371591713990373">"Standby"</string>
- <string name="magnification_window_title" msgid="4863914360847258333">"Magnification window"</string>
- <string name="magnification_controls_title" msgid="8421106606708891519">"Magnification window controls"</string>
+ <string name="magnification_window_title" msgid="4863914360847258333">"Magnification Window"</string>
+ <string name="magnification_controls_title" msgid="8421106606708891519">"Magnification Window Controls"</string>
<string name="accessibility_control_zoom_in" msgid="1189272315480097417">"Zoom in"</string>
<string name="accessibility_control_zoom_out" msgid="69578832020304084">"Zoom out"</string>
<string name="accessibility_control_move_up" msgid="6622825494014720136">"Move up"</string>
@@ -774,7 +783,7 @@
<string name="magnification_mode_switch_state_full_screen" msgid="5229653514979530561">"Magnify full screen"</string>
<string name="magnification_mode_switch_state_window" msgid="8597100249594076965">"Magnify part of screen"</string>
<string name="magnification_mode_switch_click_label" msgid="2786203505805898199">"Switch"</string>
- <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
+ <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customize or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
<string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
<string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
<string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string>
@@ -787,15 +796,15 @@
<string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
<string name="controls_number_of_favorites" msgid="4481806788981836355">"{count,plural, =1{# control added.}other{# controls added.}}"</string>
<string name="controls_removed" msgid="3731789252222856959">"Removed"</string>
- <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favourited"</string>
- <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favourited, position <xliff:g id="NUMBER">%d</xliff:g>"</string>
- <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavourited"</string>
- <string name="accessibility_control_change_favorite" msgid="2943178027582253261">"favourite"</string>
- <string name="accessibility_control_change_unfavorite" msgid="6997408061750740327">"unfavourite"</string>
+ <string name="accessibility_control_favorite" msgid="8694362691985545985">"Favorited"</string>
+ <string name="accessibility_control_favorite_position" msgid="54220258048929221">"Favorited, position <xliff:g id="NUMBER">%d</xliff:g>"</string>
+ <string name="accessibility_control_not_favorite" msgid="1291760269563092359">"Unfavorited"</string>
+ <string name="accessibility_control_change_favorite" msgid="2943178027582253261">"favorite"</string>
+ <string name="accessibility_control_change_unfavorite" msgid="6997408061750740327">"unfavorite"</string>
<string name="accessibility_control_move" msgid="8980344493796647792">"Move to position <xliff:g id="NUMBER">%d</xliff:g>"</string>
<string name="controls_favorite_default_title" msgid="967742178688938137">"Controls"</string>
<string name="controls_favorite_subtitle" msgid="6481675111056961083">"Choose controls to access from Quick Settings"</string>
- <string name="controls_favorite_rearrange" msgid="5616952398043063519">"Hold and drag to rearrange controls"</string>
+ <string name="controls_favorite_rearrange" msgid="5616952398043063519">"Hold & drag to rearrange controls"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"All controls removed"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"Changes not saved"</string>
<string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"See other apps"</string>
@@ -806,11 +815,11 @@
<string name="controls_dialog_ok" msgid="2770230012857881822">"Add"</string>
<string name="controls_dialog_message" msgid="342066938390663844">"Suggested by <xliff:g id="APP">%s</xliff:g>"</string>
<string name="controls_tile_locked" msgid="731547768182831938">"Device locked"</string>
- <string name="controls_settings_show_controls_dialog_title" msgid="3357852503553809554">"Show and control devices from the lock screen?"</string>
- <string name="controls_settings_show_controls_dialog_message" msgid="7666211700524587969">"You can add controls for your external devices to the lock screen.\n\nYour device app may allow you to control some devices without unlocking your phone or tablet.\n\nYou can make changes at any time in Settings."</string>
- <string name="controls_settings_trivial_controls_dialog_title" msgid="7593188157655036677">"Control devices from the lock screen?"</string>
+ <string name="controls_settings_show_controls_dialog_title" msgid="3357852503553809554">"Show and control devices from lock screen?"</string>
+ <string name="controls_settings_show_controls_dialog_message" msgid="7666211700524587969">"You can add controls for your external devices to the lock screen.\n\nYour device app may allow you to control some devices without unlocking your phone or tablet.\n\nYou can make changes any time in Settings."</string>
+ <string name="controls_settings_trivial_controls_dialog_title" msgid="7593188157655036677">"Control devices from lock screen?"</string>
<string name="controls_settings_trivial_controls_dialog_message" msgid="237183787721917586">"You can control some devices without unlocking your phone or tablet.\n\nYour device app determines which devices can be controlled in this way."</string>
- <string name="controls_settings_dialog_neutral_button" msgid="4514446354793124140">"No, thanks"</string>
+ <string name="controls_settings_dialog_neutral_button" msgid="4514446354793124140">"No thanks"</string>
<string name="controls_settings_dialog_positive_button" msgid="436070672551674863">"Yes"</string>
<string name="controls_pin_use_alphanumeric" msgid="8478371861023048414">"PIN contains letters or symbols"</string>
<string name="controls_pin_verify" msgid="3452778292918877662">"Verify <xliff:g id="DEVICE">%s</xliff:g>"</string>
@@ -845,7 +854,7 @@
<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>
- <string name="controls_error_removed_message" msgid="2885911717034750542">"Couldn’t access <xliff:g id="DEVICE">%1$s</xliff:g>. Check the <xliff:g id="APPLICATION">%2$s</xliff:g> app to make sure that the control is still available and that the app settings haven’t changed."</string>
+ <string name="controls_error_removed_message" msgid="2885911717034750542">"Couldn’t access <xliff:g id="DEVICE">%1$s</xliff:g>. Check the <xliff:g id="APPLICATION">%2$s</xliff:g> app to make sure the control is still available and that the app settings haven’t changed."</string>
<string name="controls_open_app" msgid="483650971094300141">"Open app"</string>
<string name="controls_error_generic" msgid="352500456918362905">"Can’t load status"</string>
<string name="controls_error_failed" msgid="960228639198558525">"Error, try again"</string>
@@ -853,7 +862,7 @@
<string name="controls_menu_edit" msgid="890623986951347062">"Edit controls"</string>
<string name="media_output_dialog_add_output" msgid="5642703238877329518">"Add outputs"</string>
<string name="media_output_dialog_group" msgid="5571251347877452212">"Group"</string>
- <string name="media_output_dialog_single_device" msgid="3102758980643351058">"One device selected"</string>
+ <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 device selected"</string>
<string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> devices selected"</string>
<string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(disconnected)"</string>
<string name="media_output_dialog_connect_failed" msgid="3080972621975339387">"Can\'t switch. Tap to try again."</string>
@@ -865,9 +874,9 @@
<string name="media_output_dialog_accessibility_seekbar" msgid="5332843993805568978">"Volume"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"How broadcasting works"</string>
<string name="media_output_broadcast" msgid="3555580945878071543">"Broadcast"</string>
- <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media that you\'re broadcasting"</string>
+ <string name="media_output_first_notify_broadcast_message" msgid="6353857724136398494">"People near you with compatible Bluetooth devices can listen to the media you\'re broadcasting"</string>
<string name="media_output_broadcasting_message" msgid="4150299923404886073">"To listen to your broadcast, people nearby with compatible Bluetooth devices can scan your QR code or use your broadcast name and password"</string>
- <string name="media_output_broadcast_name" msgid="8786127091542624618">"Broadcast name"</string>
+ <string name="media_output_broadcast_name" msgid="8786127091542624618">"Broadcast Name"</string>
<string name="media_output_broadcast_code" msgid="870795639644728542">"Password"</string>
<string name="media_output_broadcast_dialog_save" msgid="7910865591430010198">"Save"</string>
<string name="media_output_broadcast_starting" msgid="8130153654166235557">"Starting…"</string>
@@ -878,21 +887,21 @@
<string name="build_number_copy_toast" msgid="877720921605503046">"Build number copied to clipboard."</string>
<string name="basic_status" msgid="2315371112182658176">"Open conversation"</string>
<string name="select_conversation_title" msgid="6716364118095089519">"Conversation widgets"</string>
- <string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your home screen"</string>
+ <string name="select_conversation_text" msgid="3376048251434956013">"Tap a conversation to add it to your Home screen"</string>
<string name="no_conversations_text" msgid="5354115541282395015">"Your recent conversations will show up here"</string>
<string name="priority_conversations" msgid="3967482288896653039">"Priority conversations"</string>
<string name="recent_conversations" msgid="8531874684782574622">"Recent conversations"</string>
<string name="days_timestamp" msgid="5821854736213214331">"<xliff:g id="DURATION">%1$s</xliff:g> days ago"</string>
<string name="one_week_timestamp" msgid="4925600765473875590">"1 week ago"</string>
<string name="two_weeks_timestamp" msgid="9111801081871962155">"2 weeks ago"</string>
- <string name="over_one_week_timestamp" msgid="3770560704420807142">"More than 1 week ago"</string>
- <string name="over_two_weeks_timestamp" msgid="6300507859007874050">"More than 2 weeks ago"</string>
+ <string name="over_one_week_timestamp" msgid="3770560704420807142">"Over 1 week ago"</string>
+ <string name="over_two_weeks_timestamp" msgid="6300507859007874050">"Over 2 weeks ago"</string>
<string name="birthday_status" msgid="2596961629465396761">"Birthday"</string>
- <string name="birthday_status_content_description" msgid="682836371128282925">"It\'s <xliff:g id="NAME">%1$s</xliff:g>\'s birthday"</string>
+ <string name="birthday_status_content_description" msgid="682836371128282925">"It\'s <xliff:g id="NAME">%1$s</xliff:g>s birthday"</string>
<string name="upcoming_birthday_status" msgid="2005452239256870351">"Birthday soon"</string>
- <string name="upcoming_birthday_status_content_description" msgid="2165036816803797148">"It\'s <xliff:g id="NAME">%1$s</xliff:g>\'s birthday soon"</string>
+ <string name="upcoming_birthday_status_content_description" msgid="2165036816803797148">"It\'s <xliff:g id="NAME">%1$s</xliff:g>s birthday soon"</string>
<string name="anniversary_status" msgid="1790034157507590838">"Anniversary"</string>
- <string name="anniversary_status_content_description" msgid="8212171790843327442">"It\'s <xliff:g id="NAME">%1$s</xliff:g>\'s anniversary"</string>
+ <string name="anniversary_status_content_description" msgid="8212171790843327442">"It\'s <xliff:g id="NAME">%1$s</xliff:g>s anniversary"</string>
<string name="location_status" msgid="1294990572202541812">"Sharing location"</string>
<string name="location_status_content_description" msgid="2982386178160071305">"<xliff:g id="NAME">%1$s</xliff:g> is sharing location"</string>
<string name="new_story_status" msgid="9012195158584846525">"New story"</string>
@@ -905,7 +914,7 @@
<string name="status_before_loading" msgid="1500477307859631381">"Content will show up soon"</string>
<string name="missed_call" msgid="4228016077700161689">"Missed call"</string>
<string name="messages_count_overflow_indicator" msgid="7850934067082006043">"<xliff:g id="NUMBER">%d</xliff:g>+"</string>
- <string name="people_tile_description" msgid="8154966188085545556">"See recent messages, missed calls and status updates"</string>
+ <string name="people_tile_description" msgid="8154966188085545556">"See recent messages, missed calls, and status updates"</string>
<string name="people_tile_title" msgid="6589377493334871272">"Conversation"</string>
<string name="paused_by_dnd" msgid="7856941866433556428">"Paused by Do Not Disturb"</string>
<string name="new_notification_text_content_description" msgid="2915029960094389291">"<xliff:g id="NAME">%1$s</xliff:g> sent a message: <xliff:g id="NOTIFICATION">%2$s</xliff:g>"</string>
@@ -916,14 +925,16 @@
<string name="battery_state_unknown_notification_text" msgid="13720937839460899">"Tap for more information"</string>
<string name="qs_alarm_tile_no_alarm" msgid="4826472008616807923">"No alarm set"</string>
<string name="accessibility_fingerprint_label" msgid="5255731221854153660">"Fingerprint sensor"</string>
- <string name="accessibility_authenticate_hint" msgid="798914151813205721">"Authenticate"</string>
+ <string name="accessibility_authenticate_hint" msgid="798914151813205721">"authenticate"</string>
<string name="accessibility_enter_hint" msgid="2617864063504824834">"enter device"</string>
<string name="keyguard_try_fingerprint" msgid="2825130772993061165">"Use fingerprint to open"</string>
<string name="accessibility_fingerprint_bouncer" msgid="7189102492498735519">"Authentication required. Touch the fingerprint sensor to authenticate."</string>
<string name="ongoing_phone_call_content_description" msgid="5332334388483099947">"Ongoing phone call"</string>
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
- <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
+ <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporarily connected"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Poor connection"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
@@ -935,7 +946,7 @@
<string name="wifi_failed_connect_message" msgid="4161863112079000071">"Failed to connect to network"</string>
<string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Wi‑Fi won’t auto-connect for now"</string>
<string name="see_all_networks" msgid="3773666844913168122">"See all"</string>
- <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect Ethernet"</string>
+ <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"To switch networks, disconnect ethernet"</string>
<string name="wifi_scan_notify_message" msgid="3753839537448621794">"To improve device experience, apps and services can still scan for Wi‑Fi networks at any time, even when Wi‑Fi is off. You can change this in Wi‑Fi scanning settings. "<annotation id="link">"Change"</annotation></string>
<string name="turn_off_airplane_mode" msgid="8425587763226548579">"Turn off Airplane mode"</string>
<string name="qs_tile_request_dialog_text" msgid="3501359944139877694">"<xliff:g id="APPNAME">%1$s</xliff:g> wants to add the following tile to Quick Settings"</string>
@@ -959,13 +970,13 @@
<string name="clipboard_text_copied" msgid="5100836834278976679">"Text copied"</string>
<string name="clipboard_image_copied" msgid="3793365360174328722">"Image copied"</string>
<string name="clipboard_content_copied" msgid="144452398567828145">"Content copied"</string>
- <string name="clipboard_editor" msgid="2971197550401892843">"Clipboard editor"</string>
+ <string name="clipboard_editor" msgid="2971197550401892843">"Clipboard Editor"</string>
<string name="clipboard_overlay_window_name" msgid="6450043652167357664">"Clipboard"</string>
<string name="clipboard_image_preview" msgid="2156475174343538128">"Image preview"</string>
<string name="clipboard_edit" msgid="4500155216174011640">"edit"</string>
<string name="add" msgid="81036585205287996">"Add"</string>
<string name="manage_users" msgid="1823875311934643849">"Manage users"</string>
- <string name="drag_split_not_supported" msgid="4326847447699729722">"This notification does not support dragging to Split screen."</string>
+ <string name="drag_split_not_supported" msgid="4326847447699729722">"This notification does not support dragging to Splitscreen."</string>
<string name="dream_overlay_status_bar_wifi_off" msgid="4497069245055003582">"Wi‑Fi unavailable"</string>
<string name="dream_overlay_status_bar_priority_mode" msgid="5428462123314728739">"Priority mode"</string>
<string name="dream_overlay_status_bar_alarm_set" msgid="566707328356590886">"Alarm set"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings_tv.xml b/packages/SystemUI/res/values-en-rCA/strings_tv.xml
index 09401c3..4dbabcf 100644
--- a/packages/SystemUI/res/values-en-rCA/strings_tv.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings_tv.xml
@@ -23,11 +23,11 @@
<string name="notification_vpn_disconnected" msgid="7150747626448044843">"VPN is disconnected"</string>
<string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"Via <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
<string name="tv_notification_panel_title" msgid="5311050946506276154">"Notifications"</string>
- <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No notifications"</string>
+ <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"No Notifications"</string>
<string name="mic_recording_announcement" msgid="7587123608060316575">"Microphone is recording"</string>
<string name="camera_recording_announcement" msgid="7240177719403759112">"Camera is recording"</string>
- <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and microphone are recording"</string>
+ <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"Camera and Microphone are recording"</string>
<string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"Microphone stopped recording"</string>
<string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"Camera stopped recording"</string>
- <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and microphone stopped recording"</string>
+ <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"Camera and Microphone stopped recording"</string>
</resources>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 19845f4..bfa63f6 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -125,7 +125,7 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR code scanner"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Unlock"</string>
+ <string name="accessibility_unlock_button" msgid="3613812140816244310">"Unlocked"</string>
<string name="accessibility_lock_icon" msgid="661492842417875775">"Device locked"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanning face"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -168,7 +168,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
@@ -180,8 +180,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Aeroplane mode"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN on."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> per cent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$s</xliff:g> percentage, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Battery charging, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> percent."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"See all notifications"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter enabled."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ringer vibrate."</string>
@@ -495,7 +500,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scan QR code"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR code scanner"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -734,6 +739,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Switch back to <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile data won\'t automatically switch based on availability"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No thanks"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yes, switch"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -924,6 +933,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporarily connected"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Poor connection"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 19845f4..bfa63f6 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -125,7 +125,7 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR code scanner"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Unlock"</string>
+ <string name="accessibility_unlock_button" msgid="3613812140816244310">"Unlocked"</string>
<string name="accessibility_lock_icon" msgid="661492842417875775">"Device locked"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanning face"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -168,7 +168,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognise face"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
@@ -180,8 +180,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Aeroplane mode"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN on."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> per cent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$s</xliff:g> percentage, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Battery charging, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> percent."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"See all notifications"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter enabled."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ringer vibrate."</string>
@@ -495,7 +500,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards. Please try again later."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scan QR code"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR code scanner"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Aeroplane mode"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -734,6 +739,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Switch back to <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile data won\'t automatically switch based on availability"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No thanks"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yes, switch"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -924,6 +933,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporarily connected"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Poor connection"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index b028bf3..5f0be61 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -125,7 +125,7 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR Code Scanner"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Unlock"</string>
+ <string name="accessibility_unlock_button" msgid="3613812140816244310">"Unlocked"</string>
<string name="accessibility_lock_icon" msgid="661492842417875775">"Device locked"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanning face"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -168,7 +168,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Can’t recognize face"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use fingerprint instead"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connected."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Battery percentage unknown."</string>
@@ -180,8 +180,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Airplane mode."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN on."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Battery <xliff:g id="NUMBER">%d</xliff:g> percent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Battery <xliff:g id="PERCENTAGE">%1$s</xliff:g> percent, about <xliff:g id="TIME">%2$s</xliff:g> left based on your usage"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Battery charging, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> percent."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"See all notifications"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter enabled."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ringer vibrate."</string>
@@ -495,7 +500,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Unlock to use"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"There was a problem getting your cards, please try again later"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lock screen settings"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scan QR code"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR code scanner"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Work profile"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"You won\'t hear your next alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -734,6 +739,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"You wont have access to data or the internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your carrier"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Switch back to <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile data wont automatically switch based on availability"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No thanks"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yes, switch"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -924,6 +933,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporarily connected"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Poor connection"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 6967379..639e50d 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asistente voz"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Billetera"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Escáner de código QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Escaneando rostro"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"No se reconoce el rostro"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa la huella dactilar"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Se desconoce el porcentaje de la batería."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo de avión"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN activada"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Batería <xliff:g id="NUMBER">%d</xliff:g> por ciento"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batería: <xliff:g id="PERCENTAGE">%1$s</xliff:g> por ciento; tiempo restante: aproximadamente <xliff:g id="TIME">%2$s</xliff:g> en función del uso"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batería cargando: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas las notificaciones"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletipo habilitado"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Timbre en vibración"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o transmitas una app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo el contenido que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una app"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"¿Quieres permitir que esta app comparta o grabe tu pantalla?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Cuando compartas, grabes o transmitas contenido, esta app podrá acceder a todo lo que sea visible en la pantalla o que reproduzcas en el dispositivo. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible que pueda estar visible."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Cuando compartas, grabes o transmitas una app, esta app podrá acceder a todo el contenido que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes y otra información sensible que pueda estar visible."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueada por tu administrador de TI"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La captura de pantalla está inhabilitada debido a la política del dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Administrar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Ocurrió un problema al obtener las tarjetas; vuelve a intentarlo más tarde"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuración de pantalla de bloqueo"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Escanear QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modo de avión"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"No oirás la próxima alarma a la(s) <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"¿Deseas desactivar los datos móviles?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"No tendrás acceso a datos móviles ni a Internet a través de <xliff:g id="CARRIER">%s</xliff:g>. Solo podrás conectarte a Internet mediante Wi‑Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"tu proveedor"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"¿Volver a cambiar a <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Los datos móviles no cambiarán automáticamente en función de la disponibilidad"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, gracias"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sí, cambiar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Como una app está bloqueando una solicitud de permiso, Configuración no puede verificar tu respuesta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Puede leer información sobre <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Datos móviles"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Conexión establecida"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Conectado temporalmente"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexión deficiente"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"No se conectarán automáticamente los datos móviles"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sin conexión"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hay otras redes disponibles"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index cbce6bf..9acbe6f 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asistente voz"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Escáner de códigos QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Escaneando cara"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"No se reconoce la cara"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa la huella digital"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentaje de batería desconocido."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo Avión"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"La red VPN está activada."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> por ciento de batería"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Queda un <xliff:g id="PERCENTAGE">%1$s</xliff:g> por ciento de batería (<xliff:g id="TIME">%2$s</xliff:g> aproximadamente según tu uso)"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batería cargándose (<xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%)."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas las notificaciones"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletipo habilitado"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Modo vibración"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cuando compartas, grabes o envíes una aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> podrá acceder a todo lo que muestre o reproduzca la aplicación. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir o grabar una aplicación"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"¿Permitir que esta aplicación que comparta o grabe contenido?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Cuando compartas, grabes o envíes contenido, esta aplicación podrá acceder a todo lo que se vea en tu pantalla o reproduzcas en tu dispositivo. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Cuando compartas, grabes o envíes contenido, esta aplicación podrá acceder a todo lo que se muestre o reproduzca en ella. Debes tener cuidado con contraseñas, detalles de pagos, mensajes o cualquier otra información sensible."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueadas por tu administrador de TI"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Las capturas de pantalla están inhabilitadas debido a la política de dispositivos"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Borrar todo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestionar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Se ha producido un problema al obtener tus tarjetas. Inténtalo de nuevo más tarde."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ajustes de pantalla de bloqueo"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Escanear código QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabajo"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modo Avión"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"No oirás la próxima alarma (<xliff:g id="WHEN">%1$s</xliff:g>)"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"¿Desactivar datos móviles?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"No tendrás acceso a datos móviles ni a Internet a través de <xliff:g id="CARRIER">%s</xliff:g>. Solo podrás conectarte a Internet mediante Wi‑Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"tu operador"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"¿Cambiar de nuevo a <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Los datos móviles no cambiarán automáticamente en función de la disponibilidad"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, gracias"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sí, cambiar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Una aplicación impide ver una solicitud de permiso, por lo que Ajustes no puede verificar tu respuesta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Puede leer información de <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Datos móviles"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Conectada temporalmente"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexión inestable"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Los datos móviles no se conectarán automáticamente"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sin conexión"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hay otras redes disponibles"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 44812de..55ace2f 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Häälabi"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-koodi skanner"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Luku avamine"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Seade on lukustatud"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Näo skannimine"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Saada"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Nägu ei õnnestu tuvastada"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Kasutage sõrmejälge"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth on ühendatud."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Aku laetuse protsent on teadmata."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Lennukirežiim."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN on sees."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Aku: <xliff:g id="NUMBER">%d</xliff:g> protsenti."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Aku protsent <xliff:g id="PERCENTAGE">%1$s</xliff:g>, teie kasutuse põhjal on jäänud ligikaudu <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Akut laetakse (<xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%)."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Kõikide märguannete kuvamine"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter lubatud."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibreeriv kõlisti."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kui jagate, salvestate või kannate rakendust üle, on rakendusel <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jätka"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Rakenduse jagamine või salvestamine"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Kas lubada sellel rakendusel jagada või salvestada?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kui jagate, salvestate või kannate üle, on selle rakendusel juurdepääs kõigele, mis on teie ekraanikuval nähtaval või mida teie seadmes esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kui jagate, salvestate või kannate rakendust üle, on sellel rakendusel juurdepääs kõigele, mida selles rakenduses kuvatakse või esitatakse. Seega olge paroolide, makseteabe, sõnumite ja muu tundliku teabega ettevaatlik."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokeeris teie IT-administraator"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekraanikuva jäädvustamine on seadmereeglitega keelatud"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tühjenda kõik"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Haldamine"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Ajalugu"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Avage kasutamiseks"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Teie kaartide hankimisel ilmnes probleem, proovige hiljem uuesti"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lukustuskuva seaded"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR-koodi skannimine"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Tööprofiil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Lennukirežiim"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Te ei kuule järgmist äratust kell <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Kas lülitada mobiilne andmeside välja?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Pärast seda pole teil operaatori <xliff:g id="CARRIER">%s</xliff:g> kaudu juurdepääsu andmesidele ega internetile. Internet on saadaval ainult WiFi kaudu."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"teie operaator"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Kas vahetada tagasi operaatorile <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiilandmeside operaatorit ei vahetata saadavuse alusel automaatselt"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Tänan, ei"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Jah, vaheta"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Seaded ei saa teie vastust kinnitada, sest rakendus varjab loataotlust."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Kas lubada rakendusel <xliff:g id="APP_0">%1$s</xliff:g> näidata rakenduse <xliff:g id="APP_2">%2$s</xliff:g> lõike?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- See saab lugeda teavet rakendusest <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiilne andmeside"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ühendatud"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ajutiselt ühendatud"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Kehv ühendus"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiilset andmesideühendust ei looda automaatselt"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Ühendus puudub"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ühtegi muud võrku pole saadaval"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 45b4cd6..89f1ebe 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Ahots-laguntza"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Diru-zorroa"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR kodeen eskanerra"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Desblokeatu"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Gailua blokeatuta dago"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Aurpegia eskaneatzen"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Bidali"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Ezin da ezagutu aurpegia"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Erabili hatz-marka"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetootha konektatuta."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Bateriaren ehunekoa ezezaguna da."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Hegaldi-modua"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN eginbidea aktibatuta."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Bateriaren karga: <xliff:g id="NUMBER">%d</xliff:g>."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateriak ehuneko <xliff:g id="PERCENTAGE">%1$s</xliff:g> dauka kargatuta. Zure erabilera kontuan izanda, <xliff:g id="TIME">%2$s</xliff:g> inguru gelditzen zaizkio."</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Kargatzen ari da bateria. Ehuneko <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> arte kargatu da oraingoz."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Ikusi jakinarazpen guztiak"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter gaituta."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Tonu-jotzailea dardara moduan."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Egin aurrera"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partekatu edo grabatu aplikazioak"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Edukia partekatzeko edo grabatzeko baimena eman nahi diozu aplikazioari?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Edukia partekatzen, grabatzen edo igortzen ari zarenean, pantailan ikusgai dagoen edo gailuan erreproduzitzen ari den guztirako sarbidea du aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Aplikazio bat partekatzen, grabatzen edo igortzen ari zarenean, aplikazio horretan ikusgai dagoen edo bertan erreproduzitzen ari den guztirako sarbidea du aplikazioak. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin edo bestelako kontuzko informazioarekin."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IKT saileko administratzaileak blokeatu du"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pantaila-kapturak egiteko aukera desgaituta dago, gailu-gidalerroei jarraikiz"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Garbitu guztiak"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Kudeatu"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desblokeatu erabiltzeko"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Arazo bat izan da txartelak eskuratzean. Saiatu berriro geroago."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Pantaila blokeatuaren ezarpenak"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Eskaneatu QR kodea"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Work profila"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Hegaldi modua"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Ez duzu entzungo hurrengo alarma (<xliff:g id="WHEN">%1$s</xliff:g>)"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Datu-konexioa desaktibatu nahi duzu?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> erabilita ezingo dituzu erabili datuak edo Internet. Wifi-sare baten bidez soilik konektatu ahal izango zara Internetera."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Zure operadorea"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> operadorera aldatu nahi duzu berriro?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Datu-konexioa ez da automatikoki aldatuko erabilgarritasunaren arabera"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ez, eskerrik asko"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Bai, aldatu nahi dut"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Aplikazio bat baimen-eskaera oztopatzen ari denez, ezarpenek ezin dute egiaztatu erantzuna."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakusteko baimena eman nahi diozu <xliff:g id="APP_0">%1$s</xliff:g> aplikazioari?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> aplikazioaren informazioa irakur dezake."</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Datu-konexioa"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> (<xliff:g id="NETWORKMODE">%2$s</xliff:g>)"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Konektatuta"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Aldi baterako konektatuta"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Konexio ahula"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Ez da automatikoki aktibatuko datu-konexioa"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Konexiorik gabe"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ez dago beste sare erabilgarririk"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index a504720..1a8553c 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"دستیار صوتی"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"کیف پول"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"کدخوان پاسخسریع"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"باز کردن قفل"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"دستگاه قفل است"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"درحال اسکن کردن چهره"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ارسال"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"چهره شناسایی نشد"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"از اثر انگشت استفاده کنید"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوتوث متصل است."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"درصد شارژ باتری مشخص نیست."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"حالت هواپیما."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN روشن است."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"باتری <xliff:g id="NUMBER">%d</xliff:g> درصد."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"باتری <xliff:g id="PERCENTAGE">%1$s</xliff:g> درصد شارژ دارد، براساس مصرفتان تقریباً <xliff:g id="TIME">%2$s</xliff:g> شارژ باقیمانده است"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"در حال شارژ باتری، <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> درصد"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"دیدن همه اعلانها"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"تلهتایپ فعال شد."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"زنگ لرزشی."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"وقتی درحال همرسانی، ضبط، یا پخش محتوای برنامهای هستید، <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> به همه محتوایی که در آن برنامه نمایان است یا پخش میشود دسترسی دارد. بنابراین مراقب گذرواژهها، جزئیات پرداخت، پیامها، یا دیگر اطلاعات حساس باشید."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ادامه"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"همرسانی یا ضبط برنامه"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"به این برنامه اجازه همرسانی یا ضبط میدهید؟"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"وقتی درحال همرسانی، ضبط، یا پخش محتوا هستید، این برنامه به همه محتوایی که در صفحهتان نمایان است یا در دستگاهتان پخش میشود دسترسی دارد. بنابراین مراقب گذرواژهها، جزئیات پرداخت، پیامها، یا دیگر اطلاعات حساس باشید."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"وقتی درحال همرسانی، ضبط، یا پخش محتوای برنامهای هستید، این برنامه به همه محتوایی که در آن برنامه نمایان است یا پخش میشود دسترسی دارد. بنابراین مراقب گذرواژهها، جزئیات پرداخت، پیامها، یا دیگر اطلاعات حساس باشید."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"سرپرست فناوری اطلاعات آن را مسدود کرده است"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"«ضبط صفحهنمایش» بهدلیل خطمشی دستگاه غیرفعال است"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"مدیریت"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"سابقه"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"برای استفاده، قفل را باز کنید"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"هنگام دریافت کارتها مشکلی پیش آمد، لطفاً بعداً دوباره امتحان کنید"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"تنظیمات صفحه قفل"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"اسکن رمزینه پاسخسریع"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"نمایه کاری"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"حالت هواپیما"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"در ساعت <xliff:g id="WHEN">%1$s</xliff:g>، دیگر صدای زنگ ساعت را نمیشنوید"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"داده تلفن همراه خاموش شود؟"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"نمیتوانید ازطریق <xliff:g id="CARRIER">%s</xliff:g> به داده یا اینترنت دسترسی داشته باشید. اینترنت فقط ازطریق Wi-Fi در دسترس خواهد بود."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"شرکت مخابراتی شما"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"میخواهید به <xliff:g id="CARRIER">%s</xliff:g> برگردید؟"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"وضعیت داده تلفن همراه بهطور خودکار براساس دردسترس بودن تغییر نخواهد کرد"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"نه متشکرم"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"بله، عوض شود"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"چون برنامهای درحال ایجاد تداخل در درخواست مجوز است، «تنظیمات» نمیتواند پاسخ شما را تأیید کند."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"به <xliff:g id="APP_0">%1$s</xliff:g> اجازه داده شود تکههای <xliff:g id="APP_2">%2$s</xliff:g> را نشان دهد؟"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- میتواند اطلاعات <xliff:g id="APP">%1$s</xliff:g> را بخواند"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"داده تلفن همراه"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"متصل است"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"موقتاً متصل است"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"اتصال ضعیف"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"داده تلفن همراه بهطور خودکار متصل نخواهد شد"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"اتصال برقرار نیست"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"شبکه دیگری وجود ندارد"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index ba8bfe0..12e9797 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Ääniapuri"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-koodiskanneri"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Avaa lukitus"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Laite lukittu"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Kasvojen skannaus"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Lähetä"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Kasvoja ei voi tunnistaa"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Käytä sormenjälkeä"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth yhdistetty."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akun varaustaso ei tiedossa."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Lentokonetila."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN päällä"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Akun virta <xliff:g id="NUMBER">%d</xliff:g> prosenttia."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Akkua jäljellä <xliff:g id="PERCENTAGE">%1$s</xliff:g> prosenttia eli noin <xliff:g id="TIME">%2$s</xliff:g> käyttösi perusteella"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Akku latautuu: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> prosenttia"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Näytä kaikki ilmoitukset"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Tekstipuhelin käytössä."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Soittoääni: värinä."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kun jaat, tallennat tai striimaat sovellusta, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Jatka"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Jaa sovellus tai tallenna sen sisältöä"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Sallitko sovelluksen jakaa tai tallentaa sisältöä?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kun jaat, tallennat tai striimaat sisältöä, sovellus saa pääsyn kaikkeen näytölläsi näkyvään tai laitteellasi toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kun jaat, tallennat tai striimaat sovellusta, tämä sovellus saa pääsyn kaikkeen sovelluksessa näkyvään tai toistettuun sisältöön. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä tai muita arkaluontoisia tietoja."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT-järjestelmänvalvojasi estämä"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Kuvakaappaus on poistettu käytöstä laitekäytännön perusteella"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tyhjennä kaikki"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Muuta asetuksia"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Avaa lukitus ja käytä"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Korttien noutamisessa oli ongelma, yritä myöhemmin uudelleen"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lukitusnäytön asetukset"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skannaa QR-koodi"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Työprofiili"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Lentokonetila"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Et kuule seuraavaa hälytystäsi (<xliff:g id="WHEN">%1$s</xliff:g>)."</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Laitetaanko mobiilidata pois päältä?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> ei enää tarjoa pääsyä dataan eikä internetyhteyttä, joka on saatavilla vain Wi-Fin kautta."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operaattorisi"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Palauta käyttöön <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiilidata ei vaihdu automaattisesti saatavuuden perusteella"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ei kiitos"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Kyllä, vaihda"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Sovellus peittää käyttöoikeuspyynnön, joten Asetukset ei voi vahvistaa valintaasi."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Saako <xliff:g id="APP_0">%1$s</xliff:g> näyttää osia sovelluksesta <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Se voi lukea tietoja sovelluksesta <xliff:g id="APP">%1$s</xliff:g>."</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiilidata"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Yhdistetty"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Väliaikaisesti yhdistetty"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Heikko yhteys"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiilidata ei yhdisty automaattisesti"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Ei yhteyttä"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ei muita verkkoja 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 8b183bf..99e8c7f 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistance vocale"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Portefeuille"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Lecteur de code QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Déverrouiller"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Appareil verrouillé"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Numérisation du visage"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Envoyer"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Visage non reconnu"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utiliser l\'empreinte digitale"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pourcentage de la pile inconnu."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mode Avion"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"RPV activé."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Pile : <xliff:g id="NUMBER">%d</xliff:g> pour cent"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Pile chargée à <xliff:g id="PERCENTAGE">%1$s</xliff:g> pour cent (environ <xliff:g id="TIME">%2$s</xliff:g> d\'autonomie en fonction de votre usage)"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Pile en charge : <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Afficher toutes les notifications"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Téléscripteur activé"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Sonnerie en mode vibreur"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lorsque vous partagez, enregistrez ou diffusez une application, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est affiché ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages ou toute autre information confidentielle."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuer"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partager ou enregistrer une application"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Autoriser cette application à partager ou à enregistrer?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Lorsque vous partagez, enregistrez ou diffusez, cette application a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages ou toute autre information confidentielle."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Lorsque vous partagez, enregistrez ou diffusez une application, cette application a accès à tout ce qui est affiché ou lu sur cette application. Par conséquent, soyez prudent avec les mots de passe, les détails du paiement, les messages ou toute autre information confidentielle."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquée par votre administrateur informatique"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La fonctionnalité de capture d\'écran est désactivée par l\'application Device Policy"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Déverrouiller pour utiliser"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Un problème est survenu lors de la récupération de vos cartes, veuillez réessayer plus tard"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Paramètres de l\'écran de verrouillage"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Numériser le code QR"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"lecteur de code QR"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Vous n\'entendrez pas votre prochaine alarme à <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Désactiver les données cellulaires?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Vous n\'aurez pas accès aux données ni à Internet avec <xliff:g id="CARRIER">%s</xliff:g>. Vous ne pourrez accéder à Internet que par Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"votre fournisseur de services"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Rebasculer vers <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Il n\'y aura pas de basculement automatique entre les données mobiles selon la disponibilité"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Non merci"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Oui, basculer"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Une application obscurcit une demande d\'autorisation, alors Paramètres ne peut pas vérifier votre réponse."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher <xliff:g id="APP_2">%2$s</xliff:g> tranches?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Il peut lire de l\'information de <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Données cellulaires"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connexion active"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connectée temporairement"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connexion faible"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Aucune connexion auto. des données cellulaires"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Aucune connexion"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Aucun autre réseau n\'est accessible"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index f5bcfb3..68fae12 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistance vocale"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Lecteur de code QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Déverrouiller"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Appareil verrouillé"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Analyse du visage en cours"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Envoyer"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Visage non reconnu"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Utilisez empreinte digit."</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth connecté"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pourcentage de la batterie inconnu."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mode Avion"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"Le VPN est activé."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Batterie : <xliff:g id="NUMBER">%d</xliff:g> pour cent"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batterie chargée à <xliff:g id="PERCENTAGE">%1$s</xliff:g> pour cent : il reste environ <xliff:g id="TIME">%2$s</xliff:g> d\'autonomie, selon votre utilisation"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batterie en charge : <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Afficher toutes les notifications"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Téléscripteur activé"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Sonnerie en mode vibreur"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lorsque vous partagez, enregistrez ou castez une appli, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuer"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partager ou enregistrer une appli"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Autoriser cette appli à partager ou enregistrer ?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Lorsque vous partagez, enregistrez ou castez, cette appli a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Lorsque vous partagez, enregistrez ou castez une appli, cette appli a accès à tout ce qui est visible sur votre écran ou lu sur votre appareil. Faites donc attention à vos mots de passe, détails de mode de paiement, messages ou autres informations sensibles."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloquée par votre administrateur informatique"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"La capture d\'écran est désactivée conformément aux règles relatives à l\'appareil"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tout effacer"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gérer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historique"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Déverrouiller pour utiliser"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Problème de récupération de vos cartes. Réessayez plus tard"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Paramètres de l\'écran de verrouillage"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scanner un code QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Profil professionnel"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mode Avion"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Vous n\'entendrez pas votre prochaine alarme <xliff:g id="WHEN">%1$s</xliff:g>."</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Désactiver les données mobiles ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Vous n\'aurez pas accès aux données mobiles ni à Internet via <xliff:g id="CARRIER">%s</xliff:g>. Vous ne pourrez accéder à Internet que par Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"votre opérateur"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Rebasculer sur <xliff:g id="CARRIER">%s</xliff:g> ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Il n\'y aura pas de basculement automatique entre les données mobile selon la disponibilité"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Non, merci"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Oui, revenir"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"L\'application Paramètres ne peut pas valider votre réponse, car une application masque la demande d\'autorisation."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher des éléments de <xliff:g id="APP_2">%2$s</xliff:g> ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Accès aux informations de <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Données mobiles"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connecté"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connectée temporairement"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connexion médiocre"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Pas de connexion automatique des données mobiles"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Aucune connexion"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Aucun autre réseau disponible"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 4b1809be..fd554ba 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asistente de voz"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Escáner de código QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Analizando cara"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Non se recoñeceu a cara"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa a impresión dixital"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Descoñécese a porcentaxe da batería."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo avión"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"A VPN está activada."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Carga da batería: <xliff:g id="NUMBER">%d</xliff:g> por cento."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batería: <xliff:g id="PERCENTAGE">%1$s</xliff:g> por cento, durará <xliff:g id="TIME">%2$s</xliff:g> co uso que adoitas darlle"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batería cargando. Nivel: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> por cento."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas as notificacións"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletipo activado"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Timbre en vibración"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Cando compartes, gravas ou emites unha aplicación, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ten acceso a todo o que se vexa ou se reproduza nela. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartir ou gravar unha aplicación"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Queres permitir que esta aplicación comparta ou grave contido?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Cando compartes, gravas ou emites contido, esta aplicación ten acceso a todo o que se vexa na pantalla ou se reproduza no teu dispositivo. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Cando compartes, gravas ou emites aplicacións, esta aplicación ten acceso a todo o que se vexa ou se reproduza nelas. Polo tanto, debes ter coidado cos contrasinais, os detalles de pago, as mensaxes ou outra información confidencial."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"O teu administrador de TI bloqueou esta aplicación"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A política do dispositivo desactivou a opción de capturar a pantalla"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Eliminar todas"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Xestionar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Produciuse un problema ao obter as tarxetas. Téntao de novo máis tarde"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configuración da pantalla de bloqueo"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Escanear código QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de traballo"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modo avión"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Non escoitarás a alarma seguinte <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Queres desactivar os datos móbiles?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Non terás acceso aos datos nin a Internet a través de <xliff:g id="CARRIER">%s</xliff:g>. Internet só estará dispoñible mediante a wifi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"o teu operador"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Seguro que queres cambiar de novo a <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"O uso de datos móbiles non cambiará automaticamente en función da dispoñibilidade"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Non, grazas"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Si, cambiar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Dado que unha aplicación se superpón sobre unha solicitude de permiso, a configuración non pode verificar a túa resposta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Queres permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre fragmentos de aplicación de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler información da aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Datos móbiles"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Conectada"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Conectada temporalmente"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexión deficiente"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Os datos móbiles non se conectarán automaticamente"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sen conexión"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Non hai outras redes dispoñibles"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 8174ce7..72c535b 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"વૉઇસ સહાય"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR કોડ સ્કૅનર"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"અનલૉક કરો"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"ડિવાઇસ લૉક કરેલું છે"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"ચહેરો સ્કૅન કરવો"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"મોકલો"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ચહેરો ઓળખાતો નથી"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"તો ફિંગરપ્રિન્ટ વાપરો"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"બ્લૂટૂથ કનેક્ટ થયું."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"બૅટરીની ટકાવારી અજાણ છે."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"એરપ્લેન મોડ."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ચાલુ છે."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"બૅટરી <xliff:g id="NUMBER">%d</xliff:g> ટકા."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"તમારા વપરાશના આધારે બૅટરી <xliff:g id="PERCENTAGE">%1$s</xliff:g> ટકા, જે લગભગ <xliff:g id="TIME">%2$s</xliff:g> સુધી ચાલે તેટલી બચી છે"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"બૅટરી ચાર્જ થઈ રહી છે, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"બધી સૂચના જુઓ"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"ટેલિટાઇપરાઇટર સક્ષમ કર્યું."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"રિંગર વાઇબ્રેટ."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"જ્યારે તમે કોઈ ઍપ શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તે ઍપ પર બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી કોઈપણ વસ્તુનો ઍક્સેસ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ધરાવે છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ અથવા અન્ય સંવેદનશીલ માહિતીની બાબતે સાવચેત રહેશો."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ચાલુ રાખો"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"કોઈ ઍપ શેર કરો અથવા રેકોર્ડ કરો"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"આ ઍપને શેર કે રેકોર્ડ કરવાની મંજૂરી આપીએ?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"જ્યારે તમે શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે આ ઍપ પાસે તમારી સ્ક્રીન પર જોઈ શકાતી કે તમારા ડિવાઇસ પર ચલાવવામાં આવતી બધી વસ્તુઓનો ઍક્સેસ હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ અથવા અન્ય સંવેદનશીલ માહિતીની બાબતે સાવચેત રહેશો."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"જ્યારે તમે કોઈ ઍપને શેર, રેકોર્ડ અથવા કાસ્ટ કરી રહ્યાં હો, ત્યારે તેના પર બતાવવામાં કે ચલાવવામાં આવતી બધી વસ્તુઓનો ઍક્સેસ આ ઍપ પાસે હોય છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ અથવા અન્ય સંવેદનશીલ માહિતીની બાબતે સાવચેત રહેશો."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"તમારા IT ઍડમિન દ્વારા બ્લૉક કરાયેલી"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ડિવાઇસ પૉલિસી અનુસાર સ્ક્રીન કૅપ્ચર કરવાની સુવિધા બંધ કરવામાં આવી છે"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"મેનેજ કરો"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ઇતિહાસ"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ઉપયોગ કરવા માટે અનલૉક કરો"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"તમારા કાર્ડની માહિતી મેળવવામાં સમસ્યા આવી હતી, કૃપા કરીને થોડા સમય પછી ફરી પ્રયાસ કરો"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"લૉક સ્ક્રીનના સેટિંગ"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR કોડ સ્કૅન કરો"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"ઑફિસની પ્રોફાઇલ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"એરપ્લેન મોડ"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"તમે <xliff:g id="WHEN">%1$s</xliff:g> એ તમારો આગલો એલાર્મ સાંભળશો નહીં"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"મોબાઇલ ડેટા બંધ કરીએ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"તમને <xliff:g id="CARRIER">%s</xliff:g> મારફતે ડેટા અથવા ઇન્ટરનેટનો ઍક્સેસ મળશે નહીં. ઇન્ટરનેટ માત્ર વાઇ-ફાઇ દ્વારા ઉપલબ્ધ થશે."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"તમારા કૅરિઅર"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> પર પાછા સ્વિચ કરીએ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"મોબાઇલ ડેટાને ઉપલબ્ધતાના આધારે ઑટોમૅટિક રીતે સ્વિચ કરવામાં આવશે નહીં"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ના, આભાર"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"હા, સ્વિચ કરો"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"કોઈ ઍપ પરવાનગી વિનંતીને અસ્પષ્ટ કરતી હોવાને કારણે, સેટિંગ તમારા પ્રતિસાદને ચકાસી શકતું નથી."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>ને <xliff:g id="APP_2">%2$s</xliff:g> સ્લાઇસ બતાવવાની મંજૂરી આપીએ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- મારાથી <xliff:g id="APP">%1$s</xliff:g>ની માહિતી વાંચી શકાતી નથી"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"મોબાઇલ ડેટા"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"કનેક્ટ કરેલું"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"હંગામી રીતે કનેક્ટ કર્યું"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"નબળું કનેક્શન"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"મોબાઇલ ડેટા ઑટોમૅટિક રીતે કનેક્ટ થશે નહીં"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"કોઈ કનેક્શન નથી"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"બીજાં કોઈ નેટવર્ક ઉપલબ્ધ નથી"</string>
diff --git a/packages/SystemUI/res/values-h700dp/dimens.xml b/packages/SystemUI/res/values-h700dp/dimens.xml
index fbd985e..055308f 100644
--- a/packages/SystemUI/res/values-h700dp/dimens.xml
+++ b/packages/SystemUI/res/values-h700dp/dimens.xml
@@ -1,5 +1,5 @@
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ 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.
@@ -15,6 +15,6 @@
-->
<resources>
- <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
- <dimen name="large_clock_text_size">170dp</dimen>
-</resources>
+ <!-- Margin above the ambient indication container -->
+ <dimen name="ambient_indication_container_margin_top">15dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 94fe209..3a71994 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -15,9 +15,9 @@
-->
<resources>
- <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
- <dimen name="large_clock_text_size">200dp</dimen>
-
<!-- With the large clock, move up slightly from the center -->
<dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
+
+ <!-- Margin above the ambient indication container -->
+ <dimen name="ambient_indication_container_margin_top">20dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 862ef34..a49c8d0 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"आवाज़ से डिवाइस का इस्तेमाल"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"क्यूआर कोड स्कैनर"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"अनलॉक करें"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"डिवाइस लॉक है"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"डिवाइस अनलॉक करने के लिए चेहरा स्कैन किया जाता है"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"भेजें"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"चेहरे की पहचान नहीं हुई"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"फ़िंगरप्रिंट इस्तेमाल करें"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्ट किया गया."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"इस बारे में जानकारी नहीं है कि अभी बैटरी कितने प्रतिशत चार्ज है."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"हवाई जहाज़ मोड."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN चालू."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> प्रतिशत बैटरी."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> प्रतिशत बैटरी बची है और आपके इस्तेमाल के हिसाब से यह <xliff:g id="TIME">%2$s</xliff:g> में खत्म हो जाएगी"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"बैटरी चार्ज हो रही है, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> प्रतिशत."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"पूरी सूचनाएं देखें"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"टेलीटाइपराइटर सक्षम."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"रिंगर कंपन (वाइब्रेशन)."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"शेयर, रिकॉर्ड या कास्ट करते समय, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> के पास उस ऐप्लिकेशन पर दिख रही हर चीज़ या उस पर चल रहे हर मीडिया का ऐक्सेस होता है. इसलिए, शेयर, रिकॉर्ड या कास्ट करते समय, पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या किसी और संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी रखें"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ऐप्लिकेशन शेयर करें या उसकी रिकॉर्डिंग करें"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"क्या इस ऐप्लिकेशन को शेयर या रिकॉर्ड करने की अनुमति देनी है?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"शेयर, रिकॉर्ड या कास्ट करते समय, इस ऐप्लिकेशन के पास स्क्रीन पर दिख रही हर चीज़ या डिवाइस पर चल रहे सभी मीडिया का ऐक्सेस होता है. इसलिए, इस दौरान पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या दूसरी संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"किसी ऐप्लिकेशन को शेयर, रिकॉर्ड या कास्ट करते समय, इस ऐप्लिकेशन के पास उस ऐप्लिकेशन पर दिख रही हर चीज़ या उस पर चल रहे सभी मीडिया का ऐक्सेस होता है. इसलिए, इस दौरान पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज या दूसरी संवेदनशील जानकारी को लेकर खास सावधानी बरतें."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"आपके आईटी एडमिन ने स्क्रीन कैप्चर करने की सुविधा पर रोक लगाई है"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिवाइस से जुड़ी नीति के तहत स्क्रीन कैप्चर करने की सुविधा बंद है"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सभी को हटाएं"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"मैनेज करें"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"इस्तेमाल करने के लिए, डिवाइस अनलॉक करें"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"आपके कार्ड की जानकारी पाने में कोई समस्या हुई है. कृपया बाद में कोशिश करें"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लॉक स्क्रीन की सेटिंग"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"क्यूआर कोड स्कैन करें"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"वर्क प्रोफ़ाइल"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"हवाई जहाज़ मोड"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"आपको <xliff:g id="WHEN">%1$s</xliff:g> पर अपना अगला अलार्म नहीं सुनाई देगा"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा बंद करना चाहते हैं?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"आप <xliff:g id="CARRIER">%s</xliff:g> से डेटा या इंटरनेट का इस्तेमाल नहीं कर पाएंगे. इंटरनेट सिर्फ़ वाई-फ़ाई से चलेगा."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"आपको मोबाइल और इंटरनेट सेवा देने वाली कंपनी"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"क्या आपको मोबाइल डेटा, <xliff:g id="CARRIER">%s</xliff:g> पर वापस से स्विच करना है?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"उपलब्ध होने पर, मोबाइल डेटा अपने-आप स्विच नहीं होगा"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"स्विच न करें"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"स्विच करें"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ऐप की वजह से मंज़ूरी के अनुरोध को समझने में दिक्कत हो रही है, इसलिए सेटिंग से आपके जवाब की पुष्टि नहीं हो पा रही है."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> को <xliff:g id="APP_2">%2$s</xliff:g> के हिस्से (स्लाइस) दिखाने की मंज़ूरी दें?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- यह <xliff:g id="APP">%1$s</xliff:g> से सूचना पढ़ सकता है"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"कनेक्ट हो गया"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"इंटरनेट कनेक्शन कुछ समय के लिए है"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"इंटरनेट कनेक्शन खराब है"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा अपने-आप कनेक्ट नहीं होगा"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"इंटरनेट कनेक्शन नहीं है"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"कोई दूसरा नेटवर्क उपलब्ध नहीं है"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index d7fe7d8..71ba52c9 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Glasovna pomoć"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Čitač QR koda"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Otključavanje"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Uređaj je zaključan"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Skeniranje lica"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Pošalji"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Lice nije prepoznato"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Upotrijebite otisak prsta"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth povezan."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Postotak baterije nije poznat."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Način rada u zrakoplovu"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN uključen."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Baterija <xliff:g id="NUMBER">%d</xliff:g> posto."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterija je na <xliff:g id="PERCENTAGE">%1$s</xliff:g> posto, još otprilike <xliff:g id="TIME">%2$s</xliff:g> na temelju vaše upotrebe"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Baterija se puni, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> posto."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Pogledajte sve obavijesti"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter je omogućen."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibracija softvera zvona."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kad dijelite, snimate ili emitirate aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Nastavi"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dijeljenje ili snimanje pomoću aplikacije"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Želite li ovoj aplikaciji omogućiti dijeljenje ili bilježenje?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kad dijelite, snimate ili emitirate, ova aplikacija ima pristup svemu što je vidljivo na vašem zaslonu ili se reproducira na vašem uređaju. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kad dijelite, snimate ili emitirate aplikaciju, ova aplikacija ima pristup svemu što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na zaporke, podatke o plaćanju, poruke i druge osjetljive podatke."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokirao vaš IT administrator"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snimanje zaslona onemogućeno je u skladu s pravilima za uređaje"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši sve"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Povijest"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Otključajte da biste koristili"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Pojavio se problem prilikom dohvaćanja kartica, pokušajte ponovo kasnije"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Postavke zaključanog zaslona"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skeniraj QR kôd"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"Čitač QR koda"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Poslovni profil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Način rada u zrakoplovu"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nećete čuti sljedeći alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Isključiti mobilne podatke?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup mobilnim podacima ili internetu putem operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo putem Wi-Fija."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vaš mobilni operater"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vratiti se na mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilni podaci neće se automatski prebaciti na temelju dostupnosti"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, prebaci"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Budući da aplikacija prekriva zahtjev za dopuštenje, Postavke ne mogu potvrditi vaš odgovor."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Želite li dopustiti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– može čitati informacije aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilni podaci"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Privremeno povezano"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slaba veza"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna veza neće se automatski uspostaviti"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Niste povezani"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nije dostupna nijedna druga mreža"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 51089e7..d9c438c 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Hangsegéd"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-kód-szkennelő"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Feloldás"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Az eszköz zárolva van"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Arc keresése"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Küldés"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Az arc nem ismerhető fel"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Használjon ujjlenyomatot"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth csatlakoztatva."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Az akkumulátor töltöttségi szintje ismeretlen."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Repülőgép üzemmód."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN bekapcsolva."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Akkumulátor <xliff:g id="NUMBER">%d</xliff:g> százalék."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Az akkumulátor <xliff:g id="PERCENTAGE">%1$s</xliff:g> százalékon áll, a használati adatok alapján körülbelül <xliff:g id="TIME">%2$s</xliff:g> múlva merül le"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Tölt az akkumulátor, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> százalék."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Összes értesítés megtekintése"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter engedélyezve."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Csengő rezeg."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Amikor Ön megoszt, rögzít vagy átküld egy alkalmazást, a(z) <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> az adott appban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel és más bizalmas információkkal."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Folytatás"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Alkalmazás megosztása és rögzítése"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Engedélyezi ennek az alkalmazásnak a megosztást és a rögzítést?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Amikor Ön megosztást, rögzítést vagy átküldést végez, az alkalmazás a képernyőn látható vagy az eszközön lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel és más bizalmas információkkal."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Amikor Ön megoszt, rögzít vagy átküld egy alkalmazást, az alkalmazás az adott appban látható vagy lejátszott minden tartalomhoz hozzáfér. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel és más bizalmas információkkal."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Rendszergazda által letiltva"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A képernyőfelvételt eszközszabályzat tiltja"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Az összes törlése"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Kezelés"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Előzmények"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Oldja fel a használathoz"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Probléma merült fel a kártyák lekérésekor, próbálja újra később"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Lezárási képernyő beállításai"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR-kód beolvasása"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Munkahelyi profil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Repülős üzemmód"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nem fogja hallani az ébresztést ekkor: <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Kikapcsolja a mobiladatokat?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nem lesz adat-, illetve internet-hozzáférése a <xliff:g id="CARRIER">%s</xliff:g> szolgáltatón keresztül. Az internethez csak Wi-Fi-n keresztül csatlakozhat."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"saját mobilszolgáltató"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Átvált a következőre: <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Nem kerül sor a mobiladat-forgalom automatikus átváltására a rendelkezésre állás alapján"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Most nem"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Igen, átváltok"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Mivel az egyik alkalmazás eltakarja az engedélykérést, a Beállítások alkalmazás nem tudja ellenőrizni az Ön válaszát."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Engedélyezi a(z) <xliff:g id="APP_0">%1$s</xliff:g> alkalmazásnak, hogy részleteket mutasson a(z) <xliff:g id="APP_2">%2$s</xliff:g> alkalmazásból?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Információkat olvashat a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásból"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiladat"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="NETWORKMODE">%2$s</xliff:g>/<xliff:g id="STATE">%1$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Csatlakozva"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ideiglenesen csatlakoztatva"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Gyenge kapcsolat"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Nincs automatikus mobiladat-kapcsolat"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nincs kapcsolat"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nincs több rendelkezésre álló hálózat"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index f1d11fb..820b666 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Ձայնային հուշումներ"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR կոդերի սկաներ"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Ապակողպել"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Սարքը կողպված է"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Դեմքի սկանավորում"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Ուղարկել"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Դեմքը չի ճանաչվել"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Օգտագործեք մատնահետք"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-ը միացված է:"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Մարտկոցի լիցքի մակարդակն անհայտ է։"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Ավիառեժիմ"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"Միացնել VPN-ը։"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Մարտկոցը <xliff:g id="NUMBER">%d</xliff:g> տոկոս է:"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Մարտկոցի լիցքը <xliff:g id="PERCENTAGE">%1$s</xliff:g> տոկոս է և կբավարարի մոտ <xliff:g id="TIME">%2$s</xliff:g>՝ կախված օգտագործման եղանակից:"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Մարտկոցը լիցքավորվում է: Լիցքը <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> տոկոս է:"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Տեսնել բոլոր ծանուցումները"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Հեռատիպը միացված է:"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Թրթռազանգ:"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> հավելվածին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Շարունակել"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Հավելվածի էկրանի ցուցադրում կամ տեսագրում"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Թույլատրե՞լ այս հավելվածին ցուցադրել կամ տեսագրել էկրանը"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք էկրանը, այս հավելվածին հասանելի է դառնում այն ամենը, ինչ տեսանելի է էկրանին և նվագարկվում է ձեր սարքում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Երբ դուք ցուցադրում, տեսագրում կամ հեռարձակում եք որևէ հավելվածի էկրանը, այս հավելվածին հասանելի է դառնում այն ամենը, ինչ ցուցադրվում է կամ նվագարկվում այդ հավելվածում։ Հիշեք այդ մասին, երբ պատրաստվում եք դիտել կամ մուտքագրել գաղտնաբառեր, վճարային տվյալներ, հաղորդագրություններ և այլ կոնֆիդենցիալ տեղեկություններ։"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Արգելափակվել է ձեր ՏՏ ադմինիստրատորի կողմից"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Էկրանի տեսագրումն անջատված է սարքի կանոնների համաձայն"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Մաքրել բոլորը"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Կառավարել"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Պատմություն"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ապակողպել՝ օգտագործելու համար"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Չհաջողվեց բեռնել քարտերը։ Նորից փորձեք։"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Կողպէկրանի կարգավորումներ"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Սկանավորել QR կոդը"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Android for Work-ի պրոֆիլ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Ավիառեժիմ"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Ժամը <xliff:g id="WHEN">%1$s</xliff:g>-ի զարթուցիչը չի զանգի"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Անջատե՞լ բջջային ինտերնետը"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> օպերատորի բջջային ինտերնետը հասանելի չի լինի: Համացանցից կկարողանաք օգտվել միայն Wi-Fi-ի միջոցով:"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Ձեր"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Անցնե՞լ <xliff:g id="CARRIER">%s</xliff:g> ցանցին"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Սարքն ավտոմատ չի անցնի բջջային ինտերնետին"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ոչ, շնորհակալություն"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Այո"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Քանի որ ներածումն արգելափակված է ինչ-որ հավելվածի կողմից, Կարգավորումները չեն կարող հաստատել ձեր պատասխանը:"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Թույլատրե՞լ <xliff:g id="APP_0">%1$s</xliff:g> հավելվածին ցուցադրել հատվածներ <xliff:g id="APP_2">%2$s</xliff:g> հավելվածից"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Կարող է կարդալ տեղեկություններ <xliff:g id="APP">%1$s</xliff:g> հավելվածից"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Բջջային ինտերնետ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Միացած է"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ժամանակավոր կապ"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Թույլ կապ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Բջջային ինտերնետն ավտոմատ չի միանա"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Կապ չկա"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Այլ հասանելի ցանցեր չկան"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index ac5abde..a5ff4915 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Bantuan Suara"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Pemindai Kode QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Buka kunci"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Perangkat terkunci"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Memindai wajah"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Kirim"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Tidak mengenali wajah"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gunakan sidik jari"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth terhubung."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Persentase baterai tidak diketahui."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mode pesawat."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN aktif."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Baterai <xliff:g id="NUMBER">%d</xliff:g> persen."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Baterai <xliff:g id="PERCENTAGE">%1$s</xliff:g> persen, sekitar <xliff:g id="TIME">%2$s</xliff:g> lagi berdasarkan penggunaan Anda"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Mengisi daya baterai, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> persen."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Lihat semua notifikasi"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter diaktifkan."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Pendering bergetar."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Lanjutkan"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Bagikan atau rekam aplikasi"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Izinkan aplikasi ini membagikan atau merekam?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Jika Anda membagikan, merekam, atau mentransmisikan, aplikasi ini akan memiliki akses ke semua hal yang ditampilkan di layar atau yang diputar di perangkat Anda. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Jika Anda membagikan, merekam, atau mentransmisikan suatu aplikasi, aplikasi ini akan memiliki akses ke semua hal yang ditampilkan atau yang diputar di aplikasi tersebut. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, atau informasi sensitif lainnya."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Diblokir oleh admin IT Anda"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Pengambilan screenshot dinonaktifkan oleh kebijakan perangkat"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hapus semua"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Kelola"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histori"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Buka kunci untuk menggunakan"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Terjadi error saat mendapatkan kartu Anda, coba lagi nanti"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setelan layar kunci"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Pindai kode QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mode pesawat"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Anda tidak akan mendengar alarm berikutnya <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Nonaktifkan data seluler?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Anda tidak akan dapat mengakses data atau internet melalui <xliff:g id="CARRIER">%s</xliff:g>. Internet hanya akan tersedia melalui Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Operator Seluler Anda"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Beralih kembali ke <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Data seluler tidak akan dialihkan secara otomatis berdasarkan ketersediaan"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Lain kali"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ya, alihkan"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Karena sebuah aplikasi menghalangi permintaan izin, Setelan tidak dapat memverifikasi respons Anda."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Izinkan <xliff:g id="APP_0">%1$s</xliff:g> menampilkan potongan <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Dapat membaca informasi dari <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Data seluler"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Terhubung"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Terhubung sementara"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Koneksi buruk"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Data seluler tidak akan terhubung otomatis"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Tidak ada koneksi"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Jaringan lain tidak tersedia"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 40c7d90..bc84dc9 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Raddaðstoð"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Veski"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-kóðaskanni"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Taka úr lás"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Tækið er læst"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Andlit skannað"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Senda"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Andlit þekkist ekki"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Nota fingrafar í staðinn"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth tengt."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Staða rafhlöðu óþekkt."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Flugstilling"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"Kveikt á VPN."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> prósent á rafhlöðu."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Rafhlaða í <xliff:g id="PERCENTAGE">%1$s</xliff:g> prósentum, um það bil <xliff:g id="TIME">%2$s</xliff:g> eftir miðað við notkun þína"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Rafhlaða í hleðslu, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Sjá allar tilkynningar"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Fjarriti virkur."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Titrar við hringingu."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Þegar þú deilir, tekur upp eða sendir út forrit hefur <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> aðgang að öllu sem sést eða spilast í viðkomandi forriti. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Áfram"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deila eða taka upp forrit"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Leyfa þessu forriti að deila eða taka upp?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Þegar þú deilir, tekur upp eða sendir út fær þetta forrit aðgang að öllu sem sést á skjánum eða spilast í tækinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Þegar þú deilir, tekur upp eða sendir út forrit hefur viðkomandi forrit aðgang að öllu sem sést eða spilast í forritinu. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð eða aðrar viðkvæmar upplýsingar."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Útilokað af kerfisstjóra"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Slökkt er á skjáupptöku í tækjareglum"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Stjórna"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Ferill"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Taktu úr lás til að nota"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Vandamál kom upp við að sækja kortin þín. Reyndu aftur síðar"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Stillingar fyrir læstan skjá"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skanna QR-kóða"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Vinnusnið"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Flugstilling"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Ekki mun heyrast í vekjaranum <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Viltu slökkva á farsímagögnum?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Þú munt ekki hafa aðgang að gögnum eða internetinu í gegnum <xliff:g id="CARRIER">%s</xliff:g>. Aðeins verður hægt að tengjast internetinu með Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"símafyrirtækið þitt"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Skipta aftur yfir í <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Ekki verður skipt sjálfkrafa á milli farsímagagna byggt á tiltækileika"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nei takk"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Já, skipta"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Stillingar geta ekki staðfest svarið þitt vegna þess að forrit er að fela heimildarbeiðni."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Viltu leyfa <xliff:g id="APP_0">%1$s</xliff:g> að sýna sneiðar úr <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Það getur lesið upplýsingar úr <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Farsímagögn"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Tengt"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tímabundin tenging"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Léleg tenging"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Farsímagögn tengjast ekki sjálfkrafa"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Engin tenging"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Engin önnur net í boði"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 1ebbbd8..0d2c3b1 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Scanner codici QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Sblocca"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloccato"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Scansione del viso"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Invia"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Volto non riconosciuto"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usa l\'impronta"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth collegato."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percentuale della batteria sconosciuta."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modalità aereo."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN attiva."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Batteria: <xliff:g id="NUMBER">%d</xliff:g> percento."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Livello della batteria: <xliff:g id="PERCENTAGE">%1$s</xliff:g> percento. Tempo rimanente in base al tuo utilizzo: <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batteria in carica, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Visualizza tutte le notifiche"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Telescrivente abilitata."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Suoneria vibrazione."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando condividi, registri o trasmetti un\'app, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ha accesso a qualsiasi elemento visualizzato o riprodotto sull\'app. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continua"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Condividi o registra un\'app"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Consentire all\'app di condividere o registrare?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Quando condividi, registri o trasmetti, l\'app ha accesso a qualsiasi elemento visibile sul tuo schermo o in riproduzione sul tuo dispositivo. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Quando condividi, registri o trasmetti un\'app, questa app ha accesso a qualsiasi elemento visualizzato o riprodotto su quell\'app. Presta quindi attenzione a password, dati di pagamento, messaggi o altre informazioni sensibili."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloccata dall\'amministratore IT"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"L\'acquisizione schermo è disattivata dai criteri relativi ai dispositivi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Cancella tutto"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestisci"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Cronologia"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Sblocca per usare"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Si è verificato un problema durante il recupero delle tue carte. Riprova più tardi."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Impostazioni schermata di blocco"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scansiona codice QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Profilo di lavoro"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modalità aereo"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Non sentirai la tua prossima sveglia <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Disattivare i dati mobili?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Non avrai accesso ai dati o a Internet tramite <xliff:g id="CARRIER">%s</xliff:g>. Internet sarà disponibile soltanto tramite Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"il tuo operatore"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vuoi passare nuovamente all\'operatore <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"I dati mobili non passeranno automaticamente all\'operatore in base alla disponibilità"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No, grazie"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sì, confermo"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Un\'app sta oscurando una richiesta di autorizzazione, pertanto Impostazioni non può verificare la tua risposta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vuoi consentire all\'app <xliff:g id="APP_0">%1$s</xliff:g> di mostrare porzioni dell\'app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Può leggere informazioni dell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dati mobili"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Connessione attiva"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Connessa temporaneamente"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Connessione debole"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Nessuna connessione dati mobili automatica"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nessuna connessione"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nessun\'altra rete disponibile"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index ce8c974..453e8d6 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -125,7 +125,7 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"האסיסטנט"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"ארנק"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"סורק קודי QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"ביטול נעילה"</string>
+ <string name="accessibility_unlock_button" msgid="3613812140816244310">"הנעילה מבוטלת"</string>
<string name="accessibility_lock_icon" msgid="661492842417875775">"המכשיר נעול"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"סורק פנים"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"שליחה"</string>
@@ -168,7 +168,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"לא ניתן לזהות את הפנים"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"שימוש בטביעת אצבע במקום זאת"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth מחובר."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"אחוז טעינת הסוללה לא ידוע."</string>
@@ -180,8 +180,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"מצב טיסה"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN פועל."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> אחוזים של סוללה."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"רמת הטעינה בסוללה: <xliff:g id="PERCENTAGE">%1$s</xliff:g> אחוזים, הזמן הנותר המשוער על סמך השימוש שלך: <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"הסוללה בטעינה, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"הצגת כל ההתראות"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter מופעל"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"צלצול ורטט."</string>
@@ -375,16 +380,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה ל-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> גישה לכל מה שגלוי באפליקציה או מופעל מהאפליקציה. כדאי להיזהר עם סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"המשך"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"שיתוף או הקלטה של אפליקציה"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"לאפשר לאפליקציה הזו לשתף או להקליט?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"בזמן שיתוף, הקלטה או העברה (cast) תהיה לאפליקציה הזו גישה לכל הפרטים שגלויים במסך שלך או מופעלים מהמכשיר שלך. כדאי להיזהר ולא לחשוף סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"בזמן שיתוף, הקלטה או העברה (cast) של אפליקציה, תהיה לאפליקציה הזו גישה לכל מה שמוצג בה או מופעל ממנה. כדאי להיזהר ולא לחשוף סיסמאות, פרטי תשלום, הודעות או מידע רגיש אחר."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"נחסם על ידי מנהל ה-IT"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"צילום המסך מושבת בגלל מדיניות המכשיר"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכול"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ניהול"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"היסטוריה"</string>
@@ -500,7 +500,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"יש לבטל את הנעילה כדי להשתמש"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"הייתה בעיה בקבלת הכרטיסים שלך. כדאי לנסות שוב מאוחר יותר"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"הגדרות מסך הנעילה"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"סריקת קוד QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"פרופיל עבודה"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"מצב טיסה"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"לא ניתן יהיה לשמוע את ההתראה הבאה שלך <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"לכבות את חבילת הגלישה?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"לא תהיה לך גישה לנתונים או לאינטרנט באמצעות <xliff:g id="CARRIER">%s</xliff:g>. אינטרנט יהיה זמין רק באמצעות Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"הספק שלך"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"לחזור אל <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"לא תתבצע החלפה אוטומטית של חבילת הגלישה על סמך זמינות"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"לא, תודה"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"כן, אני רוצה להחליף"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"יש אפליקציה שמסתירה את בקשת ההרשאה, ולכן אין אפשרות לאמת את התשובה בהגדרות."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"האם לאפשר ל-<xliff:g id="APP_0">%1$s</xliff:g> להציג חלקים מ-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- תהיה לה אפשרות לקרוא מידע מאפליקציית <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"חבילת גלישה"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"מחובר"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"מחובר באופן זמני"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"חיבור באיכות ירודה"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"החיבור לנתונים סלולריים לא מתבצע באופן אוטומטי"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"אין חיבור"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"אין רשתות זמינות אחרות"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index c76b207..7efa1a2 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -125,7 +125,7 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"音声アシスト"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"ウォレット"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR コードスキャナ"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"ロック解除"</string>
+ <string name="accessibility_unlock_button" msgid="3613812140816244310">"ロック解除済み"</string>
<string name="accessibility_lock_icon" msgid="661492842417875775">"デバイスはロックされています"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"顔のスキャン"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"送信"</string>
@@ -168,7 +168,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"顔を認識できません"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"指紋認証をお使いください"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetoothに接続済み。"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"バッテリー残量は不明です。"</string>
@@ -180,8 +180,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"機内モード。"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN は ON です。"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"バッテリー残量: <xliff:g id="NUMBER">%d</xliff:g>パーセント"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"バッテリー残量: <xliff:g id="PERCENTAGE">%1$s</xliff:g>、およそ <xliff:g id="TIME">%2$s</xliff:g> にバッテリー切れ(使用状況に基づく)"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"電池充電中: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>パーセント"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"通知をすべて表示"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"テレタイプライターが有効です。"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"バイブレーション着信。"</string>
@@ -375,16 +380,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"アプリの共有、録画、キャスト中は、そのアプリで表示されている内容や再生している内容に <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> がアクセスできるため、パスワード、お支払いの詳細、メッセージなどの機密情報にご注意ください。"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"続行"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"アプリの共有、録画"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"このアプリに共有や録画を許可しますか?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"共有、録画、キャスト中は、画面に表示されている内容やデバイスで再生している内容にこのアプリがアクセスできるため、パスワード、お支払いの詳細、メッセージなどの機密情報にご注意ください。"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"アプリの共有、録画、キャスト中は、そのアプリで表示されている内容や再生している内容にこのアプリがアクセスできるため、パスワード、お支払いの詳細、メッセージなどの機密情報にご注意ください。"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理者によりブロックされました"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"デバイス ポリシーに基づき、画面のキャプチャが無効になりました"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"すべて消去"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"履歴"</string>
@@ -445,7 +445,7 @@
<string name="volume_odi_captions_content_description" msgid="4172765742046013630">"字幕のオーバーレイ"</string>
<string name="volume_odi_captions_hint_enable" msgid="2073091194012843195">"有効にする"</string>
<string name="volume_odi_captions_hint_disable" msgid="2518846326748183407">"無効にする"</string>
- <string name="sound_settings" msgid="8874581353127418308">"着信音とバイブレーション"</string>
+ <string name="sound_settings" msgid="8874581353127418308">"サウンドとバイブレーション"</string>
<string name="volume_panel_dialog_settings_button" msgid="2513228491513390310">"設定"</string>
<string name="screen_pinning_title" msgid="9058007390337841305">"アプリは固定されています"</string>
<string name="screen_pinning_description" msgid="8699395373875667743">"固定を解除するまで画面が常に表示されるようになります。[戻る] と [最近] を同時に押し続けると固定が解除されます。"</string>
@@ -500,7 +500,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ロックを解除して使用"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"カードの取得中に問題が発生しました。しばらくしてからもう一度お試しください"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ロック画面の設定"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR コードのスキャン"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR コードスキャナ"</string>
<string name="status_bar_work" msgid="5238641949837091056">"仕事用プロファイル"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"機内モード"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"次回のアラーム(<xliff:g id="WHEN">%1$s</xliff:g>)は鳴りません"</string>
@@ -739,6 +739,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"モバイルデータを OFF にしますか?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g>でデータやインターネットにアクセスできなくなります。インターネットには Wi-Fi からのみ接続できます。"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"携帯通信会社"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> に戻しますか?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"利用可能な場合でも、モバイルデータを利用するよう自動的に切り替わることはありません"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"キャンセル"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"切り替える"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"アプリが許可リクエストを隠しているため、設定側でユーザーの応答を確認できません。"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"「<xliff:g id="APP_2">%2$s</xliff:g>」のスライスの表示を「<xliff:g id="APP_0">%1$s</xliff:g>」に許可しますか?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- 「<xliff:g id="APP">%1$s</xliff:g>」からの情報の読み取り"</string>
@@ -929,6 +933,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"モバイルデータ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"接続済み"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"一時的に接続されています"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"接続が不安定です"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"モバイルデータには自動接続しません"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"接続なし"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"利用できるネットワークはありません"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 729951d..292aaa4 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ხმოვანი დახმარება"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"საფულე"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR კოდის სკანერი"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"განბლოკვა"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"მოწყობილობა ჩაკეტილია"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"მიმდინარეობს სახის სკანირება"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"გაგზავნა"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"სახის ამოცნობა შეუძლებ."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"გამოიყენეთ თითის ანაბეჭდი"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth დაკავშირებულია."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ბატარეის პროცენტული მაჩვენებელი უცნობია."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"თვითმფრინავის რეჟიმი"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ჩართულია."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"ბატარეა: <xliff:g id="NUMBER">%d</xliff:g> პროცენტი."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ბატარეა <xliff:g id="PERCENTAGE">%1$s</xliff:g> პროცენტზეა, მოხმარების გათვალისწინებით დარჩა დაახლოებით <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ბატარეა იტენება. ამჟამად არის <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> პროცენტი."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"ყველა შეტყობინების ნახვა"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"ტელეტაიპი ჩართულია."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ვიბრაციის რეჟიმი."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"აპის გაზიარებისას, ჩაწერისას ან ტრანსლირებისას <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> აქვს წვდომა აქვს ყველაფერზე, რაც ჩანს აპში ან ითამაშეთ. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა მგრძნობიარე ინფორმაციასთან."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"გაგრძელება"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"გააზიარეთ ან ჩაწერეთ აპი"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"გსურთ ამ აპისთვის გაზიარების ან ჩაწერის უფლების მიცემა?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"როდესაც თქვენ აზიარებთ, იწერთ ან ტრანსლირებთ, ამ აპს აქვს წვდომა ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენს მოწყობილობაზე. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა სენსიტიურ ინფორმაციასთან."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"როდესაც თქვენ აზიარებთ, იწერთ ან ტრანსლირებთ, ამ აპს აქვს წვდომა ყველაფერზე, რაც ჩანს თქვენს ეკრანზე ან უკრავს თქვენს მოწყობილობაზე. ამიტომ იყავით ფრთხილად პაროლებთან, გადახდის დეტალებთან, შეტყობინებებთან ან სხვა სენსიტიურ ინფორმაციასთან."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"დაბლოკილია თქვენი IT-ადმინისტრატორის მიერ"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ეკრანის აღბეჭდვა გამორთულია მოწყობილობის წესების თანახმად"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ყველას გასუფთავება"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"მართვა"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ისტორია"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"გამოსაყენებლად განბლოკვა"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"თქვენი ბარათების მიღებისას პრობლემა წარმოიშვა. ცადეთ ხელახლა მოგვიანებით"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ჩაკეტილი ეკრანის პარამეტრები"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR კოდის სკანირება"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR კოდის სკანერი"</string>
<string name="status_bar_work" msgid="5238641949837091056">"სამსახურის პროფილი"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"თვითმფრინავის რეჟიმი"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"ვერ გაიგონებთ მომდევნო მაღვიძარას <xliff:g id="WHEN">%1$s</xliff:g>-ზე"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"გსურთ მობილური ინტერნეტის გამორთვა?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"თქვენ არ გექნებათ მობილურ ინტერნეტზე ან ზოგადად ინტერნეტზე წვდომა <xliff:g id="CARRIER">%s</xliff:g>-ის მეშვეობით. ინტერნეტი მხოლოდ Wi-Fi-კავშირის მეშვეობით იქნება ხელმისაწვდომი."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"თქვენი ოპერატორი"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"გსურთ ისევ <xliff:g id="CARRIER">%s</xliff:g>-ზე გადართვა?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"მობილური მონაცემების ხელმისაწვდომობის მიხედვით ავტომატური გადართვა არ მოხდება"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"არა, გმადლობთ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"დიახ, გადაირთოს"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ვინაიდან აპი ფარავს ნებართვის მოთხოვნას, პარამეტრების მიერ თქვენი პასუხი ვერ დასტურდება."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"ანიჭებთ ნებართვას <xliff:g id="APP_0">%1$s</xliff:g>-ს, აჩვენოს <xliff:g id="APP_2">%2$s</xliff:g>-ის ფრაგმენტები?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- მას შეუძლია ინფორმაციის <xliff:g id="APP">%1$s</xliff:g>-დან წაკითხვა"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"მობილური ინტერნეტი"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"დაკავშირებული"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"დროებით დაკავშირებული"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"სუსტი კავშირი"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"მობილურ ინტერნეტს ავტომატურად არ დაუკავშირდება"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"კავშირი არ არის"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"სხვა ქსელები მიუწვდომელია"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 9a6530e..bffda32 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Дауыс көмекшісі"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR кодын сканерлеу қолданбасы"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Бекітпесін ашу"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Құрылғы құлыпталды."</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Бетті сканерлеу"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Жіберу"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Бет танылмады."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Орнына саусақ ізін пайдаланыңыз."</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth қосылған."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея зарядының мөлшері белгісіз."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Ұшақ режимі."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN қосулы."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Батарея <xliff:g id="NUMBER">%d</xliff:g> пайыз."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батарея заряды: <xliff:g id="PERCENTAGE">%1$s</xliff:g> пайыз. Пайдалануға байланысты шамамен <xliff:g id="TIME">%2$s</xliff:g> уақытқа жетеді."</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Батарея зарядталуда, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Барлық хабарландыруды қарау"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Телетайп қосылған."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Қоңырау тербелісі."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Қолданба экранын бөлісу, жазу не трансляциялау кезінде <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> қолданбасы онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Жалғастыру"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Қолданба экранын бөлісу не жазу"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Қолданбаға бөлісуге не жазып алуға рұқсат берілсін бе?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Экранды бөлісу, жазып алу не трансляциялау кезінде бұл қолданба экраныңызда көрінетін не құрылғыңызда ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Қолданбаны бөлісу, жазып алу не трансляциялау кезінде бұл қолданба онда көрінетін не ойнатылатын барлық нәрсені пайдалана алады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізу кезінде сақ болыңыз."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Әкімшіңіз бөгеген"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Құрылғы саясатына байланысты экранды түсіру өшірілді."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Барлығын тазалау"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Басқару"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Тарих"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Пайдалану үшін құлыпты ашу"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Карталарыңыз алынбады, кейінірек қайталап көріңіз."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Экран құлпының параметрлері"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR кодын сканерлеу"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Жұмыс профилі"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Ұшақ режимі"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Келесі <xliff:g id="WHEN">%1$s</xliff:g> дабылыңызды есітпейсіз"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобильдік интернет өшірілсін бе?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> операторы арқылы деректерге немесе интернетке кіре алмайсыз. Интернетке тек Wi-Fi арқылы кіресіз."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"операторыңыз"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> операторына қайта ауысу керек пе?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобильдік интернет операторды қолдану мүмкіндігіне қарай автоматты түрде ауыспайды."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Жоқ, рақмет"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Иә, ауыстырылсын"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Басқа қолданба рұқсат сұрауын жасырып тұрғандықтан, параметрлер жауабыңызды растай алмайды."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> қолданбасына <xliff:g id="APP_2">%2$s</xliff:g> қолданбасының үзінділерін көрсетуге рұқсат берілсін бе?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Бұл <xliff:g id="APP">%1$s</xliff:g> қолданбасындағы ақпаратты оқи алады"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобильдік интернет"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Жалғанды"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Уақытша байланыс орнатылды."</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Байланыс нашар."</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобильдік интернет автоматты түрде қосылмайды."</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Байланыс жоқ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Басқа қолжетімді желі жоқ"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 979c1c3..6d2e5a8 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ជំនួយសំឡេង"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"កម្មវិធីស្កេនកូដ QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"ដោះសោ"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"បានចាក់សោឧបករណ៍"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"ការស្កេនមុខ"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ផ្ញើ"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"មិនអាចសម្គាល់មុខបានទេ"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ប្រើស្នាមម្រាមដៃជំនួសវិញ"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"បានតភ្ជាប់ប៊្លូធូស។"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"មិនដឹងអំពីភាគរយថ្មទេ។"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"ពេលជិះយន្តហោះ"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"បើក VPN ។"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"ថ្ម <xliff:g id="NUMBER">%d</xliff:g> ភាគរយ។"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ថ្ម <xliff:g id="PERCENTAGE">%1$s</xliff:g> ភាគរយ អាចប្រើបានប្រហែល <xliff:g id="TIME">%2$s</xliff:g> ទៀត ផ្អែកលើការប្រើប្រាស់របស់អ្នក"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"កំពុងសាកថ្ម <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> ភាគរយ"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"មើលការជូនដំណឹងទាំងអស់"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"បានបើកម៉ាស៊ីនអង្គុលីលេខ"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"កម្មវិធីរោទ៍ញ័រ។"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬបញ្ជូនកម្មវិធី <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> មានសិទ្ធិចូលប្រើប្រាស់អ្វីៗដែលបង្ហាញ ឬលេងនៅលើកម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើបផ្សេងទៀត។"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"បន្ត"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ចែករំលែក ឬថតកម្មវិធី"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"អនុញ្ញាតកម្មវិធីនេះឱ្យចែករំលែក ឬថតឬ?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬបញ្ជូន កម្មវិធីនេះមានសិទ្ធិចូលប្រើប្រាស់អ្វីៗដែលបង្ហាញឱ្យឃើញនៅលើអេក្រង់របស់អ្នក ឬលេងនៅលើឧបករណ៍របស់អ្នក។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើបផ្សេងទៀត។"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"នៅពេលអ្នកកំពុងចែករំលែក ថត ឬបញ្ជូនកម្មវិធី កម្មវិធីនេះមានសិទ្ធិចូលប្រើប្រាស់អ្វីៗដែលបង្ហាញ ឬលេងនៅលើកម្មវិធីនោះ។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ ឬព័ត៌មានរសើបផ្សេងទៀត។"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"បានទប់ស្កាត់ដោយអ្នកគ្រប់គ្រងផ្នែកព័ត៌មានវិទ្យារបស់អ្នក"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ការថតអេក្រង់ត្រូវបានបិទដោយគោលការណ៍ឧបករណ៍"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"សម្អាតទាំងអស់"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"គ្រប់គ្រង"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ប្រវត្តិ"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ដោះសោដើម្បីប្រើប្រាស់"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"មានបញ្ហាក្នុងការទាញយកកាតរបស់អ្នក សូមព្យាយាមម្ដងទៀតនៅពេលក្រោយ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ការកំណត់អេក្រង់ចាក់សោ"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"ស្កេនកូដ QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"ប្រវត្តិរូបការងារ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ពេលជិះយន្តហោះ"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"អ្នកនឹងមិនលឺម៉ោងរោទ៍ <xliff:g id="WHEN">%1$s</xliff:g> បន្ទាប់របស់អ្នកទេ"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"បិទទិន្នន័យទូរសព្ទចល័ត?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"អ្នកនឹងមិនមានសិទ្ធិចូលប្រើទិន្នន័យ ឬអ៊ីនធឺណិតតាមរយៈ <xliff:g id="CARRIER">%s</xliff:g> បានឡើយ។ អ៊ីនធឺណិតនឹងអាចប្រើបានតាមរយៈ Wi-Fi តែប៉ុណ្ណោះ។"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ក្រុមហ៊ុនសេវាទូរសព្ទរបស់អ្នក"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ប្ដូរទៅ <xliff:g id="CARRIER">%s</xliff:g> វិញឬ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ទិន្នន័យទូរសព្ទចល័តនឹងមិនប្ដូរដោយស្វ័យប្រវត្តិដោយផ្អែកតាមភាពអាចប្រើបាននោះទេ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ទេ អរគុណ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"បាទ/ចាស ប្ដូរ"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ការកំណត់មិនអាចផ្ទៀងផ្ទាត់ការឆ្លើយតបរបស់អ្នកបានទេ ដោយសារកម្មវិធីកំពុងបាំងសំណើសុំការអនុញ្ញាត។"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"អនុញ្ញាតឱ្យ <xliff:g id="APP_0">%1$s</xliff:g> បង្ហាញស្ថិតិប្រើប្រាស់របស់ <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- វាអាចអានព័ត៌មានពី <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ទិន្នន័យទូរសព្ទចល័ត"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"បានភ្ជាប់"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"បានភ្ជាប់ជាបណ្ដោះអាសន្ន"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ការតភ្ជាប់ខ្សោយ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ទិន្នន័យទូរសព្ទចល័តនឹងមិនភ្ជាប់ដោយស្វ័យប្រវត្តិទេ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"មិនមានការតភ្ជាប់ទេ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"មិនមានបណ្ដាញផ្សេងទៀតដែលអាចប្រើបានទេ"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 48270a6..099139f 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ಧ್ವನಿ ಸಹಾಯಕ"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"ವಾಲೆಟ್"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR ಕೋಡ್ ಸ್ಕ್ಯಾನರ್"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"ಅನ್ಲಾಕ್"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"ಸಾಧನ ಲಾಕ್ ಆಗಿದೆ"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"ಮುಖವನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ಕಳುಹಿಸಿ"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ಮುಖ ಗುರುತಿಸಲಾಗುತ್ತಿಲ್ಲ"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ಬದಲಿಗೆ ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಬಳಸಿ"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ಬ್ಲೂಟೂತ್ ಸಂಪರ್ಕಗೊಂಡಿದೆ."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ಬ್ಯಾಟರಿ ಶೇಕಡಾವಾರು ತಿಳಿದಿಲ್ಲ."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"ಏರೋಪ್ಲೇನ್ ಮೋಡ್"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"ನಲ್ಲಿ VPN"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"ಬ್ಯಾಟರಿ <xliff:g id="NUMBER">%d</xliff:g> ಪ್ರತಿಶತ."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ನಿಮ್ಮ ಬಳಕೆಯ ಆಧಾರದ ಮೇಲೆ ಬ್ಯಾಟರಿಯು ಪ್ರತಿಶತ <xliff:g id="PERCENTAGE">%1$s</xliff:g> ರಷ್ಟು ಮತ್ತು <xliff:g id="TIME">%2$s</xliff:g> ಸಮಯ ಬಾಕಿ ಉಳಿದಿದೆ"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ಬ್ಯಾಟರಿ <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> ಪ್ರತಿಶತ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"ಎಲ್ಲಾ ಅಧಿಸೂಚನೆಗಳನ್ನು ನೋಡಿ"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"ಟೆಲಿಟೈಪ್ರೈಟರ್ ಸಕ್ರಿಯವಾಗಿದೆ."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ರಿಂಗರ್ ವೈಬ್ರೇಟ್."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ಮುಂದುವರಿಸಿ"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ಆ್ಯಪ್ ಅನ್ನು ಹಂಚಿಕೊಳ್ಳಿ ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ಹಂಚಿಕೊಳ್ಳಲು ಅಥವಾ ರೆಕಾರ್ಡ್ ಮಾಡಲು ಈ ಆ್ಯಪ್ಗೆ ಅನುಮತಿಸುವುದೇ?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ನೀವು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಮೇಲೆ ಕಾಣಿಸುವ ಅಥವಾ ನಿಮ್ಮ ಸಾಧನದಲ್ಲಿ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ ಈ ಆ್ಯಪ್ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ನೀವು ಆ್ಯಪ್ ಒಂದನ್ನು ಹಂಚಿಕೊಳ್ಳುತ್ತಿರುವಾಗ, ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ ಅಥವಾ ಬಿತ್ತರಿಸುತ್ತಿರುವಾಗ, ಆ ಆ್ಯಪ್ನಲ್ಲಿ ತೋರಿಸಲಾಗುವ ಅಥವಾ ಪ್ಲೇ ಆಗುವ ಯಾವುದೇ ವಿಷಯಕ್ಕೆ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಈ ಆ್ಯಪ್ ಹೊಂದಿರುತ್ತದೆ. ಹಾಗಾಗಿ, ಪಾಸ್ವರ್ಡ್ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು ಅಥವಾ ಇತರ ಸೂಕ್ಷ್ಮ ಮಾಹಿತಿಯ ಕುರಿತು ಜಾಗರೂಕರಾಗಿರಿ."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರು ನಿರ್ಬಂಧಿಸಿದ್ದಾರೆ"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ಸಾಧನ ನೀತಿಯಿಂದ ಸ್ಕ್ರೀನ್ ಕ್ಯಾಪ್ಚರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ಇತಿಹಾಸ"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ಬಳಸಲು ಅನ್ಲಾಕ್ ಮಾಡಿ"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"ನಿಮ್ಮ ಕಾರ್ಡ್ಗಳನ್ನು ಪಡೆಯುವಾಗ ಸಮಸ್ಯೆ ಉಂಟಾಗಿದೆ, ನಂತರ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ಲಾಕ್ ಸ್ಕ್ರ್ರೀನ್ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR ಕೋಡ್ ಸ್ಕ್ಯಾನ್ ಮಾಡಿ"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ಏರ್ಪ್ಲೇನ್ ಮೋಡ್"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"ನಿಮ್ಮ ಮುಂದಿನ <xliff:g id="WHEN">%1$s</xliff:g> ಅಲಾರಮ್ ಅನ್ನು ನೀವು ಆಲಿಸುವುದಿಲ್ಲ"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ಮೊಬೈಲ್ ಡೇಟಾ ಆಫ್ ಮಾಡಬೇಕೆ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"ನೀವು <xliff:g id="CARRIER">%s</xliff:g> ಮೂಲಕ ಡೇಟಾ ಅಥವಾ ಇಂಟರ್ನೆಟ್ಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುವುದಿಲ್ಲ. ಇಂಟರ್ನೆಟ್, ವೈ-ಫೈ ಮೂಲಕ ಮಾತ್ರ ಲಭ್ಯವಿರುತ್ತದೆ."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ನಿಮ್ಮ ವಾಹಕ"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> ಗೆ ಬದಲಿಸುವುದೇ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ಲಭ್ಯತೆಯ ಆಧಾರದ ಮೇಲೆ ಮೊಬೈಲ್ ಡೇಟಾ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಬದಲಾಗುವುದಿಲ್ಲ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ಬೇಡ, ಧನ್ಯವಾದಗಳು"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ಹೌದು, ಬದಲಿಸಿ"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ಅನುಮತಿ ವಿನಂತಿಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಮರೆಮಾಚುತ್ತಿರುವ ಕಾರಣ, ಸೆಟ್ಟಿಂಗ್ಗಳಿಗೆ ನಿಮ್ಮ ಪ್ರತಿಕ್ರಿಯೆಯನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್ಗಳನ್ನು ತೋರಿಸಲು <xliff:g id="APP_0">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ನಿಂದ ಮಾಹಿತಿಯನ್ನು ಓದಬಹುದು"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ಮೊಬೈಲ್ ಡೇಟಾ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ತಾತ್ಕಾಲಿಕವಾಗಿ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ಕಳಪೆ ಸಂಪರ್ಕ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ಮೊಬೈಲ್ ಡೇಟಾ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಕನೆಕ್ಟ್ ಆಗುವುದಿಲ್ಲ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ಯಾವುದೇ ಕನೆಕ್ಷನ್ ಇಲ್ಲ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ಇತರ ಯಾವುದೇ ನೆಟ್ವರ್ಕ್ಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index dab98eb..9e3d6c3 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"음성 지원"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"월렛"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR 코드 스캐너"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"잠금 해제"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"기기 잠김"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"얼굴 스캔 중"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"보내기"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"얼굴을 인식할 수 없습니다."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"대신 지문을 사용하세요."</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"블루투스가 연결되었습니다."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"배터리 잔량을 알 수 없습니다."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"비행기 모드입니다."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN 켜짐"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"배터리 <xliff:g id="NUMBER">%d</xliff:g>퍼센트"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"배터리 <xliff:g id="PERCENTAGE">%1$s</xliff:g>퍼센트, 평소 사용량 기준 약 <xliff:g id="TIME">%2$s</xliff:g> 남음"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"배터리 충전 중, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%입니다."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"모든 알림 보기"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"전신 타자기(TTY)가 사용 설정되었습니다."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"벨소리가 진동입니다."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"앱을 공유하거나 녹화하거나 전송할 때는 <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>에서 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"계속"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"앱 공유 또는 녹화"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"앱에서 공유하거나 기록하도록 허용하시겠습니까?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"공유하거나 녹화하거나 전송할 때 이 앱에서 화면에 표시되거나 기기에서 재생되는 모든 항목에 액세스할 수 있습니다. 따라서 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"앱을 공유하거나 녹화하거나 전송할 때는 이 앱에서 해당 앱에 표시되거나 재생되는 모든 항목에 액세스할 수 있으므로 비밀번호, 결제 세부정보, 메시지 등 민감한 정보가 노출되지 않도록 주의하세요."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 관리자에 의해 차단됨"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"기기 정책에 의해 화면 캡처가 사용 중지되었습니다."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"기록"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"잠금 해제하여 사용"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"카드를 가져오는 중에 문제가 발생했습니다. 나중에 다시 시도해 보세요."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"잠금 화면 설정"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR 코드 스캔"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"직장 프로필"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"비행기 모드"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>에 다음 알람을 들을 수 없습니다."</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"모바일 데이터를 사용 중지하시겠습니까?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g>을(를) 통해 데이터 또는 인터넷에 액세스할 수 없게 됩니다. 인터넷은 Wi-Fi를 통해서만 사용할 수 있습니다."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"이동통신사"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"다시 <xliff:g id="CARRIER">%s</xliff:g>(으)로 전환할까요?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"모바일 데이터가 가용성에 따라 자동으로 전환하지 않습니다."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"나중에"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"예, 전환합니다"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"앱이 권한 요청을 가리고 있기 때문에 설정에서 내 응답을 확인할 수 없습니다."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>에서 <xliff:g id="APP_2">%2$s</xliff:g>의 슬라이스를 표시하도록 허용하시겠습니까?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g>의 정보를 읽을 수 있음"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"모바일 데이터"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"연결됨"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"일시적으로 연결됨"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"연결 상태 나쁨"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"모바일 데이터가 자동으로 연결되지 않음"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"연결되지 않음"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"사용 가능한 다른 네트워크가 없음"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index a3c98ce..bfa6bb8 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Үн жардамчысы"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Капчык"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR коддорунун сканери"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Кулпусун ачуу"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Түзмөк кулпуланды"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Жүз скандалууда"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Жөнөтүү"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Жүз таанылбай жатат"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Манжа изин колдонуңуз"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth байланышта"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарея кубатынын деңгээли белгисиз."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Учак тартиби."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN күйүк."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Батарея <xliff:g id="NUMBER">%d</xliff:g> пайыз."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батареянын деңгээли <xliff:g id="PERCENTAGE">%1$s</xliff:g> пайыз, колдонгонуңузга караганда болжол менен <xliff:g id="TIME">%2$s</xliff:g> калды"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Батарея кубатталууда, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Бардык билдирмелерди көрүү"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"ТелеТайп терүүсү жандырылган."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Шыңгыраганда титирөө."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Бөлүшүп, жаздырып же тышкы экранда бөлүшкөндө <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ал колдонмодо көрүнүп жана ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөм маалыматын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Улантуу"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Колдонмону бөлүшүү же жаздыруу"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Бул колдонмого бөлүшүп же жаздырууга уруксат бересизби?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Бөлүшүп, жаздырып же тышкы экранга чыгарганда бул колдонмо экраныңызда көрүнүп жана түзмөктө ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Бөлүшүп, жаздырып же тышкы экранга чыгарганда бул колдонмо ал колдонмодо көрсөтүлүп жана ойнотулуп жаткан нерселерге мүмкүнчүлүк алат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү жана башка купуя маалыматты көрсөтүп албаңыз."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT администраторуңуз бөгөттөп койгон"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Түзмөк саясаты экрандагыны тартып алууну өчүрүп койгон"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Баарын тазалап салуу"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Башкаруу"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Таржымал"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Колдонуу үчүн кулпусун ачыңыз"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Кыйытмаларды алууда ката кетти. Бир аздан кийин кайталап көрүңүз."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Экранды кулпулоо параметрлери"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR кодун скандоо"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Жумуш профили"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Учак режими"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> боло турган кийинки эскертмени укпайсыз"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобилдик Интернетти өчүрөсүзбү?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> байланыш оператору аркылуу Интернетке кире албай каласыз. Интернетке Wi-Fi аркылуу гана кирүүгө болот."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"байланыш операторуңуз"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Кайра <xliff:g id="CARRIER">%s</xliff:g> байланыш операторуна которуласызбы?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Жеткиликтүү болгондо мобилдик Интернет автоматтык түрдө которулбайт"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Жок, рахмат"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ооба, которулуу"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Уруксат берүү сурамыңыз көрүнбөй калгандыктан, Жөндөөлөр жообуңузду ырастай албай жатат."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосуна <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөтүүгө уруксат берилсинби?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунун маалыматын окуйт"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилдик трафик"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Туташты"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Убактылуу туташып турат"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Байланыш начар"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобилдик трафик автоматтык түрдө туташтырылбайт"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Байланыш жок"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Башка тармактар жеткиликсиз"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 862cd52..e3a9079 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ຊ່ວຍເຫຼືອທາງສຽງ"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"ຕົວສະແກນລະຫັດ QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"ປົດລັອກ"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"ອຸປະກອນຖືກລັອກໄວ້"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"ການສະແກນໜ້າ"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ສົ່ງ"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ບໍ່ສາມາດຈຳແນກໃບໜ້າໄດ້"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ກະລຸນາໃຊ້ລາຍນິ້ວມືແທນ"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ເຊື່ອມຕໍ່ Bluetooth ແລ້ວ."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ບໍ່ຮູ້ເປີເຊັນແບັດເຕີຣີ."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"ໂໝດໃນຍົນ."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ເປີດ."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"ແບັດເຕີຣີ <xliff:g id="NUMBER">%d</xliff:g> ເປີເຊັນ."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ແບັດເຕີຣີ <xliff:g id="PERCENTAGE">%1$s</xliff:g> ເປີເຊັນ, ເຫຼືອປະມານ <xliff:g id="TIME">%2$s</xliff:g> ອ້າງອີງຈາກການນຳໃຊ້ຂອງທ່ານ"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ກຳລັງສາກແບັດເຕີຣີ, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> ເປີເຊັນ."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"ເບິ່ງການແຈ້ງເຕືອນທັງໝົດ"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter ຖືກເປີດຢູ່."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ສັ່ນເຕືອນພ້ອມສຽງເອີ້ນເຂົ້າ."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ໃນຕອນທີ່ທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານແອັບ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ມີສິດເຂົ້າເຖິງສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ໃນແອັບນັ້ນ. ດັ່ງນັ້ນໃຫ້ລະມັດລະວັງກ່ຽວກັບລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ ຫຼື ຂໍ້ມູນທີ່ລະອຽດອ່ອນອື່ນໆ."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ສືບຕໍ່"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ແບ່ງປັນ ຫຼື ບັນທຶກແອັບ"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ອະນຸຍາດໃຫ້ແອັບນີ້ແບ່ງປັນ ຫຼື ບັນທຶກບໍ?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ເມື່ອທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານ, ແອັບນີ້ຈະມີສິດເຂົ້າເຖິງສິ່ງທີ່ເຫັນໄດ້ໃນໜ້າຈໍຂອງທ່ານ ຫຼື ຫຼິ້ນຢູ່ອຸປະກອນຂອງທ່ານ. ດັ່ງນັ້ນໃຫ້ລະມັດລະວັງເລື່ອງລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ ຫຼື ຂໍ້ມູນທີ່ລະອຽດອ່ອນອື່ນໆ."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ໃນຕອນທີ່ທ່ານກຳລັງແບ່ງປັນ, ບັນທຶກ ຫຼື ສົ່ງສັນຍານແອັບ, ແອັບນີ້ຈະມີສິດເຂົ້າເຖິງສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ໃນແອັບນັ້ນ. ດັ່ງນັ້ນໃຫ້ລະມັດລະວັງກ່ຽວກັບລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ ຫຼື ຂໍ້ມູນທີ່ລະອຽດອ່ອນອື່ນໆ."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ຖືກບລັອກໄວ້ໂດຍຜູ້ເບິ່ງແຍງລະບົບໄອທີຂອງທ່ານ"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ການຖ່າຍຮູບໜ້າຈໍຖືກປິດການນຳໃຊ້ໄວ້ໂດຍນະໂຍບາຍອຸປະກອນ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ລຶບລ້າງທັງໝົດ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ຈັດການ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ປະຫວັດ"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ປົດລັອກເພື່ອໃຊ້"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"ເກີດບັນຫາໃນການໂຫຼດບັດຂອງທ່ານ, ກະລຸນາລອງໃໝ່ໃນພາຍຫຼັງ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ການຕັ້ງຄ່າໜ້າຈໍລັອກ"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"ສະແກນລະຫັດ QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ໂໝດເຮືອບິນ"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"ທ່ານຈະບໍ່ໄດ້ຍິນສຽງໂມງປ <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ປິດອິນເຕີເນັດມືຖືໄວ້ບໍ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"ທ່ານຈະບໍ່ມີສິດເຂົ້າເຖິງຂໍ້ມູນ ຫຼື ອິນເຕີເນັດຜ່ານ <xliff:g id="CARRIER">%s</xliff:g>. ອິນເຕີເນັດຈະສາມາດໃຊ້ໄດ້ຜ່ານ Wi-Fi ເທົ່ານັ້ນ."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ຜູ້ໃຫ້ບໍລິການຂອງທ່ານ"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ສະຫຼັບກັບໄປໃຊ້ <xliff:g id="CARRIER">%s</xliff:g> ບໍ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ອິນເຕີເນັດມືຖືຈະບໍ່ປ່ຽນຕາມຄວາມພ້ອມໃຫ້ບໍລິການໂດຍອັດຕະໂນມັດ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ບໍ່, ຂອບໃຈ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ແມ່ນແລ້ວ, ສະຫຼັບ"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ເນື່ອງຈາກມີແອັບໃດໜຶ່ງກຳລັງຂັດຂວາງການຂໍອະນຸຍາດ, ການຕັ້ງຄ່າຈຶ່ງບໍ່ສາມາດຢັ້ງຢືນການຕອບຮັບຂອງທ່ານໄດ້."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"ອະນຸຍາດ <xliff:g id="APP_0">%1$s</xliff:g> ໃຫ້ສະແດງ <xliff:g id="APP_2">%2$s</xliff:g> ສະໄລ້ບໍ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ມັນສາມາດອ່ານຂໍ້ມູນຈາກ <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ອິນເຕີເນັດມືຖື"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ເຊື່ອມຕໍ່ແລ້ວຊົ່ວຄາວ"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ສັນຍານເຊື່ອມຕໍ່ຊ້າ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ຈະບໍ່ເຊື່ອມຕໍ່ອິນເຕີເນັດມືຖືອັດຕະໂນມັດ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ບໍ່ມີການເຊື່ອມຕໍ່"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ບໍ່ມີເຄືອຂ່າຍອື່ນທີ່ສາມາດໃຊ້ໄດ້"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 0b13747..7daedbd 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR kodų skaitytuvas"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Atrakinti"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Įrenginys užrakintas"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Nuskaitomas veidas"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Siųsti"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Veidas neatpažintas"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Naudoti piršto antspaudą"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"„Bluetooth“ prijungtas."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akumuliatoriaus energija procentais nežinoma."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Lėktuvo režimas."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN įjungtas."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Akumuliatorius: <xliff:g id="NUMBER">%d</xliff:g> proc."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> proc. akumuliatoriaus energijos – liko maždaug <xliff:g id="TIME">%2$s</xliff:g>, atsižvelgiant į naudojimą"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Įkraunamas akumuliatorius, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Žr. visus pranešimus"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"„TeleTypewriter“ įgalinta."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibracija skambinant."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kai bendrinate, įrašote ar perduodate turinį, „<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>“ gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs su slaptažodžiais, išsamia mokėjimo metodo informacija, pranešimais ar kita neskelbtina informacija."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Tęsti"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Programos bendrinimas ar įrašymas"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Leisti šiai programai bendrinti arba įrašyti?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kai bendrinate, įrašote ar perduodate turinį, ši programa gali pasiekti viską, kas rodoma ekrane ar leidžiama įrenginyje. Todėl būkite atsargūs su slaptažodžiais, išsamia mokėjimo metodo informacija, pranešimais ar kita neskelbtina informacija."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kai bendrinate, įrašote ar perduodate turinį, ši programa gali pasiekti viską, kas rodoma ar leidžiama programoje. Todėl būkite atsargūs su slaptažodžiais, išsamia mokėjimo metodo informacija, pranešimais ar kita neskelbtina informacija."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Užblokavo jūsų IT administratorius"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekrano fiksavimo funkcija išjungta vadovaujantis įrenginio politika"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Viską išvalyti"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Tvarkyti"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Atrakinti, kad būtų galima naudoti"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Gaunant korteles kilo problema, bandykite dar kartą vėliau"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Užrakinimo ekrano nustatymai"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR kodo nuskaitymas"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Darbo profilis"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Lėktuvo režimas"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Negirdėsite kito signalo <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Išjungti mobiliojo ryšio duomenis?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Naudodamiesi „<xliff:g id="CARRIER">%s</xliff:g>“ paslaugomis neturėsite galimybės pasiekti duomenų arba interneto. Internetą galėsite naudoti tik prisijungę prie „Wi-Fi“."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"savo operatoriaus"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Perjungti atgal į „<xliff:g id="CARRIER">%s</xliff:g>“?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiliojo ryšio duomenys nebus automatiškai perjungti atsižvelgiant į pasiekiamumą"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, ačiū"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Taip, perjungti"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Kadangi programa užstoja leidimo užklausą, nustatymuose negalima patvirtinti jūsų atsakymo."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Leisti „<xliff:g id="APP_0">%1$s</xliff:g>“ rodyti „<xliff:g id="APP_2">%2$s</xliff:g>“ fragmentus?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Gali nuskaityti informaciją iš „<xliff:g id="APP">%1$s</xliff:g>“"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiliojo ryšio duomenys"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Prisijungta"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Laikinai prijungta"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Prastas ryšys"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Naud. mob. r. duomenis nebus autom. prisijungiama"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nėra ryšio"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nėra kitų pasiekiamų tinklų"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index e90a0a4..d431233 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Balss palīgs"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Maks"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Kvadrātkoda skeneris"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Atbloķēt"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Ierīce ir bloķēta"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Sejas skenēšana"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Sūtīt"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Nevar atpazīt seju"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Lietot pirksta nospiedumu"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth savienojums ir izveidots."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Akumulatora uzlādes līmenis procentos nav zināms."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Lidmašīnas režīms."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ieslēgts"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Akumulators: <xliff:g id="NUMBER">%d</xliff:g> procenti"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Akumulatora uzlādes līmenis: <xliff:g id="PERCENTAGE">%1$s</xliff:g> procenti. Ņemot vērā lietojumu, atlikušais laiks ir apmēram <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Notiek akumulatora uzlāde, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Skatīt visus paziņojumus"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletaips ir iespējots."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Zvana signāls — vibrācija."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Lietotnes kopīgošanas, ierakstīšanas vai apraides laikā <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Turpināt"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Lietotnes kopīgošana vai ierakstīšana"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vai atļaut šai lietotnei kopīgot vai ierakstīt?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kopīgošanas, ierakstīšanas vai apraides laikā šī lietotne var piekļūt visam, kas tiek rādīts jūsu ekrānā vai atskaņots jūsu ierīcē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kopīgošanas, ierakstīšanas vai apraides laikā lietotne var piekļūt visam, kas tiek rādīts vai atskaņots attiecīgajā lietotnē. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem un citu sensitīvu informāciju."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloķējis jūsu IT administrators"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ierīces politika ir atspējojusi ekrānuzņēmumu izveidi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Dzēst visu"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Pārvaldīt"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Vēsture"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lai izmantotu, atbloķējiet ekrānu"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Ienesot jūsu kartes, radās problēma. Lūdzu, vēlāk mēģiniet vēlreiz."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Bloķēšanas ekrāna iestatījumi"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Kvadrātkoda skenēšana"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Darba profils"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Lidojuma režīms"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nākamais signāls (<xliff:g id="WHEN">%1$s</xliff:g>) netiks atskaņots."</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vai izslēgt mobilos datus?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Izmantojot mobilo sakaru operatora <xliff:g id="CARRIER">%s</xliff:g> pakalpojumus, nevarēsiet piekļūt datiem vai internetam. Internetam varēsiet piekļūt, tikai izmantojot Wi-Fi savienojumu."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"jūsu mobilo sakaru operators"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vai pārslēgties atpakaļ uz operatoru <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilie dati netiks automātiski pārslēgti, pamatojoties uz pieejamību."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nē, paldies"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Jā, pārslēgties"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Lietotne Iestatījumi nevar verificēt jūsu atbildi, jo cita lietotne aizsedz atļaujas pieprasījumu."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vai atļaut lietotnei <xliff:g id="APP_0">%1$s</xliff:g> rādīt lietotnes <xliff:g id="APP_2">%2$s</xliff:g> sadaļas?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Var lasīt informāciju no lietotnes <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilie dati"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ir izveidots savienojums"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Īslaicīgi izveidots savienojums"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Vājš savienojums"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilo datu savienojums netiks veidots automātiski"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nav savienojuma"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nav pieejams neviens cits tīkls"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 454d58d..de4ae1e 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Гласовна помош"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Скенер на QR-кодови"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Отклучување"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Уредот е заклучен"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Скенирање лице"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Испрати"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Не се препознава ликот"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Користи отпечаток"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth е поврзан."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Процентот на батеријата е непознат."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Авионски режим."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN е вклучена."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Батерија <xliff:g id="NUMBER">%d</xliff:g> проценти."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батерија <xliff:g id="PERCENTAGE">%1$s</xliff:g> отсто, уште околу <xliff:g id="TIME">%2$s</xliff:g> според вашето користење"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Полнење на батеријата, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> отсто."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Видете ги сите известувања"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Овозможен е телепринтер."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ѕвонче на вибрации."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Кога споделувате, снимате или емитувате апликација, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со лозинки, детали за плаќање, пораки или други чувствителни податоци."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Продолжи"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Споделете или снимете апликација"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Да се дозволи апликацијава да споделува или снима?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Кога споделувате, снимате или емитувате, апликацијава има пристап до сѐ што е видливо на вашиот екран или пуштено на вашиот уред. Затоа, бидете внимателни со лозинки, детали за плаќање, пораки или други чувствителни податоци."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Кога споделувате, снимате или емитувате апликација, апликацијава има пристап до сѐ што се прикажува или пушта на таа апликација. Затоа, бидете внимателни со лозинки, детали за плаќање, пораки или други чувствителни податоци."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокирано од IT-администраторот"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимањето на екранот е оневозможено со правила на уредот"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Управувајте"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Отклучете за да користите"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Имаше проблем при преземањето на картичките. Обидете се повторно подоцна"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Поставки за заклучен екран"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Скенирајте QR-код"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"Скенер на QR-кодови"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Работен профил"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Авионски режим"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Нема да го слушнете следниот аларм <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Да се исклучи мобилниот интернет?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Нема да имате пристап до податоците или интернетот преку <xliff:g id="CARRIER">%s</xliff:g>. Интернетот ќе биде достапен само преку Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"вашиот оператор"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Да се префрли на <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобилниот интернет нема автоматски да се префрли според достапноста"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, фала"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да, префрли се"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Бидејќи апликацијата го прикрива барањето за дозвола, „Поставките“ не може да го потврдат вашиот одговор."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Да се дозволи <xliff:g id="APP_0">%1$s</xliff:g> да прикажува делови од <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Може да чита информации од <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилен интернет"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Поврзано"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Привремено поврзано"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Слаба интернет-врска"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобилниот интернет не може да се поврзе автоматски"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Нема интернет-врска"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Нема други достапни мрежи"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index cb9d469..e92ebea 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"വോയ്സ് സഹായം"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR കോഡ് സ്കാനർ"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"അണ്ലോക്ക് ചെയ്യുക"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"ഉപകരണം ലോക്ക് ചെയ്തു"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"മുഖം സ്കാൻ ചെയ്യുന്നു"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"അയയ്ക്കുക"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"മുഖം തിരിച്ചറിയാനാകുന്നില്ല"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"പകരം ഫിംഗർപ്രിന്റ് ഉപയോഗിക്കൂ"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ബ്ലൂടൂത്ത് കണക്റ്റുചെയ്തു."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ബാറ്ററി ശതമാനം അജ്ഞാതമാണ്."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"ഫ്ലൈറ്റ് മോഡ്."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ഓണാണ്."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"ബാറ്ററി <xliff:g id="NUMBER">%d</xliff:g> ശതമാനം."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ബാറ്ററി <xliff:g id="PERCENTAGE">%1$s</xliff:g> ശതമാനം, നിങ്ങളുടെ ഉപയോഗത്തിൻ്റെ അടിസ്ഥാനത്തിൽ ഏകദേശം <xliff:g id="TIME">%2$s</xliff:g> സമയം കൂടി ശേഷിക്കുന്നു"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ബാറ്ററി ചാർജ് ചെയ്യുന്നു, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"എല്ലാ അറിയിപ്പുകളും കാണുക"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter പ്രവർത്തനക്ഷമമാണ്."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"റിംഗർ വൈബ്രേറ്റ് ചെയ്യുന്നു."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> എന്നതിന് ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"തുടരുക"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ഒരു ആപ്പ് പങ്കിടുക അല്ലെങ്കിൽ റെക്കോർഡ് ചെയ്യുക"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"പങ്കിടാനോ റെക്കോർഡ് ചെയ്യാനോ ഈ ആപ്പിനെ അനുവദിക്കണോ?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, ഈ ആപ്പിന് നിങ്ങളുടെ സ്ക്രീനിൽ ദൃശ്യമാകുന്നതോ ഉപകരണത്തിൽ പ്ലേ ചെയ്യുന്നതോ ആയ ഏത് കാര്യത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ഒരു ആപ്പ് പങ്കിടുമ്പോൾ, റെക്കോർഡ് ചെയ്യുമ്പോൾ അല്ലെങ്കിൽ കാസ്റ്റ് ചെയ്യുമ്പോൾ, ഈ ആപ്പിന് ആപ്പിൽ കാണിക്കുന്ന അല്ലെങ്കിൽ പ്ലേ ചെയ്യുന്ന എല്ലാത്തിലേക്കും ആക്സസ് ഉണ്ട്. അതിനാൽ, പാസ്വേഡുകൾ, പേയ്മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ അല്ലെങ്കിൽ സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട മറ്റു വിവരങ്ങൾ എന്നിവ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"നിങ്ങളുടെ ഐടി അഡ്മിൻ ബ്ലോക്ക് ചെയ്തു"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ഉപകരണ നയം, സ്ക്രീൻ ക്യാപ്ചർ ചെയ്യൽ പ്രവർത്തനരഹിതമാക്കി"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്ക്കുക"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"മാനേജ് ചെയ്യുക"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ചരിത്രം"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ഉപയോഗിക്കാൻ അൺലോക്ക് ചെയ്യുക"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"നിങ്ങളുടെ കാർഡുകൾ ലഭ്യമാക്കുന്നതിൽ ഒരു പ്രശ്നമുണ്ടായി, പിന്നീട് വീണ്ടും ശ്രമിക്കുക"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ലോക്ക് സ്ക്രീൻ ക്രമീകരണം"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR കോഡ് സ്കാൻ ചെയ്യുക"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ഫ്ലൈറ്റ് മോഡ്"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-നുള്ള നിങ്ങളുടെ അടുത്ത അലാറം കേൾക്കില്ല"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"മൊബൈൽ ഡാറ്റ ഓഫാക്കണോ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"നിങ്ങൾക്ക് ഡാറ്റയിലേക്ക് ആക്സസോ അല്ലെങ്കിൽ <xliff:g id="CARRIER">%s</xliff:g> മുഖേനയുള്ള ഇന്റർനെറ്റോ ഉണ്ടാകില്ല. വൈഫൈ മുഖേന മാത്രമായിരിക്കും ഇന്റർനെറ്റ് ലഭ്യത."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"നിങ്ങളുടെ കാരിയർ"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> എന്നതിലേക്ക് വീണ്ടും മാറണോ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ലഭ്യതയുടെ അടിസ്ഥാനത്തിൽ, മൊബൈൽ ഡാറ്റ സ്വയമേവ മാറില്ല"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"വേണ്ട"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ഉവ്വ്, മാറുക"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"അനുമതി അഭ്യർത്ഥനയെ ഒരു ആപ്പ് മറയ്ക്കുന്നതിനാൽ, ക്രമീകരണത്തിന് നിങ്ങളുടെ പ്രതികരണം പരിശോധിച്ചുറപ്പിക്കാനാകില്ല."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP_0">%1$s</xliff:g>-നെ അനുവദിക്കണോ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ഇതിന് <xliff:g id="APP">%1$s</xliff:g>-ൽ നിന്ന് വിവരങ്ങൾ വായിക്കാനാകും"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"മൊബൈൽ ഡാറ്റ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"കണക്റ്റ് ചെയ്തു"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"താൽക്കാലികമായി കണക്റ്റ് ചെയ്തിരിക്കുന്നു"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ദുർബലമായ കണക്ഷൻ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"മൊബൈൽ ഡാറ്റ സ്വയം കണക്റ്റ് ചെയ്യില്ല"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"കണക്ഷനില്ല"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"മറ്റ് നെറ്റ്വർക്കുകളൊന്നും ലഭ്യമല്ല"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 6e5421c..005a041 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Дуут туслах"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR код сканнер"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Тайлах"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Төхөөрөмжийг түгжсэн"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Скан хийх нүүр царай"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Илгээх"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Царайг танихгүй байна"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Оронд нь хурууны хээ ашиглах"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth холбогдсон."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Батарейн хувь тодорхойгүй байна."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Нислэгийн горим"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN асаалттай байна."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Батарей <xliff:g id="NUMBER">%d</xliff:g> хувьтай."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батарей <xliff:g id="PERCENTAGE">%1$s</xliff:g> хувьтай байна. Таны хэрэглээнд тулгуурлан ойролцоогоор <xliff:g id="TIME">%2$s</xliff:g> үлдсэн"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Батарейг цэнэглэж байна, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Бүх мэдэгдлийг харах"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter идэвхтэй болов."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Хонхны чичиргээ."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Таныг хуваалцаж, бичиж эсвэл дамжуулж байх үед <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> нь тухайн апп дээр харуулсан эсвэл тоглуулсан аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж эсвэл бусад эмзэг мэдээлэлд болгоомжтой хандаарай."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Үргэлжлүүлэх"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Хуваалцах эсвэл бичих апп"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Энэ аппад хуваалцах эсвэл бичихийг зөвшөөрөх үү?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Таныг хуваалцаж, бичиж эсвэл дамжуулж байх үед энэ апп нь таны дэлгэц дээр харагдаж буй аливаа зүйл эсвэл төхөөрөмж дээр тань тоглуулж буй зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж эсвэл бусад эмзэг мэдээлэлд болгоомжтой хандаарай."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Таныг хуваалцаж, бичиж эсвэл дамжуулж байх үед энэ апп нь тухайн апп дээр харуулж эсвэл тоглуулж буй аливаа зүйлд хандах эрхтэй. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж эсвэл бусад эмзэг мэдээлэлд болгоомжтой хандаарай."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Таны IT админ блоклосон"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Төхөөрөмжийн бодлогоор дэлгэцийн зураг авахыг идэвхгүй болгосон"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Удирдах"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Түүх"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ашиглахын тулд түгжээг тайлах"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Таны картыг авахад асуудал гарлаа. Дараа дахин оролдоно уу"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Түгжигдсэн дэлгэцийн тохиргоо"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR код скан хийх"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR код сканнер"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Ажлын профайл"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Нислэгийн горим"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>-т та дараагийн сэрүүлгээ сонсохгүй"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобайл датаг унтраах уу?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Та <xliff:g id="CARRIER">%s</xliff:g>-р дата эсвэл интернэтэд хандах боломжгүй болно. Интернэтэд зөвхөн Wi-Fi-р холбогдох боломжтой болно."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"таны оператор компани"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> руу буцаан сэлгэх үү?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобайл дата нь боломжтой эсэхэд тулгуурлан автоматаар сэлгэхгүй"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Үгүй, баярлалаа"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Тийм, сэлгэе"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Апп нь зөвшөөрлийн хүсэлтийг танихгүй байгаа тул Тохиргооноос таны хариултыг баталгаажуулах боломжгүй байна."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>-д <xliff:g id="APP_2">%2$s</xliff:g>-н хэсгүүдийг (slices) харуулахыг зөвшөөрөх үү?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Энэ нь <xliff:g id="APP">%1$s</xliff:g>-с мэдээлэл унших боломжтой"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобайл дата"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Холбогдсон"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Түр зуур холбогдсон"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Холболт сул байна"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобайл дата автоматаар холбогдохгүй"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Холболт алга"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Өөр боломжтой сүлжээ байхгүй байна"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index d3723c2..2810cb7 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"व्हॉइस सहाय्य"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"वॉलेट"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR कोड स्कॅनर"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"अनलॉक करा"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"डिव्हाइस लॉक केले"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"चेहरा स्कॅन करत आहे"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"पाठवा"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"चेहरा ओळखू शकत नाही"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"त्याऐवजी फिंगरप्रिंट वापरा"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लूटूथ कनेक्ट केले."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"बॅटरीच्या चार्जिंगची टक्केवारी माहित नाही."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"विमान मोड."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN सुरू."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"बॅटरी <xliff:g id="NUMBER">%d</xliff:g> टक्के."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"बॅटरी <xliff:g id="PERCENTAGE">%1$s</xliff:g> टक्के, तुमच्या वापराच्या आधारावर सुमारे <xliff:g id="TIME">%2$s</xliff:g> शिल्लक आहे"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"बॅटरी चार्ज होत आहे, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> टक्के."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"सर्व सूचना पहा"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter सक्षम केले."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"रिंगर व्हायब्रेट."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तुम्ही अॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ला त्या अॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"पुढे सुरू ठेवा"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"अॅप शेअर किंवा रेकॉर्ड करा"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"या अॅपला शेअर किंवा रेकॉर्ड करण्याची अनुमती द्यायची आहे का?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"तुम्ही शेअर, रेकॉर्ड किंवा कास्ट करत असता, तेव्हा या अॅपला तुमच्या स्क्रीनवर दाखवलेल्या अथवा डिव्हाइसवर प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज किंवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"तुम्ही अॅप शेअर, रेकॉर्ड किंवा कास्ट करत असताना, या अॅपला त्या अॅपवर दाखवलेल्या किंवा प्ले केलेल्या कोणत्याही गोष्टीचा अॅक्सेस असतो. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज अथवा इतर संवेदनशील माहिती काळजीपूर्वक वापरा."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तुमच्या आयटी ॲडमिनने ब्लॉक केले आहे"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिव्हाइस धोरणाने स्क्रीन कॅप्चर करणे बंद केले आहे"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"वापरण्यासाठी अनलॉक करा"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"तुमची कार्ड मिळवताना समस्या आली, कृपया नंतर पुन्हा प्रयत्न करा"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लॉक स्क्रीन सेटिंग्ज"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR कोड स्कॅन करा"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"कार्य प्रोफाईल"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"विमान मोड"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"तुम्ही तुमचा <xliff:g id="WHEN">%1$s</xliff:g> वाजता होणारा पुढील अलार्म ऐकणार नाही"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा बंद करायचा?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"तुम्हाला <xliff:g id="CARRIER">%s</xliff:g> मधून डेटा किंवा इंटरनेटचा अॅक्सेस नसेल. इंटरनेट फक्त वाय-फाय मार्फत उपलब्ध असेल."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"तुमचा वाहक"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> वर परत स्विच करायचे आहे का?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"उपलब्धतेच्या आधारावर मोबाइल डेटा आपोआप स्विच होणार नाही"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"नाही, नको"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"होय, स्विच करा"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"अॅप परवानगी विनंती अस्पष्ट करत असल्याने, सेटिंग्ज तुमचा प्रतिसाद पडताळू शकत नाहीत."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ला <xliff:g id="APP_2">%2$s</xliff:g> चे तुकडे दाखवण्याची अनुमती द्यायची का?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ते <xliff:g id="APP">%1$s</xliff:g> ची माहिती वाचू शकते"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"कनेक्ट केले आहे"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"तात्पुरते कनेक्ट केलेले"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"खराब कनेक्शन"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा ऑटो-कनेक्ट होणार नाही"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"कोणतेही कनेक्शन नाही"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"इतर कोणतेही नेटवर्क उपलब्ध नाहीत"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index b9bbbb3..1d82ec0 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Bantuan Suara"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Pengimbas Kod QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Buka kunci"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Peranti dikunci"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Mengimbas wajah"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Hantar"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Tak dapat mengecam wajah"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gunakan cap jari"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth disambungkan."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Peratusan kuasa bateri tidak diketahui."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mod pesawat"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN dihidupkan."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Bateri <xliff:g id="NUMBER">%d</xliff:g> peratus."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateri <xliff:g id="PERCENTAGE">%1$s</xliff:g> peratus, tinggal kira-kira <xliff:g id="TIME">%2$s</xliff:g> lagi berdasarkan penggunaan anda"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateri mengecas, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> peratus."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Lihat semua pemberitahuan"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Mesin Teletaip didayakan."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Pendering bergetar."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Apabila anda berkongsi, merakam atau menghantar apl, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> mempunyai akses kepada apa-apa yang dipaparkan atau dimainkan pada apl tersebut. Jadi berhati-hati dengan kata laluan, butiran pembayaran, mesej atau maklumat sensitif lain."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Teruskan"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Kongsi atau rakam apl"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Benarkan apl ini berkongsi atau merakam?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Apabila anda berkongsi, merakam atau menghantar, apl ini mempunyai akses kepada apa-apa yang boleh dilihat pada skrin anda atau dimainkan pada peranti anda. Jadi berhati-hati dengan kata laluan, butiran pembayaran, mesej atau maklumat sensitif lain."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Apabila anda berkongsi, merakam atau menghantar apl, apl ini mempunyai akses kepada apa-apa yang dipaparkan atau dimainkan pada apl tersebut. Jadi berhati-hati dengan kata laluan, butiran pembayaran, mesej atau maklumat sensitif lain."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Disekat oleh pentadbir IT anda"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tangkapan skrin dilumpuhkan oleh dasar peranti"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Kosongkan semua"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Urus"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Sejarah"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Buka kunci untuk menggunakan"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Terdapat masalah sewaktu mendapatkan kad anda. Sila cuba sebentar lagi"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Tetapan skrin kunci"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Imbas kod QR"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"Pengimbas kod QR"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profil kerja"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mod pesawat"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Anda tidak akan mendengar penggera yang seterusnya <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Matikan data mudah alih?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Anda tidak akan mempunyai akses kepada data atau Internet melalui <xliff:g id="CARRIER">%s</xliff:g>. Internet hanya tersedia melaui Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"pembawa anda"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Tukar kembali kepada <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Data mudah alih tidak akan ditukar secara automatik berdasarkan ketersediaan"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Tidak perlu"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ya, tukar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Oleh sebab apl melindungi permintaan kebenaran, Tetapan tidak dapat mengesahkan jawapan anda."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Benarkan <xliff:g id="APP_0">%1$s</xliff:g> menunjukkan <xliff:g id="APP_2">%2$s</xliff:g> hirisan?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Hos hirisan boleh membaca maklumat daripada <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Data mudah alih"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Disambungkan"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Disambungkan buat sementara waktu"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Sambungan lemah"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Data mudah alih tidak akan autosambung"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Tiada sambungan"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Tiada rangkaian lain yang tersedia"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 69f45be..f79e32f 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"အသံ အကူအညီ"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR ကုဒ်ဖတ်စနစ်"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"သော့ဖွင့်ရန်"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"စက်ပစ္စည်းကို လော့ခ်ချထားသည်"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"မျက်နှာ စကင်ဖတ်နေသည်"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ပို့ရန်"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"မျက်နှာကို မမှတ်မိပါ"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"လက်ဗွေကို အစားထိုးသုံးပါ"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ဘလူးတုသ်ချိတ်ဆက်ထားမှု"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ဘက်ထရီရာခိုင်နှုန်းကို မသိပါ။"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"လေယာဉ်ပျံမုဒ်"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ကို ဖွင့်ထားသည်။"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"ဘက်ထရီ <xliff:g id="NUMBER">%d</xliff:g> ရာခိုင်နှုန်း။"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ဘက်ထရီ <xliff:g id="PERCENTAGE">%1$s</xliff:g> ရာခိုင်နှုန်း၊ သင်၏ အသုံးပြုမှုအပေါ် မူတည်၍ <xliff:g id="TIME">%2$s</xliff:g> ခန့်ကျန်သည်"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ဘက်ထရီအားသွင်းနေသည်၊ <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%။"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"သတိပေးချက်များအားလုံးကို ကြည့်ရန်"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter ရရှိသည်။"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"တုန်ခါခြင်း ဖုန်းမြည်သံ"</string>
@@ -215,7 +221,7 @@
<string name="accessibility_rotation_lock_on_landscape" msgid="936972553861524360">"ဖန်သားပြင် အနေအထားက အလျားလိုက်အဖြစ် ပုံသေ လုပ်ထားပါသည်"</string>
<string name="accessibility_rotation_lock_on_portrait" msgid="2356633398683813837">"ဖန်သားပြင် အနေအထားက ဒေါင်လိုက်အဖြစ် ပုံသေ လုပ်ထားပါသည်"</string>
<string name="dessert_case" msgid="9104973640704357717">"မုန့်ထည့်သော ပုံး"</string>
- <string name="start_dreams" msgid="9131802557946276718">"ဖန်သားပြင်အသုံးပြုမှု ချွေတာမှုစနစ်"</string>
+ <string name="start_dreams" msgid="9131802557946276718">"စခရင်နားချိန်"</string>
<string name="ethernet_label" msgid="2203544727007463351">"အီသာနက်"</string>
<string name="quick_settings_dnd_label" msgid="7728690179108024338">"မနှောင့်ယှက်ရ"</string>
<string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"ဘလူးတုသ်"</string>
@@ -229,7 +235,7 @@
<string name="quick_settings_rotation_unlocked_label" msgid="2359922767950346112">"အော်တို-လည်"</string>
<string name="accessibility_quick_settings_rotation" msgid="4800050198392260738">"မျက်နှာပြင်အား အလိုအလျောက်လှည့်ခြင်း"</string>
<string name="quick_settings_location_label" msgid="2621868789013389163">"တည်နေရာ"</string>
- <string name="quick_settings_screensaver_label" msgid="1495003469366524120">"စကရင်ချွေတာစနစ်"</string>
+ <string name="quick_settings_screensaver_label" msgid="1495003469366524120">"စကရင်နားချိန်"</string>
<string name="quick_settings_camera_label" msgid="5612076679385269339">"ကင်မရာသုံးခွင့်"</string>
<string name="quick_settings_mic_label" msgid="8392773746295266375">"မိုက်သုံးခွင့်"</string>
<string name="quick_settings_camera_mic_available" msgid="1453719768420394314">"ရနိုင်သည်"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"အက်ပ်ဖြင့် မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> သည် ၎င်းအက်ပ်တွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားအရေးကြီးအချက်အလက်များနှင့်ပတ်သက်၍ ဂရုစိုက်ပါ။"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ရှေ့ဆက်ရန်"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"အက်ပ် မျှဝေခြင်း (သို့) ရိုက်ကူးခြင်း"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ဤအက်ပ်ကို မျှဝေ (သို့) ရိုက်ကူး ခွင့်ပြုမလား။"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ ဤအက်ပ်သည် သင့်ဖန်သားပြင်ရှိ မြင်နိုင်သည့် (သို့) သင့်စက်တွင် ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားသတိထားရမည့် အချက်အလက်များနှင့် ပတ်သက်၍ သတိပြုပါ။"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"အက်ပ်ဖြင့် မျှဝေ၊ ရိုက်ကူး (သို့) ကာစ်လုပ်သည့်အခါ ဤအက်ပ်သည် ၎င်းတွင် ပြထားသည့် (သို့) ဖွင့်ထားသည့် အရာအားလုံးကို တွေ့နိုင်သည်။ ထို့ကြောင့် စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ် (သို့) အခြားသတိထားရမည့် အချက်အလက်များနှင့်ပတ်သက်၍ သတိပြုပါ။"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"သင်၏ IT စီမံခန့်ခွဲသူက ပိတ်ထားသည်"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ကိရိယာဆိုင်ရာ မူဝါဒက ဖန်သားပြင်ပုံဖမ်းခြင်းကို ပိတ်ထားသည်"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"အားလုံးရှင်းရန်"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"စီမံရန်"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"မှတ်တမ်း"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"သုံးရန် လော့ခ်ဖွင့်ပါ"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"သင်၏ကတ်များ ရယူရာတွင် ပြဿနာရှိနေသည်၊ နောက်မှ ထပ်စမ်းကြည့်ပါ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"လော့ခ်မျက်နှာပြင် ဆက်တင်များ"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR ကုဒ် စကင်ဖတ်ခြင်း"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"အလုပ် ပရိုဖိုင်"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"လေယာဉ်ပျံမုဒ်"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> ၌သင့်နောက်ထပ် နှိုးစက်ကို ကြားမည်မဟုတ်ပါ"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"မိုဘိုင်းဒေတာ ပိတ်မလား။"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> မှတစ်ဆင့် ဒေတာ သို့မဟုတ် အင်တာနက်ကို မသုံးနိုင်ပါ။ Wi-Fi ဖြင့်သာ အင်တာနက် သုံးနိုင်သည်။"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"သင်၏ ဝန်ဆောင်မှုပေးသူ"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> သို့ ပြန်ပြောင်းမလား။"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ရနိုင်မှုပေါ် အခြေခံပြီး မိုဘိုင်းဒေတာကို အလိုအလျောက် မပြောင်းပါ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"မလိုပါ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ပြောင်းရန်"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"အပလီကေးရှင်းတစ်ခုက ခွင့်ပြုချက်တောင်းခံမှုကို ပိတ်ထားသောကြောင့် ဆက်တင်များသည် သင်၏ လုပ်ဆောင်ကို တုံ့ပြန်နိုင်ခြင်းမရှိပါ။"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> အား <xliff:g id="APP_2">%2$s</xliff:g> ၏အချပ်များ ပြသခွင့်ပြုပါသလား။"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ၎င်းသည် <xliff:g id="APP">%1$s</xliff:g> မှ အချက်အလက်ကို ဖတ်နိုင်သည်"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"မိုဘိုင်းဒေတာ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ချိတ်ဆက်ထားသည်"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ယာယီချိတ်ဆက်ထားသည်"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ချိတ်ဆက်မှုအားနည်းသည်"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"မိုဘိုင်းဒေတာ အော်တိုမချိတ်ပါ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ချိတ်ဆက်မှုမရှိပါ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"အခြားကွန်ရက်များ မရှိပါ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 7a1b05c..bbd5dc9 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Talehjelp"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-kodeskanner"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Lås opp"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Enheten er låst"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Skanning av ansikt"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Send"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Ansiktet gjenkjennes ikke"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Bruk fingeravtrykk"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth er tilkoblet."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batteriprosenten er ukjent."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Flymodus."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN på."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Batteri – <xliff:g id="NUMBER">%d</xliff:g> prosent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batterinivået er <xliff:g id="PERCENTAGE">%1$s</xliff:g> prosent – omtrent <xliff:g id="TIME">%2$s</xliff:g> gjenstår basert på bruken din"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batteriet lades – <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> prosent."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Se alle varslene"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter er aktivert."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibreringsmodus."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Når du deler, tar opp eller caster en app, har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsett"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Del eller ta opp en app"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vil du tillate at denne appen deler eller tar opp?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Når du deler, tar opp eller caster noe, har denne appen tilgang til alt som vises på skjermen eller spilles av på enheten. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Når du deler, tar opp eller caster en app, har denne appen tilgang til alt som vises eller spilles av i den aktuelle appen. Derfor bør du være forsiktig med passord, betalingsopplysninger, meldinger og annen sensitiv informasjon."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokkert av IT-administratoren"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skjermdumper er deaktivert av enhetsinnstillingene"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Fjern alt"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Logg"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås opp for å bruke"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Det oppsto et problem med henting av kortene. Prøv på nytt senere"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Innstillinger for låseskjermen"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skann QR-koden"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Work-profil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Flymodus"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Du hører ikke neste innstilte alarm <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vil du slå av mobildata?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Du får ikke tilgang til data eller internett via <xliff:g id="CARRIER">%s</xliff:g>. Internett er bare tilgjengelig via Wifi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatøren din"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vil du bytte tilbake til <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Det byttes ikke mobildataoperatør automatisk basert på tilgjengelighet"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nei takk"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, bytt"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Fordi en app skjuler tillatelsesforespørselen, kan ikke Innstillinger bekrefte svaret ditt."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vil du tillate at <xliff:g id="APP_0">%1$s</xliff:g> viser <xliff:g id="APP_2">%2$s</xliff:g>-utsnitt?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Den kan lese informasjon fra <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Tilkoblet"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Koblet til midlertidig"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Dårlig forbindelse"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobildata kobler ikke til automatisk"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Ingen tilkobling"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ingen andre nettverk er tilgjengelige"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index df70c0d..3baf1de 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"आवाज सहायता"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR कोड स्क्यानर"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"खोल्नुहोस्"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"यन्त्र लक गरिएको छ"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"अनुहार स्क्यान गर्दै"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"पठाउनुहोस्"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"अनुहार पहिचान गर्न सकिएन"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लुटुथ जडान भयो।"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ब्याट्रीमा कति प्रतिशत चार्ज छ भन्ने कुराको जानाकरी छैन।"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"हवाइजहाज मोड।"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN सक्रिय छ।"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"ब्याट्री <xliff:g id="NUMBER">%d</xliff:g> प्रतिशत"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ब्याट्रीको चार्ज <xliff:g id="PERCENTAGE">%1$s</xliff:g> प्रतिशत छ, तपाईंको प्रयोगका आधारमा <xliff:g id="TIME">%2$s</xliff:g> बाँकी छ"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ब्याट्री चार्ज हुँदैछ, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> प्रतिशत भयो।"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"सबै सूचनाहरू हेर्नुहोस्"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"टेलि टाइपराइटर सक्षम गरियो।"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"बज्ने कम्पन हुन्छ।"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"जारी राख्नुहोस्"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"सेयर वा रेकर्ड गर्नका लागि एप चयन गर्नुहोस्"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"यो एपलाई सेयर गर्न वा रेकर्ड गर्न दिने हो?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"यो एपले तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले रेकर्ड गर्दा पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"तपाईंले सेयर गर्दा, रेकर्ड गर्दा वा कास्ट गर्दा यो एपले तपाईंको स्क्रिनमा देखिने वा डिभाइसमा प्ले गरिएका सबै कुरा खिच्न सक्छ। त्यसैले पासवर्ड, भुक्तानीको विवरण, म्यासेज वा अन्य संवेदनशील जानकारी सुरक्षित राख्नुहोला।"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"तपाईंका सूचना प्रविधि व्यवस्थापकले ब्लक गर्नुभएको छ"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"डिभाइसको नीतिका कारण स्क्रिन क्याप्चर गर्ने सुविधा अफ गरिएको छ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"सबै हटाउनुहोस्"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थित गर्नुहोस्"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"यो वालेट प्रयोग गर्न डिभाइस अनलक गर्नुहोस्"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"तपाईंका कार्डहरू प्राप्त गर्ने क्रममा समस्या भयो, कृपया पछि फेरि प्रयास गर्नुहोस्"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"लक स्क्रिनसम्बन्धी सेटिङ"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR कोड स्क्यान गर्नुहोस्"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"कार्य प्रोफाइल"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"हवाइजहाज मोड"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"तपाईँले आफ्नो अर्को अलार्म <xliff:g id="WHEN">%1$s</xliff:g> सुन्नुहुने छैन"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा निष्क्रिय पार्ने हो?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"तपाईं <xliff:g id="CARRIER">%s</xliff:g> मार्फत डेटा वा इन्टरनेट प्रयोग गर्न सक्नुहुने छैन। Wi-Fi मार्फत मात्र इन्टरनेट उपलब्ध हुने छ।"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"तपाईंको सेवा प्रदायक"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"फेरि <xliff:g id="CARRIER">%s</xliff:g> को मोबाइल डेटा अन गर्ने हो?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"मोबाइल डेटा उपलब्धताका आधारमा स्वतः बदलिँदैन"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"पर्दैन, धन्यवाद"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"हुन्छ, बदल्नुहोस्"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"कुनै एपको कारणले अनुमतिसम्बन्धी अनुरोध बुझ्न गाह्रो भइरहेकोले सेटिङहरूले तपाईंको प्रतिक्रिया प्रमाणित गर्न सक्दैनन्।"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> लाई <xliff:g id="APP_2">%2$s</xliff:g> का स्लाइसहरू देखाउन अनुमति दिने हो?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- यसले <xliff:g id="APP">%1$s</xliff:g> बाट जानकारी पढ्न सक्छ"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"इन्टरनेटमा कनेक्ट गरिएको छ"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"केही समयका लागि मोबाइल डेटामा कनेक्ट गरिएको छ"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"इन्टरनेट राम्री चलेको छैन"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा स्वतः कनेक्ट हुँदैन"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"इन्टरनेट छैन"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"अन्य नेटवर्क उपलब्ध छैनन्"</string>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 6f87169..99bc794 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -24,11 +24,6 @@
<item name="android:windowIsFloating">true</item>
</style>
- <style name="TextAppearance.QS.Status" parent="TextAppearance.QS.TileLabel.Secondary">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
- <item name="android:textColor">?android:attr/textColorPrimary</item>
- </style>
-
<!-- Screenshots -->
<style name="LongScreenshotActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="android:windowNoTitle">true</item>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 1debee4..84d5b6d 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Spraakassistent"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Portemonnee"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-codescanner"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Ontgrendelen"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Apparaat vergrendeld"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Gezicht scannen"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Verzenden"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Gezicht niet herkend"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Vingerafdruk gebruiken"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-verbinding ingesteld."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batterijpercentage onbekend."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Vliegtuigmodus."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN staat aan."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Batterij: <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batterij op <xliff:g id="PERCENTAGE">%1$s</xliff:g> procent, nog ongeveer <xliff:g id="TIME">%2$s</xliff:g> op basis van je gebruik"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batterij wordt opgeladen, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%% procent."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Alle meldingen bekijken"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter staat aan."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Belsoftware trilt."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Als je deelt, opneemt of cast, heeft <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met wachtwoorden, betalingsgegevens, berichten en andere gevoelige informatie."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Doorgaan"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"App delen of opnemen"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Deze app toestaan om te delen of op te nemen?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Als je deelt, opneemt of cast, heeft deze app toegang tot alles dat zichtbaar is op je scherm of wordt afgespeeld op je apparaat. Wees daarom voorzichtig met wachtwoorden, betalingsgegevens, berichten en andere gevoelige informatie."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Als je deelt, opneemt of cast, heeft deze app toegang tot alles dat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met wachtwoorden, betalingsgegevens, berichten en andere gevoelige informatie."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Geblokkeerd door je IT-beheerder"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Schermopname staat uit vanwege apparaatbeleid"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Beheren"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Geschiedenis"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Ontgrendelen om te gebruiken"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Er is een probleem opgetreden bij het ophalen van je kaarten. Probeer het later opnieuw."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Instellingen voor vergrendelscherm"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR-code scannen"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Werkprofiel"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Vliegtuigmodus"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Je hoort je volgende wekker niet <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobiele data uitzetten?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Je hebt dan geen toegang meer tot data of internet via <xliff:g id="CARRIER">%s</xliff:g>. Internet is alleen nog beschikbaar via wifi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"je provider"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Terugschakelen naar <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobiele data worden niet automatisch overgezet op basis van beschikbaarheid"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nee, bedankt"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, overschakelen"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Aangezien een app een rechtenverzoek afdekt, kan Instellingen je reactie niet verifiëren."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> toestaan om segmenten van <xliff:g id="APP_2">%2$s</xliff:g> te tonen?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Deze kan informatie lezen van <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiele data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Verbonden"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tijdelijk verbonden"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Matige verbinding"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiele data maakt niet automatisch verbinding"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Geen verbinding"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Geen andere netwerken beschikbaar"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index b444228..433170e 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ଭଏସ୍ ସହାୟକ"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"ୱାଲେଟ୍"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR କୋଡ ସ୍କାନର"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"ଅନଲକ୍ କରନ୍ତୁ"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"ଡିଭାଇସ୍ ଲକ୍ ହୋଇଯାଇଛି"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"ଫେସ୍ ସ୍କାନିଙ୍ଗ କରାଯାଉଛି"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ପଠାନ୍ତୁ"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ଫେସ ଚିହ୍ନଟ ହୋଇପାରିବ ନାହିଁ"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ଟିପଚିହ୍ନ ବ୍ୟବହାର କରନ୍ତୁ"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ବ୍ଲୁଟୂଥ୍ ସଂଯୋଗ କରାଯାଇଛି।"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ବ୍ୟାଟେରୀ ଶତକଡ଼ା ଅଜଣା ଅଟେ।"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"ଏରୋପ୍ଲେନ୍ ମୋଡ୍।"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ଅନ୍।"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"ବ୍ୟାଟେରୀ <xliff:g id="NUMBER">%d</xliff:g> ଶତକଡ଼ା।"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ବ୍ୟାଟେରୀ <xliff:g id="PERCENTAGE">%1$s</xliff:g> ଶତକଡା, ଆପଣଙ୍କର ବ୍ୟବହାରକୁ ଆଧାର କରି ପାଖାପାଖି <xliff:g id="TIME">%2$s</xliff:g> ବାକି ଅଛି"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ବ୍ୟାଟେରୀ ଚାର୍ଜ ହେଉଛି, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> ଶତକଡ଼ା।"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"ସମସ୍ତ ବିଜ୍ଞପ୍ତି ଦେଖନ୍ତୁ"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"ଟେଲି-ଟାଇପରାଇଟର୍ ସକ୍ଷମ ଅଛି।"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ରିଙ୍ଗର୍ କମ୍ପନରେ ଅଛି।"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ଆପଣ ସେୟାର, ରେକର୍ଡ କିମ୍ବା କାଷ୍ଟ କରିବା ସମୟରେ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ ସେହି ଆପର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ଜାରି ରଖନ୍ତୁ"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ଏକ ଆପକୁ ସେୟାର କିମ୍ବା ରେକର୍ଡ କରନ୍ତୁ"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ସେୟାର କିମ୍ବା ରେକର୍ଡ କରିବା ପାଇଁ ଏହି ଆପକୁ ଅନୁମତି ଦେବେ?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ଆପଣ ସେୟାର, ରେକର୍ଡ ବା କାଷ୍ଟ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ଆପଣଙ୍କ ଡିଭାଇସରେ ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ ଏହି ଆପର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ଆପଣ ସେୟାର, ରେକର୍ଡ ବା କାଷ୍ଟ କରିବା ସମୟରେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛିକୁ ଏହି ଆପର ଆକ୍ସେସ ଅଛି। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ କିମ୍ବା ଅନ୍ୟ ସମ୍ବେଦନଶୀଳ ସୂଚନା ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ଆପଣଙ୍କ IT ଆଡମିନଙ୍କ ଦ୍ୱାରା ବ୍ଲକ କରାଯାଇଛି"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ଡିଭାଇସ ନୀତି ଦ୍ୱାରା ସ୍କ୍ରିନ କେପଚରିଂକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ସମସ୍ତ ଖାଲି କରନ୍ତୁ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ପରିଚାଳନା କରନ୍ତୁ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ଇତିହାସ"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ବ୍ୟବହାର କରିବାକୁ ଅନଲକ୍ କରନ୍ତୁ"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"ଆପଣଙ୍କ କାର୍ଡଗୁଡ଼ିକ ପାଇବାରେ ଏକ ସମସ୍ୟା ହୋଇଥିଲା। ଦୟାକରି ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ସ୍କ୍ରିନ୍ ଲକ୍ ସେଟିଂସ୍"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR କୋଡ ସ୍କାନ କରନ୍ତୁ"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR କୋଡ ସ୍କାନର"</string>
<string name="status_bar_work" msgid="5238641949837091056">"ୱର୍କ ପ୍ରୋଫାଇଲ୍"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ଏରୋପ୍ଲେନ୍ ମୋଡ୍"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g>ବେଳେ ଆପଣ ନିଜର ପରବର୍ତ୍ତୀ ଆଲାର୍ମ ଶୁଣିପାରିବେ ନାହିଁ"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ମୋବାଇଲ୍ ଡାଟା ବନ୍ଦ କରିବେ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"ଡାଟା କିମ୍ବା ଇଣ୍ଟରନେଟ୍କୁ <xliff:g id="CARRIER">%s</xliff:g> ଦ୍ଵାରା ଆପଣଙ୍କର ଆକ୍ସେସ୍ ରହିବ ନାହିଁ। ଇଣ୍ଟରନେଟ୍ କେବଳ ୱାଇ-ଫାଇ ମାଧ୍ୟମରେ ଉପଲବ୍ଧ ହେବ।"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ଆପଣଙ୍କ କେରିଅର୍"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g>କୁ ପୁଣି ସ୍ୱିଚ କରିବେ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ଉପଲବ୍ଧତା ଆଧାରରେ ମୋବାଇଲ ଡାଟା ସ୍ୱଚାଳିତ ଭାବେ ସ୍ୱିଚ ହେବ ନାହିଁ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ନା, ଧନ୍ୟବାଦ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ହଁ, ସ୍ୱିଚ କରନ୍ତୁ"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ଗୋଟିଏ ଆପ୍ ଏକ ଅନୁମତି ଅନୁରୋଧକୁ ଦେଖିବାରେ ବାଧା ଦେଉଥିବାରୁ, ସେଟିଙ୍ଗ ଆପଣଙ୍କ ଉତ୍ତରକୁ ଯାଞ୍ଚ କରିପାରିବ ନାହିଁ।"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> ସ୍ଲାଇସ୍କୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP_0">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ଏହା <xliff:g id="APP">%1$s</xliff:g>ରୁ ସୂଚନାକୁ ପଢ଼ିପାରିବ"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ମୋବାଇଲ ଡାଟା"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ସଂଯୋଗ କରାଯାଇଛି"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ଅସ୍ଥାୟୀ ରୂପେ କନେକ୍ଟ କରାଯାଇଛି"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ଦୁର୍ବଳ କନେକ୍ସନ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ମୋବାଇଲ ଡାଟା ସ୍ୱତଃ-ସଂଯୋଗ ହେବ ନାହିଁ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ସଂଯୋଗ ନାହିଁ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ଅନ୍ୟ କୌଣସି ନେଟୱାର୍କ ଉପଲବ୍ଧ ନାହିଁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 1633c3b..ed4988f 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ਅਵਾਜ਼ੀ ਸਹਾਇਕ"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR ਕੋਡ ਸਕੈਨਰ"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"ਅਣਲਾਕ ਕਰੋ"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"ਡੀਵਾਈਸ ਲਾਕ ਹੈ"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"ਚਿਹਰਾ ਸਕੈਨ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ਭੇਜੋ"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ਚਿਹਰੇ ਦੀ ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ਇਸਦੀ ਬਜਾਏ ਫਿੰਗਰਪ੍ਰਿੰਟ ਵਰਤੋ"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ਕਨੈਕਟ ਕੀਤੀ।"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ਬੈਟਰੀ ਪ੍ਰਤੀਸ਼ਤ ਅਗਿਆਤ ਹੈ।"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"ਏਅਰਪਲੇਨ ਮੋਡ।"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ਚਾਲੂ ਹੈ।"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"ਬੈਟਰੀ <xliff:g id="NUMBER">%d</xliff:g> ਪ੍ਰਤੀਸ਼ਤ ਹੈ।"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"ਬੈਟਰੀ <xliff:g id="PERCENTAGE">%1$s</xliff:g> ਫ਼ੀਸਦ, ਤੁਹਾਡੀ ਵਰਤੋਂ ਦੇ ਆਧਾਰ \'ਤੇ ਲਗਭਗ <xliff:g id="TIME">%2$s</xliff:g> ਬਾਕੀ"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"ਬੈਟਰੀ ਚਾਰਜ ਹੋ ਰਹੀ ਹੈ, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> ਪ੍ਰਤੀਸ਼ਤ ਹੋ ਗਈ।"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"ਸਾਰੀਆਂ ਸੂਚਨਾਵਾਂ ਦੇਖੋ"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"ਟੈਲੀ ਟਾਈਪਰਾਈਟਰ ਸਮਰਥਿਤ।"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ਰਿੰਗਰ ਥਰਥਰਾਹਟ।"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ਜਾਰੀ ਰੱਖੋ"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ਐਪ ਨੂੰ ਸਾਂਝਾ ਕਰੋ ਜਾਂ ਰਿਕਾਰਡ ਕਰੋ"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"ਕੀ ਇਸ ਐਪ ਨੂੰ ਸਾਂਝਾ ਕਰਨ ਜਾਂ ਰਿਕਾਰਡ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, ਇਸ ਐਪ ਕੋਲ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਸਦੀ ਜਾਂ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਾਂਝਾ ਕਰਨ, ਰਿਕਾਰਡ ਕਰਨ, ਜਾਂ ਕਾਸਟ ਕਰਨ \'ਤੇ, ਇਸ ਐਪ ਕੋਲ ਉਸ ਐਪ \'ਤੇ ਦਿਖਾਈ ਗਈ ਜਾਂ ਚਲਾਈ ਗਈ ਹਰੇਕ ਚੀਜ਼ ਤੱਕ ਪਹੁੰਚ ਹੁੰਦੀ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ ਜਾਂ ਹੋਰ ਸੰਵੇਦਨਸ਼ੀਲ ਜਾਣਕਾਰੀ ਸੰਬੰਧੀ ਸਾਵਧਾਨ ਰਹੋ।"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਬਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"ਡੀਵਾਈਸ ਨੀਤੀ ਦੇ ਕਾਰਨ ਸਕ੍ਰੀਨ ਕੈਪਚਰ ਕਰਨਾ ਬੰਦ ਹੈ"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ਇਤਿਹਾਸ"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ਵਰਤਣ ਲਈ ਅਣਲਾਕ ਕਰੋ"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"ਤੁਹਾਡੇ ਕਾਰਡ ਪ੍ਰਾਪਤ ਕਰਨ ਵਿੱਚ ਕੋਈ ਸਮੱਸਿਆ ਆਈ, ਕਿਰਪਾ ਕਰਕੇ ਬਾਅਦ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"ਲਾਕ ਸਕ੍ਰੀਨ ਸੈਟਿੰਗਾਂ"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR ਕੋਡ ਸਕੈਨ ਕਰੋ"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ਹਵਾਈ-ਜਹਾਜ਼ ਮੋਡ"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"ਤੁਸੀਂ <xliff:g id="WHEN">%1$s</xliff:g> ਵਜੇ ਆਪਣਾ ਅਗਲਾ ਅਲਾਰਮ ਨਹੀਂ ਸੁਣੋਗੇ"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ਕੀ ਮੋਬਾਈਲ ਡਾਟਾ ਬੰਦ ਕਰਨਾ ਹੈ?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"ਤੁਸੀਂ <xliff:g id="CARRIER">%s</xliff:g> ਰਾਹੀਂ ਡਾਟੇ ਜਾਂ ਇੰਟਰਨੈੱਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕਰ ਸਕੋਗੇ। ਇੰਟਰਨੈੱਟ ਸਿਰਫ਼ ਵਾਈ-ਫਾਈ ਰਾਹੀਂ ਉਪਲਬਧ ਹੋਵੇਗਾ।"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ਤੁਹਾਡਾ ਕੈਰੀਅਰ"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"ਕੀ ਵਾਪਸ <xliff:g id="CARRIER">%s</xliff:g> \'ਤੇ ਸਵਿੱਚ ਕਰਨਾ ਹੈ?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ਮੋਬਾਈਲ ਡਾਟਾ ਉਪਲਬਧਤਾ ਦੇ ਆਧਾਰ \'ਤੇ ਸਵੈਚਲਿਤ ਤੌਰ \'ਤੇ ਸਵਿੱਚ ਨਹੀਂ ਹੋਵੇਗਾ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ਨਹੀਂ ਧੰਨਵਾਦ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ਹਾਂ, ਸਵਿੱਚ ਕਰੋ"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"ਕਿਸੇ ਐਪ ਵੱਲੋਂ ਇਜਾਜ਼ਤ ਬੇਨਤੀ ਨੂੰ ਢਕੇ ਜਾਣ ਕਾਰਨ ਸੈਟਿੰਗਾਂ ਤੁਹਾਡੇ ਜਵਾਬ ਦੀ ਪੁਸ਼ਟੀ ਨਹੀਂ ਕਰ ਸਕਦੀਆਂ।"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"ਕੀ <xliff:g id="APP_0">%1$s</xliff:g> ਨੂੰ <xliff:g id="APP_2">%2$s</xliff:g> ਦੇ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦੇਣੇ ਹਨ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ਇਹ <xliff:g id="APP">%1$s</xliff:g> ਵਿੱਚੋਂ ਜਾਣਕਾਰੀ ਪੜ੍ਹ ਸਕਦਾ ਹੈ"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ਮੋਬਾਈਲ ਡਾਟਾ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"ਕਨੈਕਟ ਹੈ"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"ਕੁਝ ਸਮੇਂ ਲਈ ਕਨੈਕਟ ਹੈ"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"ਖਰਾਬ ਕਨੈਕਸ਼ਨ"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ਮੋਬਾਈਲ ਡਾਟਾ ਸਵੈ-ਕਨੈਕਟ ਨਹੀਂ ਹੋਵੇਗਾ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ਕੋਈ ਕਨੈਕਸ਼ਨ ਨਹੀਂ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ਕੋਈ ਹੋਰ ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index fca76c4..47b68b1 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asystent głosowy"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Portfel"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Skaner kodów QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Odblokuj"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Urządzenie zablokowane"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Skanowanie twarzy"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Wyślij"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Nie można rozpoznać twarzy"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Użyj odcisku palca"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth połączony."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Poziom naładowania baterii jest nieznany."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Tryb samolotowy."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"Sieć VPN włączona."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Bateria: <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria <xliff:g id="PERCENTAGE">%1$s</xliff:g> procent, jeszcze <xliff:g id="TIME">%2$s</xliff:g> (na podstawie Twojego sposobu korzystania)"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Ładuję baterię, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> procent."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Zobacz wszystkie powiadomienia"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Dalekopis (TTY) włączony."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Dzwonek z wibracjami."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Podczas udostępniania, nagrywania lub przesyłania treści aplikacja <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Dalej"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Udostępnianie i nagrywanie za pomocą aplikacji"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Zezwolić tej aplikacji na udostępnianie lub nagrywanie?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Podczas udostępniania, nagrywania lub przesyłania treści ta aplikacja ma dostęp do wszystkiego, co jest widoczne na ekranie lub odtwarzane na urządzeniu. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Podczas udostępniania, nagrywania lub przesyłania treści ta aplikacja ma dostęp do wszystkiego, co jest w niej wyświetlane lub odtwarzane. Zachowaj ostrożność w przypadku haseł, danych do płatności, wiadomości i innych informacji poufnych."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Zablokowane przez administratora IT"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zrzuty ekranu są wyłączone zgodnie z zasadami dotyczącymi urządzeń"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Usuń wszystkie"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Zarządzaj"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odblokuj, aby użyć"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Podczas pobierania kart wystąpił problem. Spróbuj ponownie później."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Ustawienia ekranu blokady"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Zeskanuj kod QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Profil służbowy"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Tryb samolotowy"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nie usłyszysz swojego następnego alarmu <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Wyłączyć mobilną transmisję danych?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nie będziesz mieć dostępu do transmisji danych ani internetu w <xliff:g id="CARRIER">%s</xliff:g>. Internet będzie dostępny tylko przez Wi‑Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Twój operator"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Wrócić do operatora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilna transmisja danych nie będzie automatycznie przełączana na podstawie dostępności"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nie, dziękuję"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Tak, wróć"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Aplikacja Ustawienia nie może zweryfikować Twojej odpowiedzi, ponieważ inna aplikacja zasłania prośbę o udzielenie uprawnień."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Zezwolić aplikacji <xliff:g id="APP_0">%1$s</xliff:g> na pokazywanie wycinków z aplikacji <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Może odczytywać informacje z aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilna transmisja danych"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Połączono"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tymczasowe połączenie"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Słabe połączenie"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna transmisja danych nie połączy się automatycznie"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Brak połączenia"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Brak innych dostępnych sieci"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 9a3bfa6..4ff823a 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistência de voz"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Carteira"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Leitor de código QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Verificando rosto"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Rosto não reconhecido"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use a impressão digital"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentagem da bateria desconhecida."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo avião."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ativada."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Bateria em <xliff:g id="NUMBER">%d</xliff:g> por cento."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria com <xliff:g id="PERCENTAGE">%1$s</xliff:g> de carga, tempo restante aproximado, com base no seu uso: <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria carregando: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas as notificações"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTYpewriter ativado."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibração da campainha."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando você compartilha, grava ou transmite um app, o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartilhar ou gravar um app"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Permitir que este app compartilhe ou grave a tela?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Quando você compartilha, grava ou transmite a tela, este app tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens e outras informações sensíveis."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Quando você compartilha, grava ou transmite a tela, este app tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens e outras informações sensíveis."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao carregar os cards. Tente novamente mais tarde"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configurações de tela de bloqueio"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Ler código QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Você não ouvirá o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Você não terá acesso a dados ou à Internet pela operadora <xliff:g id="CARRIER">%s</xliff:g>. A Internet só estará disponível via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"sua operadora"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Voltar para a operadora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"A conexão de dados móveis não vai ser alternada automaticamente de acordo com a disponibilidade"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Agora não"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sim, voltar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Como um app está ocultando uma solicitação de permissão, as configurações não podem verificar sua resposta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporariamente conectado"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexão fraca"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem conexão automática com dados móveis"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem conexão"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index dcddba8..a50624e 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -125,7 +125,7 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistente de voz"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Carteira"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Leitor de códigos QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+ <string name="accessibility_unlock_button" msgid="3613812140816244310">"Desbloqueado"</string>
<string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"A analisar o rosto…"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -168,7 +168,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Imposs. reconhecer rosto"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Usar impressão digital"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ligado."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percentagem da bateria desconhecida."</string>
@@ -180,8 +180,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo de avião"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ativada."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Bateria a <xliff:g id="NUMBER">%d</xliff:g> por cento."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria a <xliff:g id="PERCENTAGE">%1$s</xliff:g> por cento, resta(m) cerca de <xliff:g id="TIME">%2$s</xliff:g> com base na sua utilização."</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria a carregar (<xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%)."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas as notificações"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletipo ativado."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Campainha em vibração."</string>
@@ -375,16 +380,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando está a partilhar, gravar ou transmitir uma app, a app <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Partilhe ou grave uma app"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Permitir que esta app partilhe ou grave?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Quando está a partilhar, gravar ou transmitir, esta app tem acesso a tudo o que está visível no seu ecrã ou é reproduzido no seu dispositivo. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Quando está a partilhar, gravar ou transmitir uma app, esta app tem acesso a tudo o que é apresentado ou reproduzido nessa app. Por isso, tenha cuidado com palavras-passe, detalhes de pagamento, mensagens ou outras informações confidenciais."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bloqueado pelo administrador de TI"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de ecrã está desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gerir"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
@@ -500,7 +500,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para utilizar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao obter os seus cartões. Tente mais tarde."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Definições do ecrã de bloqueio"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Leia o código QR"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"Leitor de códigos QR"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modo de avião"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Não vai ouvir o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +739,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Não terá acesso a dados ou à Internet através do operador <xliff:g id="CARRIER">%s</xliff:g>. A Internet estará disponível apenas por Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"o seu operador"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Mudar de novo para <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Os dados móveis não vão mudar automaticamente com base na disponibilidade"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Não"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sim, mudar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Uma vez que uma app está a ocultar um pedido de autorização, as Definições não conseguem validar a sua resposta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Permitir que a app <xliff:g id="APP_0">%1$s</xliff:g> mostre partes da app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações da app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +933,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ligado"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ligado temporariamente"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Ligação fraca"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem ligação automática com dados móveis"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem ligação"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 9a3bfa6..4ff823a 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Assistência de voz"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Carteira"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Leitor de código QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Desbloquear"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Dispositivo bloqueado"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Verificando rosto"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Enviar"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Rosto não reconhecido"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Use a impressão digital"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth conectado."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Porcentagem da bateria desconhecida."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Modo avião."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ativada."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Bateria em <xliff:g id="NUMBER">%d</xliff:g> por cento."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria com <xliff:g id="PERCENTAGE">%1$s</xliff:g> de carga, tempo restante aproximado, com base no seu uso: <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria carregando: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Ver todas as notificações"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTYpewriter ativado."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibração da campainha."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Quando você compartilha, grava ou transmite um app, o <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> tem acesso a todas as informações visíveis na tela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens ou outras informações sensíveis."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuar"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Compartilhar ou gravar um app"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Permitir que este app compartilhe ou grave a tela?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Quando você compartilha, grava ou transmite a tela, este app tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens e outras informações sensíveis."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Quando você compartilha, grava ou transmite a tela, este app tem acesso a todas as informações visíveis nela ou reproduzidas no dispositivo. Tenha cuidado com senhas, detalhes de pagamento, mensagens e outras informações sensíveis."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Ação bloqueada pelo administrador de TI"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"A captura de tela foi desativada pela política do dispositivo"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Limpar tudo"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gerenciar"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Histórico"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Desbloquear para usar"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Ocorreu um problema ao carregar os cards. Tente novamente mais tarde"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Configurações de tela de bloqueio"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Ler código QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Perfil de trabalho"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modo avião"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Você não ouvirá o próximo alarme às <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Você não terá acesso a dados ou à Internet pela operadora <xliff:g id="CARRIER">%s</xliff:g>. A Internet só estará disponível via Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"sua operadora"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Voltar para a operadora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"A conexão de dados móveis não vai ser alternada automaticamente de acordo com a disponibilidade"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Agora não"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Sim, voltar"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Como um app está ocultando uma solicitação de permissão, as configurações não podem verificar sua resposta."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporariamente conectado"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexão fraca"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem conexão automática com dados móveis"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem conexão"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 5825089..e281e88 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Asistent vocal"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Portofel"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Scanner de coduri QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Deblochează"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Dispozitiv blocat"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Scanarea chipului"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Trimite"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Chip nerecunoscut"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Folosește amprenta"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Conectat prin Bluetooth."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Procentajul bateriei este necunoscut."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mod Avion."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"Rețea VPN activată"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Baterie: <xliff:g id="NUMBER">%d</xliff:g> la sută."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Procentul rămas din baterie este <xliff:g id="PERCENTAGE">%1$s</xliff:g>. În baza utilizării, timpul rămas este de aproximativ <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria se încarcă, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> la sută."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Vezi toate notificările"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter activat."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibrare sonerie."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> are acces la orice se afișează pe ecran sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Continuă"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Permite accesul la o aplicație sau înregistreaz-o"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Permiți trimiterea sau înregistrarea din aplicație?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Când permiți accesul, înregistrezi sau proiectezi, aplicația are acces la orice este vizibil pe ecran sau se redă pe dispozitiv. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Când permiți accesul, înregistrezi sau proiectezi o aplicație, aceasta are acces la orice se afișează pe ecran sau se redă în aplicație. Ai grijă cu parolele, detaliile de plată, mesajele sau alte informații sensibile."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blocată de administratorul IT"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Capturile de ecran sunt dezactivate de politica privind dispozitivele"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Șterge toate notificările"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Gestionează"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Istoric"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Deblochează pentru a folosi"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"A apărut o problemă la preluarea cardurilor. Încearcă din nou mai târziu"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Setările ecranului de blocare"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Scanează codul QR"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"Scanner de coduri QR"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profil de serviciu"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Mod Avion"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nu vei auzi următoarea alarmă <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Dezactivezi datele mobile?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nu vei avea acces la date sau la internet prin intermediul <xliff:g id="CARRIER">%s</xliff:g>. Internetul va fi disponibil numai prin Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorul tău"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Revii la <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Comutarea la datele mobile nu se va face automat în funcție de disponibilitate"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nu, mulțumesc"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, fac trecerea"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Deoarece o aplicație acoperă o solicitare de permisiune, Setările nu îți pot verifica răspunsul."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Permiți ca <xliff:g id="APP_0">%1$s</xliff:g> să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Poate citi informații din <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Date mobile"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Conectat"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Conectat temporar"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Conexiune slabă"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Nu se conectează automat la date mobile"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nicio conexiune"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nu sunt disponibile alte rețele"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index b6b0628..b7f10f2 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Аудиоподсказки"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Кошелек"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Сканер QR-кодов"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Разблокировать."</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Устройство заблокировано"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Сканирование лица"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Отправить"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Лицо не распознано."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Используйте отпечаток."</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth-соединение установлено."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Уровень заряда батареи в процентах неизвестен."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Режим полета."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"Режим VPN включен."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Заряд батареи в процентах: <xliff:g id="NUMBER">%d</xliff:g>."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Заряд батареи в процентах: <xliff:g id="PERCENTAGE">%1$s</xliff:g>. Оценка оставшегося времени работы: <xliff:g id="TIME">%2$s</xliff:g>."</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Зарядка батареи. Текущий заряд: <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Показать все уведомления"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Телетайп включен."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Вибровызов."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Когда вы демонстрируете, транслируете экран или записываете видео с него, приложение \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Далее"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Демонстрация экрана или запись видео с него"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Разрешить приложению демонстрировать экран или записывать видео с него?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Когда вы демонстрируете, транслируете экран или записываете видео с него, это приложение получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Когда вы демонстрируете, транслируете экран или записываете видео с него, это приложение получает доступ ко всему, что видно и воспроизводится на экране устройства. Помните об этом, если соберетесь вводить или просматривать пароли, платежные данные, сообщения и другую конфиденциальную информацию."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблокировано вашим администратором"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запись экрана отключена в соответствии с правилами для устройства."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Настроить"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Разблокировать для использования"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Не удалось получить информацию о картах. Повторите попытку позже."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Настройки заблокированного экрана"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Сканировать QR-код"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Рабочий профиль"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Режим полета"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Следующий будильник: <xliff:g id="WHEN">%1$s</xliff:g>. Звук отключен."</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Отключить мобильный Интернет?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Вы не сможете передавать данные или выходить в Интернет через оператора \"<xliff:g id="CARRIER">%s</xliff:g>\". Интернет будет доступен только по сети Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш оператор"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Переключиться на сеть \"<xliff:g id="CARRIER">%s</xliff:g>\"?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобильный интернет не будет переключаться автоматически."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Нет"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Невозможно принять ваше согласие, поскольку запрос скрыт другим приложением."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Разрешить приложению \"<xliff:g id="APP_0">%1$s</xliff:g>\" показывать фрагменты приложения \"<xliff:g id="APP_2">%2$s</xliff:g>\"?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Ему станут доступны данные из приложения \"<xliff:g id="APP">%1$s</xliff:g>\"."</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобильный интернет"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Подключено"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Временное подключение"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Слабый сигнал"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Без автоподключения к мобильному интернету"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Нет подключения к интернету"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Нет других доступных сетей"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 832c211..f9a6b0b 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"හඬ සහාය"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR කේත ස්කෑනරය"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"අඟුල අරින්න"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"උපාංගය අගුලු දමා ඇත"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"මුහුණ ස්කෑන් කිරීම"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"යවන්න"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"මුහුණ හඳුනා ගත නොහැක"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ඒ වෙනුවට ඇඟිලි සලකුණ භාවිත කරන්න"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"බ්ලූටූත් සම්බන්ධිතයි."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"බැටරි ප්රතිශතය නොදනී."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"අහස්යානා ආකාරය."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN ක්රියාත්මකයි."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"බැටරි ප්රතිශතය <xliff:g id="NUMBER">%d</xliff:g>"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"බැටරි ප්රතිශතය <xliff:g id="PERCENTAGE">%1$s</xliff:g>, ඔබේ භාවිතයට අනුව <xliff:g id="TIME">%2$s</xliff:g> ක් පමණ ඉතුරුයි"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"බැටරිය ආරෝපණය කරමින්, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"සියලු දැනුම්දීම් බලන්න"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter ක්රියාත්මකයි."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"හඬ නඟනය කම්පනය වේ."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ඉදිරියට යන්න"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"යෙදුමක් බෙදා ගන්න හෝ පටිගත කරන්න"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"මෙම යෙදුම බෙදා ගැනීමට හෝ පටිගත කිරීමට ඉඩ දෙන්න ද?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"ඔබ බෙදා ගන්නා විට, පටිගත කරන විට, හෝ විකාශය කරන විට, හට ඔබේ තිරයේ පෙනෙන හෝ ඔබේ උපාංගයේ වාදනය වන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ඔබ යෙදුමක් බෙදා ගන්නා විට, පටිගත කරන විට හෝ විකාශය කරන විට, හට එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයකට ප්රවේශය ඇත. එබැවින් මුරපද, ගෙවීම් විස්තර, පණිවිඩ හෝ වෙනත් සංවේදී තොරතුරු සමග ප්රවේශම් වන්න."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ඔබේ IT පරිපාලක විසින් අවහිර කර ඇත"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"උපාංග ප්රතිපත්තිය මගින් තිර ග්රහණය කිරීම අබල කර ඇත"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"සියල්ල හිස් කරන්න"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"කළමනාකරණය කරන්න"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ඉතිහාසය"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"භාවිත කිරීමට අගුලු හරින්න"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"ඔබගේ කාඩ්පත ලබා ගැනීමේ ගැටලුවක් විය, කරුණාකර පසුව නැවත උත්සාහ කරන්න"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"අගුලු තිර සැකසීම්"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR කේතය ස්කෑන් කරන්න"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"කාර්යාල පැතිකඩ"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ගුවන්යානා ප්රකාරය"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"ඔබට ඔබේ ඊළඟ එලාමය <xliff:g id="WHEN">%1$s</xliff:g> නොඇසෙනු ඇත"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ජංගම දත්ත ක්රියාවිරහිත කරන්නද?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"ඔබට <xliff:g id="CARRIER">%s</xliff:g> හරහා දත්ත හෝ අන්තර්ජාලයට පිවිසීමේ හැකියාවක් නැත. අන්තර්ජාලය Wi-Fi හරහා පමණක් ලබා ගත හැකිය."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ඔබගේ වාහකය"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> වෙත ආපසු මාරු කරන්නද?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ජංගම දත්ත ලබා ගත හැකි වීමට අනුව ස්වයංක්රීයව මාරු නොවෙයි"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"එපා ස්තුතියි"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ඔව්, මාරු කරන්න"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"යෙදුමක් අවසර ඉල්ලීමක් කරන නිසා, සැකසීම්වලට ඔබගේ ප්රතිචාරය සත්යාපනය කළ නොහැකිය."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> හට කොටස් <xliff:g id="APP_2">%2$s</xliff:g>ක් පෙන්වීමට ඉඩ දෙන්නද?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- එයට <xliff:g id="APP">%1$s</xliff:g> වෙතින් තොරතුරු කියවිය හැකිය"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"ජංගම දත්ත"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"සම්බන්ධයි"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"තාවකාලිකව සම්බන්ධ කළා"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"දුර්වල සම්බන්ධතාව"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"ජංගම දත්ත ස්වංක්රියව සම්බන්ධ නොවනු ඇත"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"සම්බන්ධතාවයක් නැත"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ලබා ගත හැකි වෙනත් ජාල නැත"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 0329914..51f0c7d 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Hlasový asistent"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Peňaženka"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Skener QR kódov"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Odomknúť"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Zariadenie je uzamknuté"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Skenovanie tváre"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Odoslať"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Tvár sa nedá rozpoznať"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Používať radšej odtlačok"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth pripojené."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Percento batérie nie je známe."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Režim v lietadle."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN je zapnuté."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Batéria <xliff:g id="NUMBER">%d</xliff:g> percent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Percentá batérie: <xliff:g id="PERCENTAGE">%1$s</xliff:g>. Na základe vášho používania zostáva <xliff:g id="TIME">%2$s</xliff:g>."</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Nabíja sa batéria, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Zobraziť všetky upozornenia"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Rozhranie TeleTypewriter je povolené."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibračné zvonenie."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Počas zdieľania, nahrávania alebo prenosu bude mať aplikácia <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> prístup k všetkému obsahu, ktorý sa v nej bude zobrazovať alebo prehrávať. Preto venujte zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Pokračovať"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Aplikácia na zdieľanie alebo nahrávanie"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Chcete povoliť tejto aplikácii zdieľať alebo nahrávať?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Počas zdieľania, nahrávania alebo prenosu bude mať táto aplikácia prístup k všetkému na obrazovke, prípadne k obsahu, ktorý sa bude v zariadení prehrávať. Venujte preto zvýšenú pozornosť heslám, platobným údajom, správam a ďalším citlivým údajom."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Počas zdieľania, nahrávania alebo prenosu bude mať táto aplikácia prístup k všetkému obsahu, ktorý sa v nej bude zobrazovať alebo prehrávať. Venujte preto zvýšenú pozornosť heslám, platobným údajom, správam či ďalším citlivým údajom."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokované vaším správcom IT"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Snímanie obrazovky je zakázané pravidlami pre zariadenie"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Spravovať"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"História"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odomknúť a použiť"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Pri načítavaní kariet sa vyskytol problém. Skúste to neskôr."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavenia uzamknutej obrazovky"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skenovanie QR kódu"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Pracovný profil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Režim v lietadle"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Váš budík o <xliff:g id="WHEN">%1$s</xliff:g> sa nespustí"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Chcete vypnúť mobilné dáta?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nebudete mať prístup k dátam ani internetu prostredníctvom operátora <xliff:g id="CARRIER">%s</xliff:g>. Internet bude k dispozícii iba cez Wi‑Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"váš operátor"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Chcete prepnúť späť na operátora <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobilné dáta sa nebudú automaticky prepínať na základe dostupnosti"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nie, vďaka"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Áno, prepnúť"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Nastavenia nemôžu overiť vašu odpoveď, pretože určitá aplikácia blokuje žiadosť o povolenie."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Povoliť aplikácii <xliff:g id="APP_0">%1$s</xliff:g> zobrazovať rezy z aplikácie <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Môže čítať informácie z aplikácie <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilné dáta"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Pripojené"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Dočasne pripojené"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slabé pripojenie"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Automatické pripojenie cez mobilné dáta nefunguje"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Bez pripojenia"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nie sú k dispozícii žiadne ďalšie siete"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index b935de7..9e2a4bb 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Glasovni pomočnik"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Google Denarnica"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Optični bralnik kod QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Odkleni"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Naprava je zaklenjena."</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Optično branje obraza"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Pošlji"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Obraz ni bil prepoznan."</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Uporabite prstni odtis."</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Povezava Bluetooth vzpostavljena."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Neznan odstotek napolnjenosti baterije."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Način za letalo."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"Omrežje VPN je vklopljeno."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Baterija <xliff:g id="NUMBER">%d</xliff:g> odstotkov."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Napolnjenost baterije je <xliff:g id="PERCENTAGE">%1$s</xliff:g>, glede na način uporabe imate na voljo še približno <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Baterija se polni, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> odstotkov."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Prikaži vsa obvestila"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter omogočen."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Zvonjenje z vibriranjem."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Pri deljenju, snemanju ali predvajanju aplikacije ima aplikacija <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> dostop do vsega, kar je prikazano ali predvajano v tej aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Naprej"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Deljenje ali snemanje aplikacije"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Ali tej aplikaciji dovolite deljenje ali snemanje?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Pri deljenju, snemanju ali predvajanju ima ta aplikacija dostop do vsega, kar je prikazano na zaslonu ali se predvaja v napravi. Zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Pri deljenju, snemanju ali predvajanju aplikacije ima ta aplikacija dostop do vsega, kar je prikazano ali predvajano v tisti aplikaciji, zato bodite previdni z gesli, podatki za plačilo, sporočili ali drugimi občutljivimi podatki."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blokiral skrbnik za IT"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Zajemanje zaslonske slike je onemogočil pravilnik za naprave."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Izbriši vse"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Upravljaj"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Zgodovina"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Odklenite za uporabo"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Pri pridobivanju kartic je prišlo do težave. Poskusite znova pozneje."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Nastavitve zaklepanja zaslona"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Optično branje kode QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Profil za Android Work"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Način za letalo"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Naslednjega alarma ob <xliff:g id="WHEN">%1$s</xliff:g> ne boste slišali"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Želite izklopiti prenos podatkov v mobilnih omrežjih?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Prek operaterja »<xliff:g id="CARRIER">%s</xliff:g>« ne boste imeli dostopa do podatkovne povezave ali interneta. Internet bo na voljo samo prek povezave Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"svojega operaterja"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Želite preklopiti nazaj na ponudnika <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Prenos podatkov v mobilnem omrežju ne preklopi samodejno glede na razpoložljivost."</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ne, hvala"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Da, preklopi"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Ker aplikacija zakriva zahtevo za dovoljenje, z nastavitvami ni mogoče preveriti vašega odziva."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Želite dovoliti, da aplikacija <xliff:g id="APP_0">%1$s</xliff:g> prikaže izreze aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– lahko bere podatke v aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Prenos podatkov v mobilnem omrežju"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Začasno vzpostavljena povezava"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Slaba povezava"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna podatkovna povezava ne bo samodejna."</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Ni povezave"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nobeno drugo omrežje ni na voljo"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index b7fef86..2547563 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Ndihma zanore"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Portofoli"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Skaneri i kodeve QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Shkyç"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Pajisja është e kyçur"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Po skanon fytyrën"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Dërgo"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Fytyra nuk mund të njihet"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Përdor më mirë gjurmën e gishtit"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Pajisja është lidhur me \"bluetooth\"."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Përqindja e baterisë e panjohur."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"modaliteti i aeroplanit"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN-ja është aktive."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Bateria ka edhe <xliff:g id="NUMBER">%d</xliff:g> për qind."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Bateria <xliff:g id="PERCENTAGE">%1$s</xliff:g> përqind, rreth <xliff:g id="TIME">%2$s</xliff:g> të mbetura bazuar në përdorimin tënd"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Bateria po karikohet, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Shiko të gjitha njoftimet"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Teletajpi është i aktivizuar."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Zile me dridhje."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Gjatë shpërndarjes, regjistrimit ose transmetimit të një aplikacioni, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacione të tjera të ndjeshme."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Vazhdo"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Shpërndaj ose regjistro një aplikacion"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Të lejohet që ky aplikacion të shpërndajë ose të regjistrojë?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Gjatë shpërndarjes, regjistrimit ose transmetimit, ky aplikacion ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në pajisjen tënde. Prandaj ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacionet e tjera të ndjeshme."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Gjatë shpërndarjes, regjistrimit ose transmetimit, ky aplikacioni ka qasje te çdo gjë e dukshme në ekranin tënd ose që po luhet në atë aplikacion. Prandaj, ki kujdes me fjalëkalimet, detajet e pagesës, mesazhet ose informacionet e tjera të ndjeshme."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"U bllokua nga administratori yt i teknologjisë së informacionit"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Regjistrimi i ekranit është çaktivizuar nga politika e pajisjes."</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Pastroji të gjitha"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Menaxho"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historiku"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Shkyçe për ta përdorur"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Pati një problem me marrjen e kartave të tua. Provo përsëri më vonë"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Cilësimet e ekranit të kyçjes"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skano kodin QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Profili i punës"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Modaliteti i aeroplanit"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nuk do ta dëgjosh alarmin e radhës në <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Të çaktivizohen të dhënat celulare?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Nuk do të kesh qasje te të dhënat ose interneti nëpërmjet <xliff:g id="CARRIER">%s</xliff:g>. Interneti do të ofrohet vetëm nëpërmjet Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatori yt celular"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Të kalohet përsëri te <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Të dhënat celulare nuk do të ndërrohen automatikisht në bazë të disponueshmërisë"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Jo, faleminderit"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Po, ndërro"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Duke qenë se një aplikacion po bllokon një kërkesë për leje, \"Cilësimet\" nuk mund të verifikojnë përgjigjen tënde."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Të lejohet <xliff:g id="APP_0">%1$s</xliff:g> që të shfaqë pjesë të <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Mund të lexojë informacion nga <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Të dhënat celulare"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Lidhur"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Lidhur përkohësisht"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Lidhje e dobët"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Të dhënat celulare nuk do të lidhen automatikisht"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Nuk ka lidhje"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nuk ofrohet asnjë rrjet tjetër"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 56cb3c8..fb17123 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Гласовна помоћ"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Новчаник"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Скенер QR кода"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Откључајте"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Уређај је закључан"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Скенирање лица"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Пошаљи"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Лице није препознато"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Користите отисак прста"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth је прикључен."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Проценат напуњености батерије није познат."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Режим рада у авиону."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN је укључен."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Батерија је на <xliff:g id="NUMBER">%d</xliff:g> посто."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Батерија је на <xliff:g id="PERCENTAGE">%1$s</xliff:g> посто, преостало време на основу коришћења је <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Батерија се пуни, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> посто."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Погледајте сва обавештења"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter је омогућен."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Вибрација звона."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Будите пажљиви са лозинкама, информацијама о плаћању, порукама или другим осетљивим информацијама."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Настави"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Делите или снимите апликацију"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Желите да дозволите овој апликацији да дели или снима?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Када делите, снимате или пребацујете, ова апликација има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Будите пажљиви са лозинкама, информацијама о плаћању, порукама или другим осетљивим информацијама."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Када делите, снимате или пребацујете апликацију, ова апликација има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Будите пажљиви са лозинкама, информацијама о плаћању, порукама или другим осетљивим информацијама."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Блокира ИТ администратор"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Снимање екрана је онемогућено смерницама за уређај"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Управљајте"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Откључај ради коришћења"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Дошло је до проблема при преузимању картица. Пробајте поново касније"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Подешавања закључаног екрана"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Скенирајте QR кôд"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Пословни профил"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Режим рада у авиону"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Нећете чути следећи аларм у <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Желите да искључите мобилне податке?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Нећете имати приступ подацима или интернету преко мобилног оператера <xliff:g id="CARRIER">%s</xliff:g>. Интернет ће бити доступан само преко WiFi везе."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"мобилни оператер"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Желите да се вратите на мобилног оператера <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Мобилни подаци се неће аутоматски променити на основу доступности"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Не, хвала"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Да, пређи"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Подешавања не могу да верификују ваш одговор јер апликација скрива захтев за дозволу."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Желите ли да дозволите апликацији <xliff:g id="APP_0">%1$s</xliff:g> да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Може да чита податке из апликације <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилни подаци"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Повезано"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Привремено повезано"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Веза је лоша"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Није успело аутом. повезивање преко моб. података"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Веза није успостављена"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Није доступна ниједна друга мрежа"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 8de8d09..6e74ea2 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Röstassistent"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR-skanner"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Lås upp"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Enheten är låst"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Registrerar ansikte"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Skicka"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Ansiktet kändes inte igen"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Använd fingeravtryck"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ansluten."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Okänd batterinivå."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Flygplansläge"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN har aktiverats."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Batteri <xliff:g id="NUMBER">%d</xliff:g> procent."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batteri: <xliff:g id="PERCENTAGE">%1$s</xliff:g> procent, cirka <xliff:g id="TIME">%2$s</xliff:g> kvar utifrån din användning"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batteriet laddas, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> procent."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Visa alla aviseringar"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter aktiverad."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibrerande ringsignal."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"När du delar, spelar in eller castar en app har <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Fortsätt"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Dela eller spela in en app"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vill du tillåta att den här appen delar eller spelar in?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"När du delar, spelar in eller castar har den här appen åtkomst till allt som visas på skärmen eller spelas upp på enheten. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"När du delar, spelar in eller castar en app har den här appen åtkomst till allt som visas eller spelas upp i appen. Så var försiktig med lösenord, betalningsuppgifter, meddelanden och andra känsliga uppgifter."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Blockeras av IT-administratören"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Skärminspelning är inaktiverat av enhetspolicyn"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Rensa alla"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Hantera"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Lås upp för att använda"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Det gick inte att hämta dina kort. Försök igen senare."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Inställningar för låsskärm"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skanna QR-kod"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Jobbprofil"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Flygplansläge"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Nästa alarm, kl. <xliff:g id="WHEN">%1$s</xliff:g>, kommer inte att höras"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vill du inaktivera mobildata?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Du kan inte skicka data eller använda internet via <xliff:g id="CARRIER">%s</xliff:g>. Internetanslutning blir bara möjlig via wifi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"din operatör"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Vill du byta tillbaka till <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobildatakällan byts inte automatiskt efter tillgänglighet"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Nej tack"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ja, byt"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Svaret kan inte verifieras av Inställningar eftersom en app skymmer en begäran om behörighet."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Tillåter du att bitar av <xliff:g id="APP_2">%2$s</xliff:g> visas i <xliff:g id="APP_0">%1$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– Kan läsa information från <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ansluten"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tillfälligt ansluten"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Dålig anslutning"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Du ansluts inte till mobildata automatiskt"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Ingen anslutning"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Inga andra nätverk är tillgängliga"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index cfa370e..e8a7caf 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Mapendekezo ya Sauti"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Pochi"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Kichanganuzi cha Msimbo wa QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Fungua"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Kifaa kimefungwa"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Inachanganua uso"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Tuma"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Imeshindwa kutambua uso"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Badala yake, tumia alama ya kidole"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth imeunganishwa."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Asilimia ya betri haijulikani."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Hali ya ndegeni."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN imewashwa."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Asilimia <xliff:g id="NUMBER">%d</xliff:g> ya betri"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Betri ina asilimia <xliff:g id="PERCENTAGE">%1$s</xliff:g>, zimesalia takribani <xliff:g id="TIME">%2$s</xliff:g> kulingana na jinsi unavyoitumia"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Betri inachaji, asilimia <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Angalia arifa zote"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Kichapishaji cha Tele kimewezeshwa."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Mtetemo wa mlio"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Unapotuma, kurekodi au kushiriki programu, programu ya <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Hivyo kuwa mwangalifu na manenosiri, maelezo ya malipo, ujumbe au maelezo mengine nyeti."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Endelea"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Shiriki au rekodi programu"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Ungependa kuruhusu programu hii ishiriki au irekodi?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Unapotuma, kurekodi au kushiriki, programu hii inaweza kufikia kitu chochote kitakachoonekana kwenye skrini yako au kuchezwa kwenye kifaa chako. Hivyo kuwa mwangalifu na manenosiri, maelezo ya malipo, ujumbe au maelezo mengine nyeti."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Unapotuma, kurekodi au kushiriki programu, programu hii inaweza kufikia kitu chochote kitakachoonekana au kuchezwa kwenye programu hiyo. Hivyo kuwa mwangalifu na manenosiri, maelezo ya malipo, ujumbe au maelezo mengine nyeti."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Umezuiwa na msimamizi wako wa TEHAMA"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Mchakato wa kurekodi skrini umezimwa na sera ya kifaa"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Futa zote"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Dhibiti"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Historia"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Fungua ili utumie"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Hitilafu imetokea wakati wa kuleta kadi zako, tafadhali jaribu tena baadaye"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Mipangilio ya kufunga skrini"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Changanua msimbo wa QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Wasifu wa kazini"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Hali ya ndegeni"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Hutasikia kengele yako inayofuata ya saa <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Ungependa kuzima data ya mtandao wa simu?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Hutaweza kufikia data au intaneti kupitia <xliff:g id="CARRIER">%s</xliff:g>. Intaneti itapatikana kupitia Wi-Fi pekee."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"mtoa huduma wako"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Ungependa kubadilisha ili utumie data ya mtandao wa <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Data ya mtandao wa simu haitabadilika kiotomatiki kulingana na upatikanaji"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Hapana"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ndiyo, badili"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Kwa sababu programu nyingine inazuia ombi la ruhusa, hatuwezi kuthibitisha jibu lako katika Mipangilio."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Ungependa kuruhusu <xliff:g id="APP_0">%1$s</xliff:g> ionyeshe vipengee <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Inaweza kusoma maelezo kutoka <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Data ya mtandao wa simu"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Imeunganishwa"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Imeunganishwa kwa muda"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Muunganisho duni"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Data ya mtandao wa simu haitaunganishwa kiotomatiki"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Hakuna muunganisho"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Hakuna mitandao mingine inayopatikana"</string>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index b24ce12..6c7cab5 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -24,6 +24,7 @@
<!-- margin from keyguard status bar to clock. For split shade it should be
keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp -->
<dimen name="keyguard_clock_top_margin">8dp</dimen>
+ <dimen name="keyguard_smartspace_top_offset">0dp</dimen>
<!-- QS-->
<dimen name="qs_panel_padding_top">16dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index 868c003..3fc59e3 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -35,6 +35,11 @@
not appear immediately after user swipes to the side -->
<dimen name="qs_tiles_page_horizontal_margin">20dp</dimen>
+ <!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
+ <dimen name="qs_media_rec_icon_top_margin">27dp</dimen>
+ <dimen name="qs_media_rec_album_size">152dp</dimen>
+ <dimen name="qs_media_rec_album_side_margin">16dp</dimen>
+
<dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen>
<!-- Roughly the same distance as media on LS to media on QS. We will translate by this value
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index caea7a0..41e1f3f 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"குரல் உதவி"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR குறியீடு ஸ்கேனர்"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"அன்லாக் செய்"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"சாதனம் பூட்டப்பட்டுள்ளது"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"முகத்தை ஸ்கேன் செய்கிறது"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"அனுப்பு"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"முகத்தை கண்டறிய இயலவில்லை"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"கைரேகையை உபயோகிக்கவும்"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"புளூடூத் இணைக்கப்பட்டது."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"பேட்டரி சதவீதம் தெரியவில்லை."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"விமானப் பயன்முறை."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN இயக்கத்தில் உள்ளது."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"பேட்டரி சக்தி <xliff:g id="NUMBER">%d</xliff:g> சதவிகிதம் உள்ளது."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"பேட்டரி: <xliff:g id="PERCENTAGE">%1$s</xliff:g> சதவீதம், உபயோகத்தின் அடிப்படையில் <xliff:g id="TIME">%2$s</xliff:g> மீதமுள்ளது"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"பேட்டரி சார்ஜ் ஆகிறது, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> சதவீதம் உள்ளது."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"எல்லா அறிவிப்புகளையும் காட்டும்"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter இயக்கப்பட்டது."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"ரிங்கர் அதிர்வு."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"ஓர் ஆப்ஸை நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அந்த ஆப்ஸில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தையும் <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், பிற பாதுகாக்கப்பட வேண்டிய தகவல்கள் ஆகியவை குறித்து கவனத்துடன் இருங்கள்."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"தொடர்க"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ஆப்ஸைப் பகிர்தல் அல்லது ரெக்கார்டு செய்தல்"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"பகிர்வதற்கோ ரெக்கார்டு செய்வதற்கோ இந்த ஆப்ஸை அனுமதிக்கவா?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ திரையில் காட்டப்படும்/சாதனத்தில் பிளே செய்யப்படும் அனைத்தையும் இந்த ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள் அல்லது பாதுகாக்கப்பட வேண்டிய பிற தகவல்கள் குறித்துக் கவனமாக இருங்கள்."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"ஓர் ஆப்ஸை நீங்கள் பகிரும்போதோ ரெக்கார்டு செய்யும்போதோ அலைபரப்பும்போதோ அதில் காட்டப்படும்/பிளே செய்யப்படும் அனைத்தையும் இந்த ஆப்ஸால் அணுக முடியும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள் அல்லது பாதுகாக்கப்பட வேண்டிய பிற தகவல்கள் குறித்துக் கவனமாக இருங்கள்."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"உங்கள் IT நிர்வாகி தடுத்துள்ளார்"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"\'திரையைப் படமெடுத்தல்\' சாதனக் கொள்கையின்படி முடக்கப்பட்டுள்ளது"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"எல்லாவற்றையும் அழி"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"நிர்வகி"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"இதுவரை வந்த அறிவிப்புகள்"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"பயன்படுத்துவதற்கு அன்லாக் செய்க"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"உங்கள் கார்டுகளின் விவரங்களைப் பெறுவதில் சிக்கல் ஏற்பட்டது, பிறகு முயலவும்"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"பூட்டுத் திரை அமைப்புகள்"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR குறியீட்டை ஸ்கேன் செய்தல்"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"பணிக் கணக்கு"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"விமானப் பயன்முறை"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"அடுத்த அலாரத்தை <xliff:g id="WHEN">%1$s</xliff:g> மணிக்கு கேட்க மாட்டீர்கள்"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"மொபைல் டேட்டாவை ஆஃப் செய்யவா?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> மூலம் டேட்டா அல்லது இணையத்தை உங்களால் பயன்படுத்த முடியாது. வைஃபை வழியாக மட்டுமே இணையத்தைப் பயன்படுத்த முடியும்."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"உங்கள் மொபைல் நிறுவனம்"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g>க்கு மறுபடியும் மாற்றவா?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"கிடைக்கும் நிலையின் அடிப்படையில் மொபைல் டேட்டா தானாகவே மாறாது"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"வேண்டாம்"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"மாற்று"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"அனுமதிக் கோரிக்கையை ஆப்ஸ் மறைப்பதால், அமைப்புகளால் உங்கள் பதிலைச் சரிபார்க்க முடியாது."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ஆப்ஸை, <xliff:g id="APP_2">%2$s</xliff:g> ஆப்ஸின் விழிப்பூட்டல்களைக் காண்பிக்க அனுமதிக்கவா?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- இது, <xliff:g id="APP">%1$s</xliff:g> பயன்பாட்டிலிருந்து தகவலைப் படிக்கும்"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"மொபைல் டேட்டா"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"இணைக்கப்பட்டது"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"தற்காலிகமாக இணைக்கப்பட்டுள்ளது"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"இணைப்பு மோசமாக உள்ளது"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"மொபைல் டேட்டாவுடன் தானாக இணைக்காது"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"இணைப்பு இல்லை"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"வேறு நெட்வொர்க்குகள் எதுவும் கிடைக்கவில்லை"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 22d7190..9ad0277c 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"వాయిస్ అసిస్టెంట్"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR కోడ్ స్కానర్"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"అన్లాక్ చేయి"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"పరికరం లాక్ చేయబడింది"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"ముఖాన్ని స్కాన్ చేస్తోంది"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"పంపు"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ముఖం గుర్తించడం కుదరలేదు"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"బదులుగా వేలిముద్రను ఉపయోగించండి"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"బ్లూటూత్ కనెక్ట్ చేయబడింది."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"బ్యాటరీ శాతం తెలియదు."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"ఎయిర్ప్లేన్ మోడ్."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPNలో."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"బ్యాటరీ <xliff:g id="NUMBER">%d</xliff:g> శాతం."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"బ్యాటరీ <xliff:g id="PERCENTAGE">%1$s</xliff:g> శాతం ఉంది, మీ వినియోగాన్ని బట్టి <xliff:g id="TIME">%2$s</xliff:g> పని చేస్తుంది"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"బ్యాటరీ ఛార్జ్ అవుతోంది, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> శాతం వద్ద ఉంది."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"అన్ని నోటిఫికేషన్లను చూడండి"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"టెలిటైప్రైటర్ ప్రారంభించబడింది."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"రింగర్ వైబ్రేట్లో ఉంది."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"మీరు ఏదైనా యాప్ను షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఆ యాప్లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>కు యాక్సెస్ ఉంటుంది. కాబట్టి, పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, లేదా ఏదైనా ఇతర సున్నితమైన సమాచారం పట్ల జాగ్రత్త వహించండి."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"కొనసాగించండి"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"యాప్ను షేర్ చేయండి లేదా రికార్డ్ చేయండి"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"షేర్ చేయడానికి లేదా రికార్డ్ చేయడానికి ఈ యాప్ను అనుమతించాలా?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"మీరు షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఈ యాప్ మీ స్క్రీన్పై కనిపించే దేనికైనా లేదా మీ పరికరంలో ప్లే అయిన దేనికైనా యాక్సెస్ను కలిగి ఉంటుంది. కాబట్టి, పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, లేదా ఏదైనా ఇతర సున్నితమైన సమాచారం పట్ల జాగ్రత్త వహించండి."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"మీరు ఏదైనా యాప్ను షేర్ చేస్తున్నప్పుడు, రికార్డ్ చేస్తున్నప్పుడు, లేదా ప్రసారం చేస్తున్నప్పుడు, ఆ యాప్లో చూపబడిన దేనికైనా లేదా ప్లే అయిన దేనికైనా ఈ యాప్ యాక్సెస్ను కలిగి ఉంటుంది. కాబట్టి, పాస్వర్డ్లు, పేమెంట్ వివరాలు, మెసేజ్లు, లేదా ఏదైనా ఇతర సున్నితమైన సమాచారం పట్ల జాగ్రత్త వహించండి."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"మీ IT అడ్మిన్ ద్వారా బ్లాక్ చేయబడింది"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"పరికర పాలసీ ద్వారా స్క్రీన్ క్యాప్చర్ చేయడం డిజేబుల్ చేయబడింది"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"మేనేజ్ చేయండి"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"హిస్టరీ"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ఉపయోగించడానికి అన్లాక్ చేయండి"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"మీ కార్డ్లను పొందడంలో సమస్య ఉంది, దయచేసి తర్వాత మళ్లీ ట్రై చేయండి"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"లాక్ స్క్రీన్ సెట్టింగ్లు"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR కోడ్ను స్కాన్ చేయండి"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"ఆఫీస్ ప్రొఫైల్"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ఎయిర్ప్లేన్ మోడ్"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"మీరు <xliff:g id="WHEN">%1$s</xliff:g> సెట్ చేసిన మీ తర్వాత అలారం మీకు వినిపించదు"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"మొబైల్ డేటాను ఆఫ్ చేయాలా?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"\"<xliff:g id="CARRIER">%s</xliff:g>\" ద్వారా మీకు డేటా లేదా ఇంటర్నెట్కు యాక్సెస్ ఉండదు. Wi-Fi ద్వారా మాత్రమే ఇంటర్నెట్ అందుబాటులో ఉంటుంది."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"మీ క్యారియర్"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g>కి తిరిగి మారాలా?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"మొబైల్ డేటా లభ్యత ఆధారంగా ఆటోమేటిక్గా స్విచ్ అవ్వదు"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"వద్దు, థ్యాంక్స్"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"అవును, మార్చండి"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"అనుమతి రిక్వెస్ట్కు ఒక యాప్ అడ్డు తగులుతున్నందున సెట్టింగ్లు మీ ప్రతిస్పందనను ధృవీకరించలేకపోయాయి."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> స్లైస్లను చూపించడానికి <xliff:g id="APP_0">%1$s</xliff:g>ని అనుమతించండి?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ఇది <xliff:g id="APP">%1$s</xliff:g> నుండి సమాచారాన్ని చదువుతుంది"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"మొబైల్ డేటా"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"కనెక్ట్ చేయబడింది"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"తాత్కాలికంగా కనెక్ట్ చేయబడింది"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"కనెక్షన్ బాగాలేదు"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"మొబైల్ డేటా ఆటోమెటిక్గా కనెక్ట్ అవ్వదు"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"కనెక్షన్ లేదు"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ఇతర నెట్వర్క్లేవీ అందుబాటులో లేవు"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index bb3e4fa..f4116ad 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -125,7 +125,7 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"ตัวช่วยเสียง"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"เครื่องมือสแกนคิวอาร์โค้ด"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"ปลดล็อก"</string>
+ <string name="accessibility_unlock_button" msgid="3613812140816244310">"ปลดล็อกแล้ว"</string>
<string name="accessibility_lock_icon" msgid="661492842417875775">"อุปกรณ์ถูกล็อก"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"กำลังสแกนใบหน้า"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"ส่ง"</string>
@@ -168,7 +168,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"ไม่รู้จักใบหน้า"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"ใช้ลายนิ้วมือแทน"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"เชื่อมต่อบลูทูธแล้ว"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"ไม่ทราบเปอร์เซ็นต์แบตเตอรี่"</string>
@@ -180,8 +180,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"โหมดบนเครื่องบิน"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN เปิดอยู่"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"แบตเตอรี่ <xliff:g id="NUMBER">%d</xliff:g> เปอร์เซ็นต์"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"แบตเตอรี่ <xliff:g id="PERCENTAGE">%1$s</xliff:g> เปอร์เซ็นต์ ใช้ได้อีกประมาณ <xliff:g id="TIME">%2$s</xliff:g> ทั้งนี้ขึ้นอยู่กับการใช้งานของคุณ"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"กำลังชาร์จแบตเตอรี่ <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> เปอร์เซ็นต์"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"ดูการแจ้งเตือนทั้งหมด"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"เปิดใช้งาน TeleTypewriter อยู่"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"เสียงเรียกเข้าแบบสั่น"</string>
@@ -375,16 +380,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"เมื่อกำลังแชร์ บันทึก หรือแคสต์แอป \"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>\" จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังเกี่ยวกับรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ หรือข้อมูลที่ละเอียดอ่อนอื่นๆ"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"ต่อไป"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"แชร์หรือบันทึกแอป"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"อนุญาตให้แอปนี้แชร์หรือบันทึกไหม"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"เมื่อกำลังแชร์ บันทึก หรือแคสต์ แอปนี้จะมีสิทธิ์เข้าถึงทุกสิ่งที่ปรากฏบนหน้าจอหรือเล่นอยู่ในอุปกรณ์ ดังนั้นโปรดระวังเกี่ยวกับรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ หรือข้อมูลที่ละเอียดอ่อนอื่นๆ"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"เมื่อกำลังแชร์ บันทึก หรือแคสต์แอป แอปนี้จะมีสิทธิ์เข้าถึงทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังเกี่ยวกับรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ หรือข้อมูลที่ละเอียดอ่อนอื่นๆ"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"ผู้ดูแลระบบไอทีบล็อกไว้"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"การจับภาพหน้าจอปิดใช้โดยนโยบายด้านอุปกรณ์"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"ล้างทั้งหมด"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"จัดการ"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"ประวัติ"</string>
@@ -500,7 +500,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"ปลดล็อกเพื่อใช้"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"เกิดปัญหาในการดึงข้อมูลบัตรของคุณ โปรดลองอีกครั้งในภายหลัง"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"การตั้งค่าหน้าจอล็อก"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"สแกนคิวอาร์โค้ด"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"เครื่องมือสแกนคิวอาร์โค้ด"</string>
<string name="status_bar_work" msgid="5238641949837091056">"โปรไฟล์งาน"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"โหมดบนเครื่องบิน"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"คุณจะไม่ได้ยินเสียงปลุกครั้งถัดไปในเวลา <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +739,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ปิดอินเทอร์เน็ตมือถือไหม"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"คุณจะใช้เน็ตมือถือหรืออินเทอร์เน็ตผ่าน \"<xliff:g id="CARRIER">%s</xliff:g>\" ไม่ได้ แต่จะใช้ผ่าน Wi-Fi ได้เท่านั้น"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ผู้ให้บริการของคุณ"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"เปลี่ยนกลับเป็น <xliff:g id="CARRIER">%s</xliff:g> หรือไม่"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"อินเทอร์เน็ตมือถือไม่ได้เปลี่ยนตามความพร้อมบริการโดยอัตโนมัติ"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ไม่เป็นไร"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ใช่ เปลี่ยนเลย"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"เนื่องจากแอปหนึ่งได้บดบังคำขอสิทธิ์ ระบบจึงไม่สามารถยืนยันคำตอบของคุณสำหรับการตั้งค่าได้"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"อนุญาตให้ <xliff:g id="APP_0">%1$s</xliff:g> แสดงส่วนต่างๆ ของ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- อ่านข้อมูลจาก <xliff:g id="APP">%1$s</xliff:g> ได้"</string>
@@ -929,6 +933,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"อินเทอร์เน็ตมือถือ"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"เชื่อมต่อแล้ว"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"เชื่อมต่อแล้วชั่วคราว"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"การเชื่อมต่อไม่ดี"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"อินเทอร์เน็ตมือถือจะไม่เชื่อมต่ออัตโนมัติ"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"ไม่มีการเชื่อมต่อ"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"ไม่มีเครือข่ายอื่นๆ ที่พร้อมใช้งาน"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 8ddb64e..468c8a4 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Voice Assist"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Scanner ng QR Code"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"I-unlock"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Naka-lock ang device"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Sina-scan ang mukha"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Ipadala"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Hindi makilala ang mukha"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Gumamit ng fingerprint"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Nakakonekta ang Bluetooth."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Hindi alam ang porsyento ng baterya."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Mode na eroplano."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"Naka-on ang VPN."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Baterya <xliff:g id="NUMBER">%d</xliff:g> (na) porsyento."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> (na) porsyento ang baterya, nasa <xliff:g id="TIME">%2$s</xliff:g> ang natitira batay sa paggamit mo"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Nagcha-charge ang baterya, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> (na) porsyento."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Tingnan ang lahat ng notification"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Pinapagana ang TeleTypewriter."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Pag-vibrate ng ringer."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Kapag nagbabahagi, nagre-record, o nagka-cast ka ng app, may access ang <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga password, detalye ng pagbabayad, mensahe, o iba pang impormasyon."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Magpatuloy"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Ibahagi o i-record ang isang app"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Payagan ang app na ito na magbahagi o mag-record?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Kapag nagbabahagi, nagre-record, o nagka-cast ka, may access ang app na ito sa kahit anong nakikita sa iyong screen o pine-play sa device mo. Kaya mag-ingat sa mga password, detalye ng pagbabayad, mensahe, o iba pang sensitibong impormasyon."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Kapag nagbabahagi, nagre-record, o nagka-cast ka ng app, may access ang app na ito sa kahit anong ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga password, detalye ng pagbabayad, mensahe, o iba pang impormasyon."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Na-block ng iyong IT admin"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Naka-disable ang pag-screen capture ayon sa patakaran ng device"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"I-clear lahat"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Pamahalaan"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"History"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"I-unlock para magamit"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Nagkaproblema sa pagkuha ng iyong mga card, pakisubukan ulit sa ibang pagkakataon"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Mga setting ng lock screen"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"I-scan ang QR code"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"Scanner ng QR code"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Profile sa trabaho"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Airplane mode"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Hindi mo maririnig ang iyong susunod na alarm ng <xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"I-off ang mobile data?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Hindi ka magkaka-access sa data o internet sa pamamagitan ng <xliff:g id="CARRIER">%s</xliff:g>. Available lang ang internet sa pamamagitan ng Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ang iyong carrier"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Bumalik sa <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Hindi awtomatikong magbabago ang mobile data base sa availability"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Hindi, salamat na lang"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Oo, lumipat"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Hindi ma-verify ng Mga Setting ang iyong tugon dahil may app na tumatakip sa isang kahilingan sa pagpapahintulot."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Payagan ang <xliff:g id="APP_0">%1$s</xliff:g> na ipakita ang mga slice ng <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Nakakabasa ito ng impormasyon mula sa <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Nakakonekta"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Pansamantalang nakakonekta"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Mahina ang koneksyon"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Hindi awtomatikong kokonekta ang mobile data"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Walang koneksyon"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Walang available na iba pang network"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 177ce98..7c126e5 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Sesli Yardım"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Cüzdan"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR kodu tarayıcı"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Kilidi aç"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Cihaz kilitlendi"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Yüz taranıyor"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Gönder"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Yüz tanınamadı"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Bunun yerine parmak izi kullanın"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth bağlandı."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Pil yüzdesi bilinmiyor."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Uçak modu."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN açık."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Pil yüzdesi: <xliff:g id="NUMBER">%d</xliff:g>"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Pil yüzde <xliff:g id="PERCENTAGE">%1$s</xliff:g> dolu. Kullanımınıza göre yaklaşık <xliff:g id="TIME">%2$s</xliff:g> süresi kaldı"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Pil şarj oluyor, yüzde <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Tüm bildirimleri göster"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter etkin."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Telefon zili titreşim."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Bir uygulamayı paylaşma, kaydetme ve yayınlama özelliklerini kullandığınızda <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>, söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Devam"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Uygulamayı paylaşın veya kaydedin"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Bu uygulamanın paylaşım ve kayıt yapmasına izin verilsin mi?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Paylaşım, kayıt ve yayınlama özelliklerini kullandığınızda, ekranınızda görünen veya cihazınızda oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Bir uygulamayı paylaşma, kaydetme ve yayınlama özelliklerini kullandığınızda , söz konusu uygulamada gösterilen veya oynatılan her şeye erişebilir. Dolayısıyla şifreler, ödeme ayrıntıları, mesajlar veya diğer hassas bilgiler konusunda dikkatli olun."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"BT yöneticiniz tarafından engellendi"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekran görüntüsü alma, cihaz politikası tarafından devre dışı bırakıldı"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Tümünü temizle"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Yönet"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Geçmiş"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Kullanmak için kilidi aç"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Kartlarınız alınırken bir sorun oluştu. Lütfen daha sonra tekrar deneyin"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Kilit ekranı ayarları"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR kodunu tara"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"İş profili"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Uçak modu"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"<xliff:g id="WHEN">%1$s</xliff:g> olarak ayarlanmış bir sonraki alarmınızı duymayacaksınız"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil veri kapatılsın mı?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> üzerinden veri veya internet erişiminiz olmayacak. İnternet yalnızca kablosuz bağlantı üzerinden kullanılabilecek."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatörünüz"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> operatörüne geri dönülsün mü?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Uygunluk durumuna göre otomatik olarak mobil veriye geçilmez"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Hayır, teşekkürler"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Evet, geçilsin"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Bir uygulama bir izin isteğinin anlaşılmasını engellediğinden, Ayarlar, yanıtınızı doğrulayamıyor."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> uygulamasının, <xliff:g id="APP_2">%2$s</xliff:g> dilimlerini göstermesine izin verilsin mi?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> uygulamasından bilgileri okuyabilir"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil veri"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Bağlı"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Geçici olarak bağlandı"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Bağlantı zayıf"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil veri otomatik olarak bağlanmıyor"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Bağlantı yok"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Kullanılabilir başka ağ yok"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index fdfcadc..1c32bb1d 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Голосові підказки"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Гаманець"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Сканер QR-коду"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Розблокувати"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Пристрій заблоковано"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Сканування обличчя"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Надіслати"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Обличчя не розпізнано"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Скористайтеся відбитком"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth під’єднано."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Відсоток заряду акумулятора невідомий."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Режим польоту."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"Мережу VPN увімкнено."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Заряд акумулятора у відсотках: <xliff:g id="NUMBER">%d</xliff:g>."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Згідно з даними про використання залишилося <xliff:g id="PERCENTAGE">%1$s</xliff:g> заряду акумулятора – близько <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Акумулятор заряджається, поточний заряд <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> відсотків."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Переглянути всі сповіщення"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Телетайп увімкнено."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Дзвінок на вібросигналі."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Коли ви показуєте, записуєте або транслюєте додаток, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> отримує доступ до всього, що відображається або відтворюється в цьому додатку. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Продовжити"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Показувати або записувати додаток"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Дозволити цьому додатку показувати або записувати екран?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Коли ви показуєте, записуєте або транслюєте екран, цей додаток отримує доступ до всього, що відображається на екрані чи відтворюється на пристрої. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Коли ви показуєте, записуєте або транслюєте додаток, доступ до всього, що відображається або відтворюється в ньому, отримує цей додаток. Тому будьте уважні з паролями, повідомленнями, платіжною й іншою конфіденційною інформацією."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Заблоковано адміністратором"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Запис екрана вимкнено згідно з правилами для пристрою"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Очистити все"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Керувати"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Історія"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Розблокувати, щоб використовувати"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Не вдалось отримати ваші картки. Повторіть спробу пізніше."</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Параметри блокування екрана"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Сканувати QR-код"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Робочий профіль"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Режим польоту"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Наступний сигнал о <xliff:g id="WHEN">%1$s</xliff:g> не пролунає"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Вимкнути мобільний Інтернет?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Ви не матимете доступу до даних чи Інтернету через оператора <xliff:g id="CARRIER">%s</xliff:g>. Інтернет буде доступний лише через Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш оператор"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Перейти на <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Пристрій не перемикатиметься на мобільний Інтернет автоматично"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Ні, дякую"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Так, перемикатися"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Не вдається підтвердити вашу відповідь у налаштуваннях, оскільки інший додаток заступає запит на дозвіл."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Дозволити додатку <xliff:g id="APP_0">%1$s</xliff:g> показувати фрагменти додатка <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Має доступ до інформації з додатка <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобільний трафік"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Підключено"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Тимчасово з’єднано"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Погане з’єднання"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобільний Інтернет не підключатиметься автоматично"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Немає з\'єднання"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Інші мережі недоступні"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index e222148..b78c50e 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"صوتی معاون"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"والٹ"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR کوڈ اسکینر"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"غیر مقفل کریں"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"آلہ مقفل کر دیا گیا"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"اسکیننگ چہرہ"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"بھیجیں"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"چہرے کی پہچان نہیں ہو سکی"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"اس کے بجائے فنگر پرنٹ استعمال کریں"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"بلوٹوتھ مربوط ہے۔"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"بیٹری کی فیصد نامعلوم ہے۔"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"ہوائی جہاز وضع۔"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN آن ہے۔"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"بیٹری <xliff:g id="NUMBER">%d</xliff:g> فیصد۔"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"آپ کے استعمال کی بنیاد پر بیٹری <xliff:g id="PERCENTAGE">%1$s</xliff:g> فیصد، تقریباً <xliff:g id="TIME">%2$s</xliff:g> باقی ہے"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"بیٹری چارج ہو رہی ہے، اس وقت <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> فیصد ہے۔"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"تمام اطلاعات دیکھیں"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"ٹیلی ٹائپ رائٹر فعال ہے۔"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"رنگر وائبریٹ۔"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> کو آپ کی اسکرین پر دکھائی گئی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"جاری رکھیں"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"ایپ کا اشتراک یا ریکارڈ کریں"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"اس ایپ کو اشتراک یا ریکارڈ کرنے کی اجازت دیں؟"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو اس ایپ کو آپ کی اسکرین پر دکھائی دینے والی یا آپ کے آلے پر چلنے والی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"جب آپ اشتراک، ریکارڈنگ یا کاسٹ کر رہے ہوتے ہیں تو اس ایپ کو آپ کی اسکرین پر دکھائی گئی یا آپ کے آلے پر چلائی گئی ہر چیز تک رسائی حاصل ہوتی ہے۔ اس لیے پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، یا دیگر حساس معلومات کے سلسلے میں محتاط رہیں۔"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"آپ کے IT منتظم نے مسدود کر دیا"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"اسکرین کو کیپچر کرنا آلہ کی پالیسی کے ذریعے غیر فعال ہے"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"سبھی کو صاف کریں"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"نظم کریں"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"سرگزشت"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"استعمال کرنے کے لیے غیر مقفل کریں"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"آپ کے کارڈز حاصل کرنے میں ایک مسئلہ درپیش تھا، براہ کرم بعد میں دوبارہ کوشش کریں"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"مقفل اسکرین کی ترتیبات"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR کوڈ اسکین کریں"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"دفتری پروفائل"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"ہوائی جہاز وضع"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"آپ کو <xliff:g id="WHEN">%1$s</xliff:g> بجے اپنا اگلا الارم سنائی نہیں دے گا"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"موبائل ڈیٹا آف کریں؟"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"آپ کو <xliff:g id="CARRIER">%s</xliff:g> کے ذریعے ڈیٹا یا انٹرنیٹ تک رسائی حاصل نہیں ہوگی۔ انٹرنیٹ صرف Wi-Fi کے ذریعے دستیاب ہوگا۔"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"آپ کا کریئر"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> پر واپس سوئچ کریں؟"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"دستیابی کی بنیاد پر موبائل ڈیٹا خودکار طور پر تبدیل نہیں ہوگا"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"نہیں شکریہ"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ہاں، سوئچ کریں"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"چونکہ ایک ایپ اجازت کی درخواست کو مبہم کر رہی ہے، لہذا ترتیبات آپ کے جواب کی توثیق نہیں کر سکتی ہیں۔"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> کو <xliff:g id="APP_2">%2$s</xliff:g> کے سلائسز دکھانے کی اجازت دیں؟"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- یہ <xliff:g id="APP">%1$s</xliff:g> کی معلومات پڑھ سکتا ہے"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"موبائل ڈیٹا"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="NETWORKMODE">%2$s</xliff:g> / <xliff:g id="STATE">%1$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"منسلک ہے"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"عارضی طور پر منسلک ہے"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"کمزور کنکشن"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"موبائل ڈیٹا خودکار طور پر منسلک نہیں ہوگا"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"کوئی کنکشن نہیں"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"کوئی دوسرا نیٹ ورک دستیاب نہیں ہے"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 597fa4e..3b34ac5 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Ovozli yordam"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR kod skaneri"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Qulfdan chiqarish"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Qurilma qulflandi"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Yuzni skanerlash"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Yuborish"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Yuz aniqlanmadi"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Barmoq izi orqali urining"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ulandi."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Batareya quvvati foizi nomaʼlum."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Parvoz rejimi"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN yoniq."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Batareya <xliff:g id="NUMBER">%d</xliff:g> foiz."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Batareya quvvati <xliff:g id="PERCENTAGE">%1$s</xliff:g> foiz, joriy holatda yana <xliff:g id="TIME">%2$s</xliff:g> qoldi"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Batareya quvvat olmoqda, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> foiz."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Barcha bildirishnomalarni ko‘rish"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter yoqildi."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Vibratsiyali qo‘ng‘iroq"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Ulashish, yozib olish va translatsiya qilish vaqtida <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ilovasi ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar yoki boshqa maxfiy axborot chiqmasligi uchun ehtiyot boʻling."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Davom etish"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Ilovada ulashish yoki yozib olish"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Bu ilovaga ulashish yoki yozib olish uchun ruxsat berilsinmi?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Ulashish, yozib olish va translatsiya qilish vaqtida bu ilova ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar yoki boshqa maxfiy axborot chiqmasligi uchun ehtiyot boʻling."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Ulashish, yozib olish va translatsiya qilish vaqtida bu ilova ekranda chiqadigan yoki qurilmada ijro qilinadigan kontentni koʻra oladi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar yoki boshqa maxfiy axborot chiqmasligi uchun ehtiyot boʻling."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"AT administratoringiz tomonidan bloklangan"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ekranni tasvirga olish qurilmadan foydalanish tartibi tomonidan faolsizlantirilgan"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Hammasini tozalash"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Boshqarish"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Tarix"</string>
@@ -500,7 +501,7 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Foydalanish uchun qulfdan chiqarish"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Bildirgilarni yuklashda xatolik yuz berdi, keyinroq qaytadan urining"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Qulflangan ekran sozlamalari"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"QR kodni skanerlash"</string>
+ <string name="qr_code_scanner_title" msgid="1938155688725760702">"QR kod skaneri"</string>
<string name="status_bar_work" msgid="5238641949837091056">"Ish profili"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Parvoz rejimi"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Keyingi signal (<xliff:g id="WHEN">%1$s</xliff:g>) chalinmaydi"</string>
@@ -739,6 +740,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil internet uzilsinmi?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> orqali internetdan foydalana olmaysiz. Internet faqat Wi-Fi orqali ishlaydi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"aloqa operatoringiz"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"<xliff:g id="CARRIER">%s</xliff:g> xizmati qaytarilsinmi?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobil internet mavjudligi asosida avtomatik almashtirilmaydi"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Kerak emas"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Ha, almashtirilsin"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Ilova ruxsatnoma so‘roviga xalaqit qilayotgani tufayli, “Sozlamalar” ilovasi javobingizni tekshira olmaydi."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ilovasiga <xliff:g id="APP_2">%2$s</xliff:g> ilovasidan fragmentlar ko‘rsatishga ruxsat berilsinmi?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"– <xliff:g id="APP">%1$s</xliff:g> ma’lumotlarini o‘qiy oladi"</string>
@@ -929,6 +934,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil internet"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ulandi"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Vaqtincha ulangan"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Aloqa beqaror"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil internetga avtomatik ulanmaydi"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Internetga ulanmagansiz"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Boshqa tarmoqlar mavjud emas"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 485676a..ea86c1e 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Trợ lý thoại"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"Ví"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Trình quét mã QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Mở khóa"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Đã khóa thiết bị"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Quét tìm khuôn mặt"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Gửi"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Không nhận ra khuôn mặt"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Hãy dùng vân tay"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Đã kết nối bluetooth."</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Tỷ lệ phần trăm pin không xác định."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Chế độ trên máy bay."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN đang bật."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"<xliff:g id="NUMBER">%d</xliff:g> phần trăm pin."</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"<xliff:g id="PERCENTAGE">%1$s</xliff:g> phần trăm pin, còn khoảng <xliff:g id="TIME">%2$s</xliff:g> dựa trên mức sử dụng của bạn"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Đang sạc pin, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%."</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Xem tất cả thông báo"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"Đã bật TeleTypewriter."</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Chuông rung."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Khi bạn chia sẻ, ghi hoặc truyền ứng dụng, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> sẽ có quyền truy cập vào mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy thận trọng để không làm lộ mật khẩu, thông tin thanh toán, tin nhắn hoặc thông tin nhạy cảm khác."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Tiếp tục"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Chia sẻ hoặc ghi ứng dụng"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Cho phép ứng dụng này chia sẻ hoặc ghi?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Khi bạn chia sẻ, ghi hoặc truyền, ứng dụng này sẽ truy cập được vào mọi nội dung xuất hiện trên màn hình hoặc phát trên thiết bị của bạn. Vì vậy, hãy để ý đến mật khẩu, thông tin thanh toán, tin nhắn hoặc thông tin nhạy cảm khác."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Khi bạn chia sẻ, ghi hoặc truyền ứng dụng, ứng dụng này sẽ truy cập được vào mọi nội dung xuất hiện hoặc phát trên ứng dụng đó. Vì vậy, hãy để ý đến mật khẩu, thông tin thanh toán, tin nhắn hoặc thông tin nhạy cảm khác."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Bị quản trị viên CNTT chặn"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Tính năng chụp ảnh màn hình đã bị tắt theo chính sách thiết bị"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Quản lý"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Lịch sử"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Mở khóa để sử dụng"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Đã xảy ra sự cố khi tải thẻ của bạn. Vui lòng thử lại sau"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Cài đặt màn hình khóa"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Quét mã QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Hồ sơ công việc"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Chế độ máy bay"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Bạn sẽ không nghe thấy báo thức tiếp theo lúc <xliff:g id="WHEN">%1$s</xliff:g> của mình"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Tắt dữ liệu di động?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Bạn sẽ không có quyền sử dụng dữ liệu hoặc truy cập Internet thông qua chế độ <xliff:g id="CARRIER">%s</xliff:g>. Bạn chỉ có thể truy cập Internet thông qua Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"nhà mạng của bạn"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Chuyển về <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Dữ liệu di động sẽ không tự động chuyển dựa trên tình trạng phủ sóng"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Không, cảm ơn"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Có, hãy chuyển"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Vì ứng dụng đang che khuất yêu cầu cấp quyền nên Cài đặt không thể xác minh câu trả lời của bạn."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Cho phép <xliff:g id="APP_0">%1$s</xliff:g> hiển thị các lát của <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Có thể đọc thông tin từ <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Dữ liệu di động"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Đã kết nối"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Tạm thời có kết nối"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Kết nối kém"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Dữ liệu di động sẽ không tự động kết nối"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Không có kết nối mạng"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Không có mạng nào khác"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 1b65855..ffe4f5f 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"语音助理"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"钱包"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"二维码扫描器"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"解锁"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"设备已锁定"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"正在扫描面孔"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"发送"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"人脸识别失败"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"改用指纹"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"蓝牙已连接。"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"电池电量百分比未知。"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"飞行模式。"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN 已开启。"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"电池电量为百分之 <xliff:g id="NUMBER">%d</xliff:g>。"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"电池电量为 <xliff:g id="PERCENTAGE">%1$s</xliff:g>,根据您的使用情况,大约还可使用<xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"正在充电,已完成 <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%。"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"查看所有通知"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"电传打字机已启用。"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"振铃器振动。"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"在您进行分享、录制或投射时,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以访问通过此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"继续"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或录制应用"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"是否允许此应用进行分享或录制?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"在您进行分享、录制或投屏时,此应用可以访问您的屏幕显示或设备播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"在您进行分享、录制或投屏时,此应用可以访问通过此应用显示或播放的所有内容。因此,请注意保护密码、付款信息、消息或其他敏感信息。"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被 IT 管理员禁止"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"设备政策已停用屏幕截图功能"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"历史记录"</string>
@@ -499,8 +500,9 @@
<string name="wallet_secondary_label_updating" msgid="5726130686114928551">"正在更新"</string>
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解锁设备即可使用"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"获取您的卡片时出现问题,请稍后重试"</string>
- <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"锁定屏幕设置"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"扫描二维码"</string>
+ <string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"锁屏设置"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"工作资料"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"飞行模式"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"您在<xliff:g id="WHEN">%1$s</xliff:g>将不会听到下次闹钟响铃"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"要关闭移动数据网络吗?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"您将无法通过<xliff:g id="CARRIER">%s</xliff:g>使用移动数据或互联网,只能通过 WLAN 连接到互联网。"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"您的运营商"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"切换回 <xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"移动流量不会根据可用性自动切换"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"不用了"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"是,切换"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"由于某个应用遮挡了权限请求界面,因此“设置”应用无法验证您的回应。"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"要允许“<xliff:g id="APP_0">%1$s</xliff:g>”显示“<xliff:g id="APP_2">%2$s</xliff:g>”图块吗?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- 可以读取“<xliff:g id="APP">%1$s</xliff:g>”中的信息"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"移动数据网络"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"已连接"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"暂时已连接"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"连接状况不佳"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"系统将不会自动连接到移动数据网络"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"无网络连接"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"没有其他可用网络"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 75cc85b..9a5cbd2 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"語音助手"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"錢包"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR 碼掃瞄器"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"解鎖"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"裝置已上鎖"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"掃瞄緊面孔"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"傳送"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識面孔"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"電量百分比不明。"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"飛航模式。"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"開咗 VPN。"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"電池電量為百分之 <xliff:g id="NUMBER">%d</xliff:g>。"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"電量仲剩番 <xliff:g id="PERCENTAGE">%1$s</xliff:g>。根據你嘅使用情況,仲可以用大約 <xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"正在充電:<xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%。"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"睇所有通知"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter (TTY) 已啟用。"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"鈴聲震動。"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"要允許此應用程式分享或錄製內容嗎?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"進行分享、錄製或投放時,此應用程式可存取顯示在螢幕畫面上或在裝置上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"進行分享、錄製或投放時,此應用程式可存取顯示在螢幕畫面上或在該應用程式上播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他敏感資料。"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"已被您的 IT 管理員封鎖"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"螢幕截圖功能因裝置政策而停用"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"記錄"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解鎖即可使用"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"擷取資訊卡時發生問題,請稍後再試。"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"上鎖畫面設定"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"掃瞄 QR 碼"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"工作設定檔"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"飛行模式"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"您不會<xliff:g id="WHEN">%1$s</xliff:g>聽到鬧鐘"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"要關閉流動數據嗎?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"您無法透過「<xliff:g id="CARRIER">%s</xliff:g>」使用流動數據或互聯網。如要使用互聯網,您必須連接 Wi-Fi。"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"您的流動網絡供應商"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"要切換回「<xliff:g id="CARRIER">%s</xliff:g>」嗎?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"流動數據不會根據可用性自動切換"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"不用了,謝謝"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"是,切換回 DDS 對話框"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"由於某個應用程式已阻擋權限要求畫面,因此「設定」應用程式無法驗證您的回應。"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的快訊嗎?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- 可以讀取「<xliff:g id="APP">%1$s</xliff:g>」中的資料"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"流動數據"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"已連線"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"已暫時連線"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"連線速度欠佳"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"不會自動連線至流動數據"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"沒有連線"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"沒有可用的其他網絡"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index ae8e6db..9a31504 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"語音小幫手"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"錢包"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"QR code 掃描器"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"解除鎖定"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"裝置已鎖定"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"掃描臉孔"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"傳送"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"無法辨識臉孔"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"請改用指紋"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"藍牙連線已建立。"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"電池電量不明。"</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"飛行模式。"</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"VPN 已開啟。"</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"電池電量為百分之 <xliff:g id="NUMBER">%d</xliff:g>。"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"目前的電量為 <xliff:g id="PERCENTAGE">%1$s</xliff:g>。根據你的使用情形,大約還能使用到<xliff:g id="TIME">%2$s</xliff:g>"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"充電中,已完成 <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g>%%。"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"查看所有通知"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"TeleTypewriter (TTY) 已啟用。"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"鈴聲震動。"</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"進行分享、錄製或投放應用程式時,<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> 可以存取在該應用程式中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"繼續"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"分享或錄製應用程式"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"要允許這個應用程式分享或錄製嗎?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"進行分享、錄製或投放時,這個應用程式可以存取螢幕畫面上所顯示或裝置上所播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"進行分享、錄製或投放應用程式時,這個應用程式可以存取在其中顯示或播放的所有內容。因此請謹慎處理密碼、付款資料、訊息或其他機密資訊。"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"IT 管理員已封鎖這項操作"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"根據裝置政策規定,螢幕畫面擷取功能已停用"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"全部清除"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"管理"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"記錄"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"解鎖即可使用"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"擷取卡片時發生問題,請稍後再試"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"螢幕鎖定設定"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"掃描 QR 圖碼"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"工作資料夾"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"飛航模式"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"你不會聽到下一個<xliff:g id="WHEN">%1$s</xliff:g> 的鬧鐘"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"要關閉行動數據嗎?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"你將無法透過「<xliff:g id="CARRIER">%s</xliff:g>」使用行動數據或網際網路。你只能透過 Wi-Fi 使用網際網路。"</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"你的電信業者"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"要切換回「<xliff:g id="CARRIER">%s</xliff:g>」嗎?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"行動數據不會依據可用性自動切換"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"不用了,謝謝"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"是,切換回 DDS 對話方塊"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"由於某個應用程式覆蓋了權限要求畫面,因此「設定」應用程式無法驗證你的回應。"</string>
<string name="slice_permission_title" msgid="3262615140094151017">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的區塊嗎?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- 它可以讀取「<xliff:g id="APP">%1$s</xliff:g>」的資訊"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"行動數據"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"已連線"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"已暫時建立連線"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"連線品質不佳"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"系統將不會自動使用行動數據連線"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"沒有網路連線"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"沒有可用的其他網路"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 0ce0613..36f94ae 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -125,7 +125,8 @@
<string name="accessibility_voice_assist_button" msgid="6497706615649754510">"Isisekeli sezwi"</string>
<string name="accessibility_wallet_button" msgid="1458258783460555507">"I-wallet"</string>
<string name="accessibility_qr_code_scanner_button" msgid="7521277927692910795">"Iskena sekhodi ye-QR"</string>
- <string name="accessibility_unlock_button" msgid="122785427241471085">"Vula"</string>
+ <!-- no translation found for accessibility_unlock_button (3613812140816244310) -->
+ <skip />
<string name="accessibility_lock_icon" msgid="661492842417875775">"Idivayisi ikhiyiwe"</string>
<string name="accessibility_scanning_face" msgid="3093828357921541387">"Ukuskena ubuso"</string>
<string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Thumela"</string>
@@ -168,7 +169,7 @@
<skip />
<string name="keyguard_face_failed" msgid="9044619102286917151">"Ayikwazi ukubona ubuso"</string>
<string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"Kunalokho sebenzisa isigxivizo somunwe"</string>
- <!-- no translation found for keyguard_face_unlock_unavailable (8145547300240405980) -->
+ <!-- no translation found for keyguard_face_unlock_unavailable (1581949044193418736) -->
<skip />
<string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"Bluetooth ixhunyiwe"</string>
<string name="accessibility_battery_unknown" msgid="1807789554617976440">"Iphesenti lebhethri alaziwa."</string>
@@ -180,8 +181,13 @@
<string name="accessibility_airplane_mode" msgid="1899529214045998505">"Imodi yendiza."</string>
<string name="accessibility_vpn_on" msgid="8037549696057288731">"I-VPN ivuliwe."</string>
<string name="accessibility_battery_level" msgid="5143715405241138822">"Iphesenti <xliff:g id="NUMBER">%d</xliff:g> lebhethri"</string>
- <string name="accessibility_battery_level_with_estimate" msgid="4843119982547599452">"Amaphesenti ebhethri ngu-<xliff:g id="PERCENTAGE">%1$s</xliff:g>, cishe kusele okungu-<xliff:g id="TIME">%2$s</xliff:g> kusukela ekusetshenzisweni kwakho"</string>
+ <!-- no translation found for accessibility_battery_level_with_estimate (6548654589315074529) -->
+ <skip />
<string name="accessibility_battery_level_charging" msgid="8892191177774027364">"Ibhethri liyashaja, <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> %%"</string>
+ <!-- no translation found for accessibility_battery_level_charging_paused (3560711496775146763) -->
+ <skip />
+ <!-- no translation found for accessibility_battery_level_charging_paused_with_estimate (2223541217743647858) -->
+ <skip />
<string name="accessibility_overflow_action" msgid="8555835828182509104">"Bona zonke izaziso"</string>
<string name="accessibility_tty_enabled" msgid="1123180388823381118">"i-TeleTypewriter inikwe amandla"</string>
<string name="accessibility_ringer_vibrate" msgid="6261841170896561364">"Ukudlidliza kweringa."</string>
@@ -375,16 +381,11 @@
<string name="media_projection_permission_dialog_warning_single_app" msgid="1659532781536753059">"Uma wabelana, urekhoda, noma usakaza i-app, i-<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> inokufinyelela kunoma yini eboniswayo noma edlalwayo kuleyo app. Ngakho-ke qaphela amagama ayimfihlo, imininingwane yokukhokha, imiyalezo, noma olunye ulwazi olubucayi."</string>
<string name="media_projection_permission_dialog_continue" msgid="1827799658916736006">"Qhubeka"</string>
<string name="media_projection_permission_app_selector_title" msgid="894251621057480704">"Yabelana noma rekhoda i-app"</string>
- <!-- no translation found for media_projection_permission_dialog_system_service_title (6827129613741303726) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_entire_screen (8801616203805837575) -->
- <skip />
- <!-- no translation found for media_projection_permission_dialog_system_service_warning_single_app (543310680568419338) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_title (2113331792064527203) -->
- <skip />
- <!-- no translation found for screen_capturing_disabled_by_policy_dialog_description (6015975736747696431) -->
- <skip />
+ <string name="media_projection_permission_dialog_system_service_title" msgid="6827129613741303726">"Vumela le-app ukwabelana noma ukurekhoda?"</string>
+ <string name="media_projection_permission_dialog_system_service_warning_entire_screen" msgid="8801616203805837575">"Uma wabelana, urekhoda, noma usakaza, le-app inokufinyelela kunoma yini ebonakalayo kusikrini sakho noma edlalwa kudivayisi yakho. Ngakho-ke qaphela amagama ayimfihlo, imininingwane yokukhokha, imiyalezo, noma olunye ulwazi olubucayi."</string>
+ <string name="media_projection_permission_dialog_system_service_warning_single_app" msgid="543310680568419338">"Uma wabelana, urekhoda, noma usakaza i-app, le-app inokufinyelela kunoma yini eboniswayo noma edlalwayo kuleyo app. Ngakho-ke qaphela amagama ayimfihlo, imininingwane yokukhokha, imiyalezo, noma olunye ulwazi olubucayi."</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_title" msgid="2113331792064527203">"Kuvinjelwe ngumlawuli wakho we-IT"</string>
+ <string name="screen_capturing_disabled_by_policy_dialog_description" msgid="6015975736747696431">"Ukuthwebula isikrini kukhutshazwe yinqubomgomo yedivayisi"</string>
<string name="clear_all_notifications_text" msgid="348312370303046130">"Sula konke"</string>
<string name="manage_notifications_text" msgid="6885645344647733116">"Phatha"</string>
<string name="manage_notifications_history_text" msgid="57055985396576230">"Umlando"</string>
@@ -500,7 +501,8 @@
<string name="wallet_secondary_label_device_locked" msgid="5175862019125370506">"Vula ukuze usebenzise"</string>
<string name="wallet_error_generic" msgid="257704570182963611">"Kube khona inkinga yokuthola amakhadi akho, sicela uzame futhi ngemuva kwesikhathi"</string>
<string name="wallet_lockscreen_settings_label" msgid="3539105300870383570">"Amasethingi okukhiya isikrini"</string>
- <string name="qr_code_scanner_title" msgid="5290201053875420785">"Skena ikhodi ye-QR"</string>
+ <!-- no translation found for qr_code_scanner_title (1938155688725760702) -->
+ <skip />
<string name="status_bar_work" msgid="5238641949837091056">"Iphrofayela yomsebenzi"</string>
<string name="status_bar_airplane" msgid="4848702508684541009">"Imodi yendiza"</string>
<string name="zen_alarm_warning" msgid="7844303238486849503">"Ngeke uzwe i-alamu yakho elandelayo ngo-<xliff:g id="WHEN">%1$s</xliff:g>"</string>
@@ -739,6 +741,10 @@
<string name="mobile_data_disable_title" msgid="5366476131671617790">"Vala idatha yeselula?"</string>
<string name="mobile_data_disable_message" msgid="8604966027899770415">"Ngeke ube nokufinyelela kudatha noma ku-inthanethi nge-<xliff:g id="CARRIER">%s</xliff:g>. I-inthanethi izotholakala kuphela nge-Wi-Fi."</string>
<string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"inkampani yakho yenethiwekhi"</string>
+ <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Shintshela emuva ku-<xliff:g id="CARRIER">%s</xliff:g>?"</string>
+ <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Idatha yeselula ngeke ishintshe ngokuzenzakalelayo ngokusekelwe ekutholakaleni"</string>
+ <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"Cha ngiyabonga"</string>
+ <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yebo, shintsha"</string>
<string name="touch_filtered_warning" msgid="8119511393338714836">"Ngoba uhlelo lokusebenza lusitha isicelo semvume, Izilungiselelo azikwazi ukuqinisekisa impendulo yakho."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"Vumela i-<xliff:g id="APP_0">%1$s</xliff:g> ukuthi ibonise izingcezu ze-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- Ingafunda ulwazi kusukela ku-<xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -929,6 +935,8 @@
<string name="mobile_data_settings_title" msgid="3955246641380064901">"Idatha yeselula"</string>
<string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
<string name="mobile_data_connection_active" msgid="944490013299018227">"Ixhunyiwe"</string>
+ <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Ixhume okwesikhashana"</string>
+ <string name="mobile_data_poor_connection" msgid="819617772268371434">"Uxhumo olungeluhle"</string>
<string name="mobile_data_off_summary" msgid="3663995422004150567">"Idatha yeselula ngeke ikwazi ukuxhuma ngokuzenzekelayo"</string>
<string name="mobile_data_no_connection" msgid="1713872434869947377">"Alukho uxhumano"</string>
<string name="non_carrier_network_unavailable" msgid="770049357024492372">"Awekho amanye amanethiwekhi atholakalayo"</string>
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index 8221d78..04fc4b8 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -25,6 +25,9 @@
<!-- Whether to enable clipping on Quick Settings -->
<bool name="qs_enable_clipping">true</bool>
+ <!-- Whether to enable clipping on Notification Views -->
+ <bool name="notification_enable_clipping">true</bool>
+
<!-- Whether to enable transparent background for notification scrims -->
<bool name="notification_scrim_transparent">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4ce0852..43f3c9e 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -247,4 +247,8 @@
<color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color>
<color name="dream_overlay_status_bar_key_text_shadow_color">#66000000</color>
<color name="dream_overlay_status_bar_ambient_text_shadow_color">#59000000</color>
+
+ <!-- Rear Display Education -->
+ <color name="rear_display_overlay_animation_background_color">#1E1B17</color>
+ <color name="rear_display_overlay_dialog_background_color">#1E1B17</color>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ce9829b..7a36204 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -485,6 +485,12 @@
<!-- Whether to show a severe low battery dialog. -->
<bool name="config_severe_battery_dialog">false</bool>
+ <!-- A path representing a shield. Will sometimes be displayed with the battery icon when
+ needed. This path is a 10px wide and 13px tall. -->
+ <string name="config_batterymeterShieldPath" translatable="false">
+ M5 0L0 1.88V6.19C0 9.35 2.13 12.29 5 13.01C7.87 12.29 10 9.35 10 6.19V1.88L5 0Z
+ </string>
+
<!-- A path similar to frameworks/base/core/res/res/values/config.xml
config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
@@ -737,12 +743,35 @@
<!-- How long in milliseconds before full burn-in protection is achieved. -->
<integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>
+ <!-- The duration in milliseconds of the y-translation animation when waking up from
+ the dream -->
+ <integer name="config_dreamOverlayOutTranslationYDurationMs">333</integer>
+ <!-- The delay in milliseconds of the y-translation animation when waking up from
+ the dream for the complications at the bottom of the screen -->
+ <integer name="config_dreamOverlayOutTranslationYDelayBottomMs">33</integer>
+ <!-- The delay in milliseconds of the y-translation animation when waking up from
+ the dream for the complications at the top of the screen -->
+ <integer name="config_dreamOverlayOutTranslationYDelayTopMs">117</integer>
+ <!-- The duration in milliseconds of the alpha animation when waking up from the dream -->
+ <integer name="config_dreamOverlayOutAlphaDurationMs">200</integer>
+ <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
+ complications at the top of the screen -->
+ <integer name="config_dreamOverlayOutAlphaDelayTopMs">217</integer>
+ <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
+ complications at the bottom of the screen -->
+ <integer name="config_dreamOverlayOutAlphaDelayBottomMs">133</integer>
+ <!-- The duration in milliseconds of the blur animation when waking up from
+ the dream -->
+ <integer name="config_dreamOverlayOutBlurDurationMs">250</integer>
+
<integer name="complicationFadeOutMs">500</integer>
<integer name="complicationFadeInMs">500</integer>
<integer name="complicationRestoreMs">1000</integer>
+ <integer name="complicationFadeOutDelayMs">200</integer>
+
<!-- Duration in milliseconds of the dream in un-blur animation. -->
<integer name="config_dreamOverlayInBlurDurationMs">249</integer>
<!-- Delay in milliseconds of the dream in un-blur animation. -->
@@ -774,28 +803,27 @@
<item>com.android.systemui</item>
</string-array>
- <!-- The thresholds which determine the color used by the AQI dream overlay.
- NOTE: This must always be kept sorted from low to high -->
- <integer-array name="config_dreamAqiThresholds">
- <item>-1</item>
- <item>50</item>
- <item>100</item>
- <item>150</item>
- <item>200</item>
- <item>300</item>
- </integer-array>
-
- <!-- The color values which correspond to the thresholds above -->
- <integer-array name="config_dreamAqiColorValues">
- <item>@color/dream_overlay_aqi_good</item>
- <item>@color/dream_overlay_aqi_moderate</item>
- <item>@color/dream_overlay_aqi_unhealthy_sensitive</item>
- <item>@color/dream_overlay_aqi_unhealthy</item>
- <item>@color/dream_overlay_aqi_very_unhealthy</item>
- <item>@color/dream_overlay_aqi_hazardous</item>
- </integer-array>
-
<!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
is available. If false, UI will never show regardless of tethering availability" -->
<bool name="config_show_wifi_tethering">true</bool>
+
+ <!-- A collection of "slots" for placing quick affordance actions on the lock screen when the
+ device is locked. Each item is a string consisting of two parts, separated by the ':' character.
+ The first part is the unique ID for the slot, it is not a human-visible name, but should still
+ be unique across all slots specified. The second part is the capacity and must be a positive
+ integer; this is how many quick affordance actions that user is allowed to add to the slot. -->
+ <string-array name="config_keyguardQuickAffordanceSlots" translatable="false">
+ <item>bottom_start:1</item>
+ <item>bottom_end:1</item>
+ </string-array>
+
+ <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
+ string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
+ separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
+ default is displayed by System UI as long as the user hasn't made a different choice for that
+ slot. If the user did make a choice, even if the choice is the "None" option, the default is
+ ignored. -->
+ <string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
+ </string-array>
+
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e8ae929..e849b1f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -105,6 +105,12 @@
so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
<dimen name="status_bar_battery_icon_width">7.8dp</dimen>
+ <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
+ @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
+ the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
+ bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
+ <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
+
<!-- The font size for the clock in the status bar. -->
<dimen name="status_bar_clock_size">14sp</dimen>
@@ -1045,6 +1051,7 @@
<dimen name="qs_media_session_collapsed_guideline">144dp</dimen>
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
+ <dimen name="qs_media_rec_icon_top_margin">16dp</dimen>
<dimen name="qs_media_rec_album_size">88dp</dimen>
<dimen name="qs_media_rec_album_side_margin">16dp</dimen>
<dimen name="qs_media_rec_album_bottom_margin">8dp</dimen>
@@ -1161,7 +1168,7 @@
<!-- Screen record dialog -->
<dimen name="screenrecord_option_padding">18dp</dimen>
- <dimen name="screenrecord_logo_size">26dp</dimen>
+ <dimen name="screenrecord_logo_size">30dp</dimen>
<dimen name="screenrecord_option_icon_size">24dp</dimen>
<!-- Screen record status bar icon -->
<dimen name="screenrecord_status_text_size">14sp</dimen>
@@ -1169,6 +1176,18 @@
<dimen name="screenrecord_status_icon_width">21dp</dimen>
<dimen name="screenrecord_status_icon_height">17.5dp</dimen>
<dimen name="screenrecord_status_icon_bg_radius">8dp</dimen>
+ <!-- Screen record spinner -->
+ <dimen name="screenrecord_spinner_height">72dp</dimen>
+ <dimen name="screenrecord_spinner_margin">24dp</dimen>
+ <dimen name="screenrecord_spinner_text_padding_start">20dp</dimen>
+ <dimen name="screenrecord_spinner_text_padding_end">80dp</dimen>
+ <dimen name="screenrecord_spinner_arrow_size">24dp</dimen>
+ <dimen name="screenrecord_spinner_background_radius">28dp</dimen>
+
+ <dimen name="screenrecord_title_margin_top">20dp</dimen>
+ <dimen name="screenrecord_warning_line_height">20dp</dimen>
+ <dimen name="screenrecord_options_padding_bottom">16dp</dimen>
+ <dimen name="screenrecord_buttons_margin_top">20dp</dimen>
<!-- Keyguard user switcher -->
<dimen name="kg_user_switcher_text_size">16sp</dimen>
@@ -1398,6 +1417,11 @@
<dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
<dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+ <!-- Status bar user chip -->
+ <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
+ <dimen name="status_bar_user_chip_end_margin">12dp</dimen>
+ <dimen name="status_bar_user_chip_text_size">12sp</dimen>
+
<!-- Internet panel related dimensions -->
<dimen name="internet_dialog_list_max_height">662dp</dimen>
<!-- The height of the WiFi network in Internet panel. -->
@@ -1477,11 +1501,15 @@
<dimen name="dream_overlay_status_bar_extra_margin">8dp</dimen>
<!-- Dream overlay complications related dimensions -->
- <dimen name="dream_overlay_complication_clock_time_text_size">86sp</dimen>
+ <dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen>
+ <dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen>
+ <dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen>
<dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen>
<dimen name="dream_overlay_complication_preview_text_size">36sp</dimen>
<dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen>
<dimen name="dream_overlay_complication_shadow_padding">2dp</dimen>
+ <dimen name="dream_overlay_complication_smartspace_padding">24dp</dimen>
+ <dimen name="dream_overlay_complication_smartspace_max_width">408dp</dimen>
<!-- The position of the end guide, which dream overlay complications can align their start with
if their end is aligned with the parent end. Represented as the percentage over from the
@@ -1526,6 +1554,7 @@
<dimen name="dream_overlay_complication_margin">0dp</dimen>
<dimen name="dream_overlay_y_offset">80dp</dimen>
+ <dimen name="dream_overlay_exit_y_offset">40dp</dimen>
<dimen name="dream_aqi_badge_corner_radius">28dp</dimen>
<dimen name="dream_aqi_badge_padding_vertical">6dp</dimen>
@@ -1578,4 +1607,10 @@
<dimen name="config_rounded_mask_size">0px</dimen>
<dimen name="config_rounded_mask_size_top">0px</dimen>
<dimen name="config_rounded_mask_size_bottom">0px</dimen>
+
+ <!-- Rear Display Education dimens -->
+ <dimen name="rear_display_animation_width">273dp</dimen>
+ <dimen name="rear_display_animation_height">200dp</dimen>
+ <dimen name="rear_display_title_top_padding">24dp</dimen>
+ <dimen name="rear_display_title_bottom_padding">16dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index eead934..b39f49f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -200,6 +200,8 @@
<!-- Informs the user that a screenshot is being saved. [CHAR LIMIT=50] -->
<string name="screenshot_saving_title">Saving screenshot\u2026</string>
+ <!-- Informs the user that a screenshot is being saved. [CHAR LIMIT=50] -->
+ <string name="screenshot_saving_work_profile_title">Saving screenshot to work profile\u2026</string>
<!-- Notification title displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=50] -->
<string name="screenshot_saved_title">Screenshot saved</string>
<!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
@@ -316,7 +318,7 @@
<!-- Content description of the QR Code scanner for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_qr_code_scanner_button">QR Code Scanner</string>
<!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_unlock_button">Unlock</string>
+ <string name="accessibility_unlock_button">Unlocked</string>
<!-- Content description of the lock icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_lock_icon">Device locked</string>
<!-- Content description hint of the unlock button when fingerprint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -405,8 +407,8 @@
<string name="keyguard_face_failed">Can\u2019t recognize face</string>
<!-- Message shown to suggest using fingerprint sensor to authenticate after another biometric failed. [CHAR LIMIT=25] -->
<string name="keyguard_suggest_fingerprint">Use fingerprint instead</string>
- <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=65] -->
- <string name="keyguard_face_unlock_unavailable">Face unlock unavailable.</string>
+ <!-- Message shown to inform the user that face unlock is not available. [CHAR LIMIT=59] -->
+ <string name="keyguard_face_unlock_unavailable">Face Unlock unavailable</string>
<!-- Content description of the bluetooth icon when connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_bluetooth_connected">Bluetooth connected.</string>
@@ -439,11 +441,17 @@
<string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>
<!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$s</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
+ <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g></string>
<!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string>
+ <!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent, charging paused for battery protection.</string>
+
+ <!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g>, charging paused for battery protection.</string>
+
<!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
<string name="accessibility_overflow_action">See all notifications</string>
@@ -1315,6 +1323,9 @@
<!-- QR Code Scanner label, title [CHAR LIMIT=32] -->
<string name="qr_code_scanner_title">QR code scanner</string>
+ <!-- QR Code Scanner Secondary label when GMS Core is Updating -->
+ <string name="qr_code_scanner_updating_secondary_label">Updating</string>
+
<!-- Name of the work status bar icon. -->
<string name="status_bar_work">Work profile</string>
@@ -1934,6 +1945,9 @@
<!-- SysUI Tuner: Summary of no shortcut being selected [CHAR LIMIT=60] -->
<string name="lockscreen_none">None</string>
+ <!-- ClockId to use when none is set by user -->
+ <string name="lockscreen_clock_id_fallback" translatable="false">DEFAULT</string>
+
<!-- SysUI Tuner: Format string for describing launching an app [CHAR LIMIT=60] -->
<string name="tuner_launch_app">Launch <xliff:g id="app" example="Settings">%1$s</xliff:g></string>
@@ -2670,4 +2684,63 @@
<!-- Time format for the Dream Time Complication for 24-hour time format [CHAR LIMIT=NONE] -->
<string name="dream_time_complication_24_hr_time_format">kk:mm</string>
+
+ <!--
+ Template for an action that opens a specific app. [CHAR LIMIT=16]
+ -->
+ <string name="keyguard_affordance_enablement_dialog_action_template">Open <xliff:g id="appName" example="Wallet">%1$s</xliff:g></string>
+
+ <!--
+ Template for a message shown right before a list of instructions that tell the user what to do
+ in order to enable a shortcut to a specific app. [CHAR LIMIT=NONE]
+ -->
+ <string name="keyguard_affordance_enablement_dialog_message">To add the <xliff:g id="appName" example="Wallet">%1$s</xliff:g> app as a shortcut, make sure</string>
+
+ <!--
+ Requirement for the wallet app to be available for the user to use. This is shown as part of a
+ bulleted list of requirements. When all requirements are met, the app can be accessed through a
+ shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ -->
+ <string name="keyguard_affordance_enablement_dialog_wallet_instruction_1">• The app is set up</string>
+
+ <!--
+ Requirement for the wallet app to be available for the user to use. This is shown as part of a
+ bulleted list of requirements. When all requirements are met, the app can be accessed through a
+ shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ -->
+ <string name="keyguard_affordance_enablement_dialog_wallet_instruction_2">• At least one card has been added to Wallet</string>
+
+ <!--
+ Requirement for the QR code scanner functionality to be available for the user to use. This is
+ shown as part of a bulleted list of requirements. When all requirements are met, the piece of
+ functionality can be accessed through a shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ -->
+ <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction">• Install a camera app</string>
+
+ <!--
+ Requirement for the home app to be available for the user to use. This is shown as part of a
+ bulleted list of requirements. When all requirements are met, the app can be accessed through a
+ shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ -->
+ <string name="keyguard_affordance_enablement_dialog_home_instruction_1">• The app is set up</string>
+
+ <!--
+ Requirement for the home app to be available for the user to use. This is shown as part of a
+ bulleted list of requirements. When all requirements are met, the app can be accessed through a
+ shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ -->
+ <string name="keyguard_affordance_enablement_dialog_home_instruction_2">• At least one device is available</string>
+
+ <!-- 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>
+ <!-- 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>
+ <!-- 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>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ff29039..b11b6d6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -23,6 +23,12 @@
<item name="android:textColor">@color/status_bar_clock_color</item>
</style>
+ <style name="TextAppearance.StatusBar.UserChip" parent="@*android:style/TextAppearance.StatusBar.Icon">
+ <item name="android:textSize">@dimen/status_bar_user_chip_text_size</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:textColor">@color/status_bar_clock_color</item>
+ </style>
+
<style name="TextAppearance.StatusBar.Expanded" parent="@*android:style/TextAppearance.StatusBar">
<item name="android:textColor">?android:attr/textColorTertiary</item>
</style>
@@ -1095,7 +1101,7 @@
<item name="android:orientation">horizontal</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
- <item name="android:background">?android:attr/selectableItemBackground</item>
+ <item name="android:background">@drawable/internet_dialog_selected_effect</item>
</style>
<style name="InternetDialog.NetworkTitle">
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index 148e5ec..1eb621e 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -44,6 +44,16 @@
app:layout_constraintTop_toTopOf="@+id/album_art"
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+ <!-- Turbulence noise must have the same constraint as the album art. -->
+ <Constraint
+ android:id="@+id/turbulence_noise_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_collapsed"
+ app:layout_constraintStart_toStartOf="@+id/album_art"
+ app:layout_constraintEnd_toEndOf="@+id/album_art"
+ app:layout_constraintTop_toTopOf="@+id/album_art"
+ app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+
<Constraint
android:id="@+id/header_title"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml
index ac484d7..64c2ef1 100644
--- a/packages/SystemUI/res/xml/media_session_expanded.xml
+++ b/packages/SystemUI/res/xml/media_session_expanded.xml
@@ -37,6 +37,16 @@
app:layout_constraintTop_toTopOf="@+id/album_art"
app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+ <!-- Turbulence noise must have the same constraint as the album art. -->
+ <Constraint
+ android:id="@+id/turbulence_noise_view"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_session_height_expanded"
+ app:layout_constraintStart_toStartOf="@+id/album_art"
+ app:layout_constraintEnd_toEndOf="@+id/album_art"
+ app:layout_constraintTop_toTopOf="@+id/album_art"
+ app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+
<Constraint
android:id="@+id/header_title"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index af4be1a..5d3650c 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -25,7 +25,7 @@
android:id="@+id/clock">
<Layout
android:layout_width="wrap_content"
- android:layout_height="0dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toStartOf="@id/begin_guide"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@@ -42,7 +42,7 @@
<Constraint
android:id="@+id/date">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
android:layout_marginStart="8dp"
app:layout_constrainedWidth="true"
@@ -57,14 +57,16 @@
<Constraint
android:id="@+id/statusIcons">
<Layout
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toEndOf="@id/date"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="packed"
/>
</Constraint>
@@ -80,12 +82,16 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="packed"
/>
</Constraint>
<Constraint
android:id="@+id/carrier_group">
<Layout
+ app:layout_constraintWidth_min="48dp"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 8248fcd..982c422 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -22,50 +22,104 @@
>
<Constraint
+ android:id="@+id/privacy_container">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintEnd_toEndOf="@id/end_guide"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/carrier_group"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ </Constraint>
+
+ <Constraint
android:id="@+id/clock">
<Layout
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/date"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/privacy_container"
+ app:layout_constraintBottom_toBottomOf="@id/carrier_group"
app:layout_constraintEnd_toStartOf="@id/carrier_group"
app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
+ <Transform
+ android:scaleX="2.57"
+ android:scaleY="2.57"
+ />
</Constraint>
<Constraint
android:id="@+id/date">
<Layout
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/clock"
+ app:layout_constraintEnd_toStartOf="@id/space"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/carrier_group"
app:layout_constraintHorizontal_bias="0"
- />
- <Motion
- app:motionStagger="0.5"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
/>
</Constraint>
<Constraint
android:id="@+id/carrier_group">
- <CustomAttribute
- app:attributeName="alpha"
- app:customFloatValue="1"
- />
+ <Layout
+ app:layout_constraintWidth_min="48dp"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintStart_toEndOf="@id/clock"
+ app:layout_constraintTop_toBottomOf="@id/privacy_container"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ />
+ <PropertySet
+ android:alpha="1"
+ />
</Constraint>
<Constraint
- android:id="@+id/privacy_container">
+ android:id="@+id/statusIcons">
<Layout
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintStart_toEndOf="@id/space"
+ app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+ app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/batteryRemainingIcon">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="@id/date"
- />
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ />
+ </Constraint>
+
+
+ <Constraint
+ android:id="@id/space">
+ <Layout
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintStart_toEndOf="@id/date"
+ app:layout_constraintEnd_toStartOf="@id/statusIcons"
+ />
</Constraint>
</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
deleted file mode 100644
index d8a4e77..0000000
--- a/packages/SystemUI/res/xml/qs_header_new.xml
+++ /dev/null
@@ -1,124 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 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.
- -->
-
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/qs_header_constraint"
->
-
- <Constraint
- android:id="@+id/privacy_container">
- <Layout
- android:layout_width="wrap_content"
- android:layout_height="@dimen/large_screen_shade_header_min_height"
- app:layout_constraintEnd_toEndOf="@id/end_guide"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/carrier_group"
- app:layout_constraintHorizontal_bias="1"
- />
- </Constraint>
-
- <Constraint
- android:id="@+id/clock">
- <Layout
- android:layout_width="wrap_content"
- android:layout_height="@dimen/large_screen_shade_header_min_height"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/privacy_container"
- app:layout_constraintBottom_toBottomOf="@id/carrier_group"
- app:layout_constraintEnd_toStartOf="@id/carrier_group"
- app:layout_constraintHorizontal_bias="0"
- />
- <Transform
- android:scaleX="2.57"
- android:scaleY="2.57"
- />
- </Constraint>
-
- <Constraint
- android:id="@+id/date">
- <Layout
- android:layout_width="0dp"
- android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/space"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/carrier_group"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintHorizontal_chainStyle="spread_inside"
- />
- </Constraint>
-
- <Constraint
- android:id="@+id/carrier_group">
- <Layout
- app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
- android:minHeight="@dimen/large_screen_shade_header_min_height"
- app:layout_constraintWidth_min="48dp"
- android:layout_width="0dp"
- android:layout_height="0dp"
- app:layout_constraintStart_toEndOf="@id/clock"
- app:layout_constraintTop_toBottomOf="@id/privacy_container"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
- />
- <PropertySet
- android:alpha="1"
- />
- </Constraint>
-
- <Constraint
- android:id="@+id/statusIcons">
- <Layout
- android:layout_width="0dp"
- android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constrainedWidth="true"
- app:layout_constraintStart_toEndOf="@id/space"
- app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
- app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="1"
- />
- </Constraint>
-
- <Constraint
- android:id="@+id/batteryRemainingIcon">
- <Layout
- android:layout_width="wrap_content"
- android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constraintHeight_min="@dimen/new_qs_header_non_clickable_element_height"
- app:layout_constraintStart_toEndOf="@id/statusIcons"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="1"
- />
- </Constraint>
-
-
- <Constraint
- android:id="@id/space">
- <Layout
- android:layout_width="0dp"
- android:layout_height="0dp"
- app:layout_constraintStart_toEndOf="@id/date"
- app:layout_constraintEnd_toStartOf="@id/statusIcons"
- />
- </Constraint>
-</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
index 49cc483..e032bb9 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt
@@ -34,13 +34,19 @@
/**
* A rule that allows to run a screenshot diff test on a view that is hosted in another activity.
*/
-class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+class ExternalViewScreenshotTestRule(
+ emulationSpec: DeviceEmulationSpec,
+ assetPathRelativeToBuildRoot: String
+) : TestRule {
private val colorsRule = MaterialYouColorsRule()
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ SystemUIGoldenImagePathManager(
+ getEmulatedDevicePathConfig(emulationSpec),
+ assetPathRelativeToBuildRoot
+ )
)
private val delegateRule =
RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule)
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
index fafc774..72d8c5a 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
@@ -23,11 +23,11 @@
/** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */
class SystemUIGoldenImagePathManager(
pathConfig: PathConfig,
- override val assetsPathRelativeToRepo: String = "tests/screenshot/assets"
+ assetsPathRelativeToBuildRoot: String
) :
GoldenImagePathManager(
appContext = InstrumentationRegistry.getInstrumentation().context,
- assetsPathRelativeToRepo = assetsPathRelativeToRepo,
+ assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
deviceLocalPath =
InstrumentationRegistry.getInstrumentation()
.targetContext
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 0b0595f..738b37c 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -41,13 +41,17 @@
/** A rule for View screenshot diff unit tests. */
class ViewScreenshotTestRule(
emulationSpec: DeviceEmulationSpec,
- private val matcher: BitmapMatcher = UnitTestBitmapMatcher
+ private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
+ assetsPathRelativeToBuildRoot: String
) : TestRule {
private val colorsRule = MaterialYouColorsRule()
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ SystemUIGoldenImagePathManager(
+ getEmulatedDevicePathConfig(emulationSpec),
+ assetsPathRelativeToBuildRoot
+ )
)
private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
private val delegateRule =
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 485a0d3..b679cfa1 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -52,23 +52,14 @@
"SystemUIUnfoldLib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
- "androidx.lifecycle_lifecycle-runtime-ktx",
- "androidx.lifecycle_lifecycle-viewmodel-ktx",
- "androidx.recyclerview_recyclerview",
- "kotlinx_coroutines_android",
- "kotlinx_coroutines",
"dagger2",
"jsr330",
],
resource_dirs: [
"res",
],
- optimize: {
- proguard_flags_files: ["proguard.flags"],
- },
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
- kotlincflags: ["-Xjvm-default=enable"],
}
java_library {
diff --git a/packages/SystemUI/shared/proguard.flags b/packages/SystemUI/shared/proguard.flags
deleted file mode 100644
index 5eda045..0000000
--- a/packages/SystemUI/shared/proguard.flags
+++ /dev/null
@@ -1,4 +0,0 @@
-# Retain signatures of TypeToken and its subclasses for gson usage in ClockRegistry
--keepattributes Signature
--keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
--keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
\ No newline at end of file
diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml
index 96a5840..f3aeaef 100644
--- a/packages/SystemUI/shared/res/values/attrs.xml
+++ b/packages/SystemUI/shared/res/values/attrs.xml
@@ -20,12 +20,6 @@
-->
<resources>
- <declare-styleable name="AnimatableClockView">
- <attr name="dozeWeight" format="integer" />
- <attr name="lockScreenWeight" format="integer" />
- <attr name="chargeAnimationDelay" format="integer" />
- </declare-styleable>
-
<declare-styleable name="DoubleShadowAttrDeclare">
<attr name="keyShadowBlur" format="dimension" />
<attr name="keyShadowOffsetX" format="dimension" />
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
index 023ef31..6bc8faa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/RegionSamplingHelper.java
@@ -117,7 +117,7 @@
@Override
public void onSampleCollected(float medianLuma) {
if (mSamplingEnabled) {
- updateMediaLuma(medianLuma);
+ updateMedianLuma(medianLuma);
}
}
};
@@ -260,7 +260,7 @@
}
}
- private void updateMediaLuma(float medianLuma) {
+ private void updateMedianLuma(float medianLuma) {
mCurrentMedianLuma = medianLuma;
// If the difference between the new luma and the current luma is larger than threshold
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
index 9ea4b57..e226d58 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginActionManager.java
@@ -38,6 +38,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.shared.plugins.VersionInfo.InvalidVersionException;
import java.util.ArrayList;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
index 131f728..2f9f5b2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginManagerImpl.java
@@ -31,6 +31,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
import java.io.FileDescriptor;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index a790d89..c9ea794 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -39,6 +39,7 @@
private boolean mIsOrientationChanged;
private SplitBounds mSplitBounds;
private int mDesiredStagePosition;
+ private boolean mTaskbarInApp;
public Matrix getMatrix() {
return mMatrix;
@@ -57,6 +58,10 @@
mDesiredStagePosition = desiredStagePosition;
}
+ public void setTaskbarInApp(boolean taskbarInApp) {
+ mTaskbarInApp = taskbarInApp;
+ }
+
/**
* Updates the matrix based on the provided parameters
*/
@@ -71,14 +76,31 @@
int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
RectF thumbnailClipHint = new RectF();
- float scaledTaskbarSize = 0;
+ float scaledTaskbarSize;
+ float canvasScreenRatio;
if (mSplitBounds != null) {
float fullscreenTaskWidth;
float fullscreenTaskHeight;
- float canvasScreenRatio;
float taskPercent;
- if (!mSplitBounds.appsStackedVertically) {
+ if (mSplitBounds.appsStackedVertically) {
+ taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
+ ? mSplitBounds.topTaskPercent
+ : (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent));
+ // Scale portrait height to that of (actual screen - taskbar inset)
+ fullscreenTaskHeight = (screenHeightPx) * taskPercent;
+ if (mTaskbarInApp) {
+ canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
+ } else {
+ if (mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+ // Top app isn't cropped at all by taskbar
+ canvasScreenRatio = 0;
+ } else {
+ // Same as fullscreen ratio
+ canvasScreenRatio = (float) canvasWidth / screenWidthPx;
+ }
+ }
+ } else {
// For landscape, scale the width
taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
? mSplitBounds.leftTaskPercent
@@ -86,17 +108,12 @@
// Scale landscape width to that of actual screen
fullscreenTaskWidth = screenWidthPx * taskPercent;
canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
- } else {
- taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
- ? mSplitBounds.leftTaskPercent
- : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
- // Scale landscape width to that of actual screen
- fullscreenTaskHeight = screenHeightPx * taskPercent;
- canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
}
- scaledTaskbarSize = taskbarSize * canvasScreenRatio;
- thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
+ } else {
+ canvasScreenRatio = (float) canvasWidth / screenWidthPx;
}
+ scaledTaskbarSize = taskbarSize * canvasScreenRatio;
+ thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
float scale = thumbnailData.scale;
final float thumbnailScale;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
index 3748eba..19d0a3d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
@@ -71,7 +71,7 @@
mKeyShadowInfo.offsetY,
mKeyShadowInfo.alpha
)
- val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DARKEN)
+ val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DST_ATOP)
renderNode.setRenderEffect(blend)
return renderNode
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 93c8073..1b0dacc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -166,15 +166,14 @@
counterLauncher.cleanUp(finishTransaction);
counterWallpaper.cleanUp(finishTransaction);
// Release surface references now. This is apparently to free GPU memory
- // while doing quick operations (eg. during CTS).
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- info.getChanges().get(i).getLeash().release();
- }
+ // before GC would.
+ info.releaseAllSurfaces();
// Don't release here since launcher might still be using them. Instead
// let launcher release them (eg. via RemoteAnimationTargets)
leashMap.clear();
try {
finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+ finishTransaction.close();
} catch (RemoteException e) {
Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+ " finished callback", e);
@@ -203,10 +202,13 @@
synchronized (mFinishRunnables) {
finishRunnable = mFinishRunnables.remove(mergeTarget);
}
+ // Since we're not actually animating, release native memory now
+ t.close();
+ info.releaseAllSurfaces();
if (finishRunnable == null) return;
onAnimationCancelled(false /* isKeyguardOccluded */);
finishRunnable.run();
}
};
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index d4d3d25..b7e2494 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -126,15 +126,18 @@
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishedCallback) {
- if (!mergeTarget.equals(mToken)) return;
- if (!mRecentsSession.merge(info, t, recents)) return;
- try {
- finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
- } catch (RemoteException e) {
- Log.e(TAG, "Error merging transition.", e);
+ if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t, recents)) {
+ try {
+ finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error merging transition.", e);
+ }
+ // commit taskAppeared after merge transition finished.
+ mRecentsSession.commitTasksAppearedIfNeeded(recents);
+ } else {
+ t.close();
+ info.releaseAllSurfaces();
}
- // commit taskAppeared after merge transition finished.
- mRecentsSession.commitTasksAppearedIfNeeded(recents);
}
};
return new RemoteTransition(remote, appThread);
@@ -248,6 +251,8 @@
}
// In this case, we are "returning" to an already running app, so just consume
// the merge and do nothing.
+ info.releaseAllSurfaces();
+ t.close();
return true;
}
final int layer = mInfo.getChanges().size() * 3;
@@ -264,6 +269,8 @@
t.setLayer(targets[i].leash, layer);
}
t.apply();
+ // not using the incoming anim-only surfaces
+ info.releaseAnimSurfaces();
mAppearedTargets = targets;
return true;
}
@@ -380,9 +387,7 @@
}
// Only release the non-local created surface references. The animator is responsible
// for releasing the leashes created by local.
- for (int i = 0; i < mInfo.getChanges().size(); ++i) {
- mInfo.getChanges().get(i).getLeash().release();
- }
+ mInfo.releaseAllSurfaces();
// Reset all members.
mWrapped = null;
mFinishCB = null;
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 74519c2..05372fe 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -22,7 +22,11 @@
private val flagMap = mutableMapOf<String, Flag<*>>()
val knownFlags: Map<String, Flag<*>>
- get() = flagMap
+ get() {
+ // We need to access Flags in order to initialize our map.
+ assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ return flagMap
+ }
fun unreleasedFlag(
id: Int,
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 7b216017..8323d09 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -34,6 +34,9 @@
@Binds
abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
+ @Binds
+ abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter
+
@Module
companion object {
@JvmStatic
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 89c0786..27c5699 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -22,7 +22,11 @@
private val flagMap = mutableMapOf<String, Flag<*>>()
val knownFlags: Map<String, Flag<*>>
- get() = flagMap
+ get() {
+ // We need to access Flags in order to initialize our map.
+ assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ return flagMap
+ }
fun unreleasedFlag(
id: Int,
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index aef8876..87beff7 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -27,4 +27,7 @@
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
+
+ @Binds
+ abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
}
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 450784e..f59bf8e 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -69,10 +69,16 @@
super.reloadColor()
}
- override fun setMessage(msg: CharSequence?) {
+ override fun setMessage(msg: CharSequence?, animate: Boolean) {
if ((msg == textAboutToShow && msg != null) || msg == text) {
return
}
+
+ if (!animate) {
+ super.setMessage(msg, animate)
+ return
+ }
+
textAboutToShow = msg
if (animatorSet.isRunning) {
@@ -89,7 +95,7 @@
hideAnimator.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
- super@BouncerKeyguardMessageArea.setMessage(msg)
+ super@BouncerKeyguardMessageArea.setMessage(msg, animate)
}
}
)
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index c9b8712..87e9d56 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -26,6 +26,7 @@
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -43,6 +44,11 @@
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
+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
@@ -50,11 +56,6 @@
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
-import java.io.PrintWriter
-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
@@ -84,6 +85,7 @@
value.initialize(resources, dozeAmount, 0f)
updateRegionSamplers(value)
+ updateFontSizes()
}
}
@@ -150,7 +152,7 @@
mainExecutor,
bgExecutor,
regionSamplingEnabled,
- updateFun = { updateColors() } )
+ updateColors)
}
var smallRegionSampler: RegionSampler? = null
@@ -166,7 +168,7 @@
}
override fun onDensityOrFontScaleChanged() {
- clock?.events?.onFontSettingChanged()
+ updateFontSizes()
}
}
@@ -251,6 +253,13 @@
largeRegionSampler?.stopRegionSampler()
}
+ 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())
+ }
+
/**
* Dump information for debugging
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
index 458d22e..a25b281 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButton.java
@@ -25,7 +25,6 @@
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.Utils;
/**
* This class implements a smart emergency button that updates itself based
@@ -91,17 +90,6 @@
return super.onTouchEvent(event);
}
- /**
- * Reload colors from resources.
- **/
- public void reloadColors() {
- int color = Utils.getColorAttrDefaultColor(getContext(),
- com.android.internal.R.attr.textColorOnAccent);
- setTextColor(color);
- setBackground(getContext()
- .getDrawable(com.android.systemui.R.drawable.kg_emergency_button_background));
- }
-
@Override
public boolean performLongClick() {
return super.performLongClick();
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index c5190e8..ea808eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -135,7 +135,7 @@
mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
}
mActivityTaskManager.stopSystemLockTaskMode();
- mShadeController.collapsePanel(false);
+ mShadeController.collapseShade(false);
if (mTelecomManager != null && mTelecomManager.isInCall()) {
mTelecomManager.showInCallScreen(false);
if (mEmergencyButtonCallback != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 92ba619..860c8e3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -20,7 +20,6 @@
import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT;
-import android.annotation.CallSuper;
import android.content.res.ColorStateList;
import android.os.AsyncTask;
import android.os.CountDownTimer;
@@ -117,13 +116,6 @@
}
}
- @CallSuper
- @Override
- public void reloadColors() {
- super.reloadColors();
- mMessageAreaController.reloadColors();
- }
-
@Override
public boolean needsInput() {
return false;
@@ -159,10 +151,12 @@
int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
Map<String, Object> arguments = new HashMap<>();
arguments.put("count", secondsRemaining);
- mMessageAreaController.setMessage(PluralsMessageFormatter.format(
- mView.getResources(),
- arguments,
- R.string.kg_too_many_failed_attempts_countdown));
+ mMessageAreaController.setMessage(
+ PluralsMessageFormatter.format(
+ mView.getResources(),
+ arguments,
+ R.string.kg_too_many_failed_attempts_countdown),
+ /* animate= */ false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 8ebad6c..40423cd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,6 +5,7 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -22,6 +23,7 @@
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
@@ -46,6 +48,7 @@
*/
private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
+ private ClockController mClock;
private View mStatusArea;
private int mSmartspaceTopOffset;
@@ -95,6 +98,8 @@
}
void setClock(ClockController clock, int statusBarState) {
+ mClock = clock;
+
// Disconnect from existing plugin.
mSmallClockFrame.removeAllViews();
mLargeClockFrame.removeAllViews();
@@ -108,6 +113,35 @@
Log.i(TAG, "Attached new clock views to switch");
mSmallClockFrame.addView(clock.getSmallClock().getView());
mLargeClockFrame.addView(clock.getLargeClock().getView());
+ updateClockTargetRegions();
+ }
+
+ void updateClockTargetRegions() {
+ if (mClock != null) {
+ if (mSmallClockFrame.isLaidOut()) {
+ int targetHeight = getResources()
+ .getDimensionPixelSize(R.dimen.small_clock_text_size);
+ mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect(
+ mSmallClockFrame.getLeft(),
+ mSmallClockFrame.getTop(),
+ mSmallClockFrame.getRight(),
+ mSmallClockFrame.getTop() + targetHeight));
+ }
+
+ if (mLargeClockFrame.isLaidOut()) {
+ int largeClockTopMargin = getResources()
+ .getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
+ int targetHeight = getResources()
+ .getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
+ int top = mLargeClockFrame.getHeight() / 2 - targetHeight / 2
+ + largeClockTopMargin / 2;
+ mClock.getLargeClock().getEvents().onTargetRegionChanged(new Rect(
+ mLargeClockFrame.getLeft(),
+ top,
+ mLargeClockFrame.getRight(),
+ top + targetHeight));
+ }
+ }
}
private void updateClockViews(boolean useLargeClock, boolean animate) {
@@ -214,6 +248,10 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
+ if (changed) {
+ post(() -> updateClockTargetRegions());
+ }
+
if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index d3cc7ed..789f621 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -77,7 +77,7 @@
@KeyguardClockSwitch.ClockSize
private int mCurrentClockSize = SMALL;
- private int mKeyguardClockTopMargin = 0;
+ private int mKeyguardSmallClockTopMargin = 0;
private final ClockRegistry.ClockChangeListener mClockChangedListener;
private ViewGroup mStatusArea;
@@ -162,7 +162,7 @@
mClockRegistry.registerClockChangeListener(mClockChangedListener);
setClock(mClockRegistry.createCurrentClock());
mClockEventController.registerListeners(mView);
- mKeyguardClockTopMargin =
+ mKeyguardSmallClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
@@ -244,10 +244,12 @@
*/
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
- mKeyguardClockTopMargin =
+ mKeyguardSmallClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
+ mView.updateClockTargetRegions();
}
+
/**
* Set which clock should be displayed on the keyguard. The other one will be automatically
* hidden.
@@ -327,7 +329,7 @@
return frameHeight / 2 + clockHeight / 2;
} else {
int clockHeight = clock.getSmallClock().getView().getHeight();
- return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ return clockHeight + statusBarHeaderHeight + mKeyguardSmallClockTopMargin;
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
index b2658c9..a5b62b6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardConstants.java
@@ -29,5 +29,4 @@
*/
public static final boolean DEBUG = Log.isLoggable("Keyguard", Log.DEBUG);
public static final boolean DEBUG_SIM_STATES = true;
- public static final boolean DEBUG_BIOMETRIC_WAKELOCK = true;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index db64f05..2b660de 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -21,7 +21,6 @@
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.SystemClock;
-import android.service.trust.TrustAgentService;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.MathUtils;
@@ -68,30 +67,24 @@
private final KeyguardUpdateMonitorCallback mUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onTrustGrantedWithFlags(int flags, int userId) {
- if (userId != KeyguardUpdateMonitor.getCurrentUser()) return;
- boolean bouncerVisible = mView.isVisibleToUser();
- boolean temporaryAndRenewable =
- (flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)
- != 0;
- boolean initiatedByUser =
- (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
- boolean dismissKeyguard =
- (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
-
- if (initiatedByUser || dismissKeyguard) {
- if ((mViewMediatorCallback.isScreenOn() || temporaryAndRenewable)
- && (bouncerVisible || dismissKeyguard)) {
- if (!bouncerVisible) {
- // The trust agent dismissed the keyguard without the user proving
- // that they are present (by swiping up to show the bouncer). That's
- // fine if the user proved presence via some other way to the trust
- //agent.
- Log.i(TAG, "TrustAgent dismissed Keyguard.");
- }
- mSecurityCallback.dismiss(false /* authenticated */, userId,
- /* bypassSecondaryLockScreen */ false, SecurityMode.Invalid);
- } else {
+ public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+ TrustGrantFlags flags, String message) {
+ if (dismissKeyguard) {
+ if (!mView.isVisibleToUser()) {
+ // The trust agent dismissed the keyguard without the user proving
+ // that they are present (by swiping up to show the bouncer). That's
+ // fine if the user proved presence via some other way to the trust
+ // agent.
+ Log.i(TAG, "TrustAgent dismissed Keyguard.");
+ }
+ mSecurityCallback.dismiss(
+ false /* authenticated */,
+ KeyguardUpdateMonitor.getCurrentUser(),
+ /* bypassSecondaryLockScreen */ false,
+ SecurityMode.Invalid
+ );
+ } else {
+ if (flags.isInitiatedByUser() || flags.dismissKeyguardRequested()) {
mViewMediatorCallback.playTrustedSound();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index faaba63..2e9ad58 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -142,16 +141,6 @@
public void showMessage(CharSequence message, ColorStateList colorState) {
}
- /**
- * Reload colors from resources.
- **/
- @CallSuper
- public void reloadColors() {
- if (mEmergencyButton != null) {
- mEmergencyButton.reloadColors();
- }
- }
-
public void startAppearAnimation() {
if (TextUtils.isEmpty(mMessageAreaController.getMessage())) {
mMessageAreaController.setMessage(getInitialMessageResId());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index a0206f1..e6283b8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -26,7 +26,6 @@
val credentialAttempted: Boolean,
val deviceInteractive: Boolean,
val dreaming: Boolean,
- val encryptedOrLockdown: Boolean,
val fingerprintDisabled: Boolean,
val fingerprintLockedOut: Boolean,
val goingToSleep: Boolean,
@@ -37,6 +36,7 @@
val primaryUser: Boolean,
val shouldListenSfpsState: Boolean,
val shouldListenForFingerprintAssistant: Boolean,
+ val strongerAuthRequired: Boolean,
val switchingUser: Boolean,
val udfps: Boolean,
val userDoesNotHaveTrust: Boolean
@@ -50,10 +50,9 @@
override val listening: Boolean,
// keep sorted
val authInterruptActive: Boolean,
- val becauseCannotSkipBouncer: Boolean,
val biometricSettingEnabledForUser: Boolean,
val bouncerFullyShown: Boolean,
- val faceAuthenticated: Boolean,
+ val faceAndFpNotAuthenticated: Boolean,
val faceDisabled: Boolean,
val faceLockedOut: Boolean,
val fpLockedOut: Boolean,
@@ -67,7 +66,9 @@
val secureCameraLaunched: Boolean,
val switchingUser: Boolean,
val udfpsBouncerShowing: Boolean,
-) : KeyguardListenModel()
+ val udfpsFingerDown: Boolean,
+ val userNotTrustedOrDetectionIsNeeded: Boolean,
+ ) : KeyguardListenModel()
/**
* Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock].
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index c79fc2c..0e5f8c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -59,6 +59,7 @@
@Nullable
private ViewGroup mContainer;
private int mTopMargin;
+ protected boolean mAnimate;
public KeyguardMessageArea(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -106,7 +107,7 @@
}
@Override
- public void setMessage(CharSequence msg) {
+ public void setMessage(CharSequence msg, boolean animate) {
if (!TextUtils.isEmpty(msg)) {
securityMessageChanged(msg);
} else {
@@ -115,21 +116,12 @@
}
@Override
- public void setMessage(int resId) {
- CharSequence message = null;
- if (resId != 0) {
- message = getContext().getResources().getText(resId);
- }
- setMessage(message);
- }
-
- @Override
public void formatMessage(int resId, Object... formatArgs) {
CharSequence message = null;
if (resId != 0) {
message = getContext().getString(resId, formatArgs);
}
- setMessage(message);
+ setMessage(message, true);
}
private void securityMessageChanged(CharSequence message) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index db986e0..6a92162 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -92,11 +92,19 @@
}
public void setMessage(CharSequence s) {
- mView.setMessage(s);
+ setMessage(s, true);
+ }
+
+ /**
+ * Sets a message to the underlying text view.
+ */
+ public void setMessage(CharSequence s, boolean animate) {
+ mView.setMessage(s, animate);
}
public void setMessage(int resId) {
- mView.setMessage(resId);
+ String message = resId != 0 ? mView.getResources().getString(resId) : null;
+ setMessage(message);
}
public void setNextMessageColor(ColorStateList colorState) {
@@ -108,13 +116,6 @@
return mView.getText();
}
- /**
- * Reload colors from resources.
- **/
- public void reloadColors() {
- mView.reloadColor();
- }
-
/** Factory for creating {@link com.android.keyguard.KeyguardMessageAreaController}. */
public static class Factory {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 0025986..195e8f9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.os.UserHandle;
import android.text.Editable;
@@ -39,7 +38,6 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
@@ -95,18 +93,6 @@
}
};
- @Override
- public void reloadColors() {
- super.reloadColors();
- int textColor = Utils.getColorAttr(mView.getContext(),
- android.R.attr.textColorPrimary).getDefaultColor();
- mPasswordEntry.setTextColor(textColor);
- mPasswordEntry.setHighlightColor(textColor);
- mPasswordEntry.setBackgroundTintList(ColorStateList.valueOf(textColor));
- mPasswordEntry.setForegroundTintList(ColorStateList.valueOf(textColor));
- mSwitchImeButton.setImageTintList(ColorStateList.valueOf(textColor));
- }
-
protected KeyguardPasswordViewController(KeyguardPasswordView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
SecurityMode securityMode,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 1f0bd54..571d274 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -35,7 +35,6 @@
import com.android.internal.widget.LockscreenCredential;
import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
-import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingClassifier;
import com.android.systemui.classifier.FalsingCollector;
@@ -272,16 +271,6 @@
}
@Override
- public void reloadColors() {
- super.reloadColors();
- mMessageAreaController.reloadColors();
- int textColor = Utils.getColorAttr(mLockPatternView.getContext(),
- android.R.attr.textColorSecondary).getDefaultColor();
- int errorColor = Utils.getColorError(mLockPatternView.getContext()).getDefaultColor();
- mLockPatternView.setColors(textColor, textColor, errorColor);
- }
-
- @Override
public void onPause() {
super.onPause();
@@ -372,10 +361,13 @@
Map<String, Object> arguments = new HashMap<>();
arguments.put("count", secondsRemaining);
- mMessageAreaController.setMessage(PluralsMessageFormatter.format(
- mView.getResources(),
- arguments,
- R.string.kg_too_many_failed_attempts_countdown));
+ mMessageAreaController.setMessage(
+ PluralsMessageFormatter.format(
+ mView.getResources(),
+ arguments,
+ R.string.kg_too_many_failed_attempts_countdown),
+ /* animate= */ false
+ );
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 7876f07..f51ac32 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -70,12 +70,6 @@
}
@Override
- public void reloadColors() {
- super.reloadColors();
- mView.reloadColors();
- }
-
- @Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
return mView.startDisappearAnimation(
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index ffcf42f..5c4126e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -727,6 +727,11 @@
mViewMode.reloadColors();
}
+ /** Handles density or font scale changes. */
+ void onDensityOrFontScaleChanged() {
+ mViewMode.onDensityOrFontScaleChanged();
+ }
+
/**
* Enscapsulates the differences between bouncer modes for the container.
*/
@@ -752,6 +757,9 @@
/** Refresh colors */
default void reloadColors() {};
+ /** Handles density or font scale changes. */
+ default void onDensityOrFontScaleChanged() {}
+
/** On a successful auth, optionally handle how the view disappears */
default void startDisappearAnimation(SecurityMode securityMode) {};
@@ -899,14 +907,9 @@
mFalsingA11yDelegate = falsingA11yDelegate;
if (mUserSwitcherViewGroup == null) {
- LayoutInflater.from(v.getContext()).inflate(
- R.layout.keyguard_bouncer_user_switcher,
- mView,
- true);
- mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
+ inflateUserSwitcher();
}
updateSecurityViewLocation();
- mUserSwitcher = mView.findViewById(R.id.user_switcher_header);
setupUserSwitcher();
mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback);
}
@@ -937,6 +940,12 @@
}
@Override
+ public void onDensityOrFontScaleChanged() {
+ mView.removeView(mUserSwitcherViewGroup);
+ inflateUserSwitcher();
+ }
+
+ @Override
public void onDestroy() {
mUserSwitcherController.removeUserSwitchCallback(mUserSwitchCallback);
}
@@ -1145,6 +1154,15 @@
}
}
+ private void inflateUserSwitcher() {
+ LayoutInflater.from(mView.getContext()).inflate(
+ R.layout.keyguard_bouncer_user_switcher,
+ mView,
+ true);
+ mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
+ mUserSwitcher = mView.findViewById(R.id.user_switcher_header);
+ }
+
interface UserSwitcherCallback {
void showUnlockToContinueMessage();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 7a49926..a72a484 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -251,6 +251,11 @@
public void onUiModeChanged() {
reloadColors();
}
+
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged();
+ }
};
private boolean mBouncerVisible = false;
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
@@ -358,16 +363,18 @@
final boolean sfpsEnabled = getResources().getBoolean(
R.bool.config_show_sidefps_hint_on_bouncer);
final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning();
- final boolean needsStrongAuth = mUpdateMonitor.userNeedsStrongAuth();
+ final boolean isUnlockingWithFpAllowed =
+ mUpdateMonitor.isUnlockingWithFingerprintAllowed();
- boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning && !needsStrongAuth;
+ boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning
+ && isUnlockingWithFpAllowed;
if (DEBUG) {
Log.d(TAG, "sideFpsToShow=" + toShow + ", "
+ "mBouncerVisible=" + mBouncerVisible + ", "
+ "configEnabled=" + sfpsEnabled + ", "
+ "fpsDetectionRunning=" + fpsDetectionRunning + ", "
- + "needsStrongAuth=" + needsStrongAuth);
+ + "isUnlockingWithFpAllowed=" + isUnlockingWithFpAllowed);
}
if (toShow) {
mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER);
@@ -723,10 +730,22 @@
}
private void reloadColors() {
- mSecurityViewFlipperController.reloadColors();
+ resetViewFlipper();
mView.reloadColors();
}
+ /** Handles density or font scale changes. */
+ private void onDensityOrFontScaleChanged() {
+ resetViewFlipper();
+ mView.onDensityOrFontScaleChanged();
+ }
+
+ private void resetViewFlipper() {
+ mSecurityViewFlipperController.clearViews();
+ mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode,
+ mKeyguardSecurityCallback);
+ }
+
static class Factory {
private final KeyguardSecurityContainer mView;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index bddf4b0..a5c8c78 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -74,15 +74,13 @@
}
}
- /**
- * Reload colors of ui elements upon theme change.
- */
- public void reloadColors() {
- for (KeyguardInputViewController<KeyguardInputView> child : mChildren) {
- child.reloadColors();
- }
+ /** Handles density or font scale changes. */
+ public void clearViews() {
+ mView.removeAllViews();
+ mChildren.clear();
}
+
@VisibleForTesting
KeyguardInputViewController<KeyguardInputView> getSecurityView(SecurityMode securityMode,
KeyguardSecurityCallback keyguardSecurityCallback) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 76f7d78..db44473 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -133,12 +133,6 @@
}
@Override
- public void reloadColors() {
- super.reloadColors();
- mView.reloadColors();
- }
-
- @Override
protected void verifyPasswordAndUnlock() {
String entry = mPasswordEntry.getText();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 5995e85..e9405eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -121,12 +121,6 @@
}
@Override
- public void reloadColors() {
- super.reloadColors();
- mView.reloadColors();
- }
-
- @Override
protected void verifyPasswordAndUnlock() {
mStateMachine.next();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 83e23bd..8b9823b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import android.content.Context;
+import android.os.Trace;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -112,4 +113,11 @@
mKeyguardSlice.dump(pw, args);
}
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("KeyguardStatusView#onMeasure");
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ Trace.endSection();
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 47ea878..c11a5c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -27,6 +27,8 @@
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED;
import static android.hardware.biometrics.BiometricConstants.LockoutMode;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
+import static android.hardware.biometrics.BiometricSourceType.FACE;
+import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
@@ -145,6 +147,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.SessionTracker;
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;
@@ -228,7 +231,15 @@
* Biometric authentication: Cancelling and waiting for the relevant biometric service to
* send us the confirmation that cancellation has happened.
*/
- private static final int BIOMETRIC_STATE_CANCELLING = 2;
+ @VisibleForTesting
+ protected static final int BIOMETRIC_STATE_CANCELLING = 2;
+
+ /**
+ * Biometric state: During cancelling we got another request to start listening, so when we
+ * receive the cancellation done signal, we should start listening again.
+ */
+ @VisibleForTesting
+ protected static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
/**
* Action indicating keyguard *can* start biometric authentiation.
@@ -243,12 +254,6 @@
*/
private static final int BIOMETRIC_ACTION_UPDATE = 2;
- /**
- * Biometric state: During cancelling we got another request to start listening, so when we
- * receive the cancellation done signal, we should start listening again.
- */
- private static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3;
-
@VisibleForTesting
public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
@@ -279,6 +284,7 @@
}
private final Context mContext;
+ private final UserTracker mUserTracker;
private final KeyguardUpdateMonitorLogger mLogger;
private final boolean mIsPrimaryUser;
private final AuthController mAuthController;
@@ -370,7 +376,8 @@
private KeyguardBypassController mKeyguardBypassController;
private List<SubscriptionInfo> mSubscriptionInfo;
- private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ @VisibleForTesting
+ protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
private boolean mIsDreaming;
private boolean mLogoutEnabled;
@@ -486,21 +493,12 @@
FACE_AUTH_TRIGGERED_TRUST_DISABLED);
}
- mLogger.logTrustChanged(wasTrusted, enabled, userId);
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onTrustChanged(userId);
- if (enabled && flags != 0) {
- cb.onTrustGrantedWithFlags(flags, userId);
- }
- }
- }
-
- if (KeyguardUpdateMonitor.getCurrentUser() == userId) {
- CharSequence message = null;
- final boolean userHasTrust = getUserHasTrust(userId);
- if (userHasTrust && trustGrantedMessages != null) {
+ if (enabled) {
+ String message = null;
+ if (KeyguardUpdateMonitor.getCurrentUser() == userId
+ && trustGrantedMessages != null) {
+ // Show the first non-empty string provided by a trust agent OR intentionally pass
+ // an empty string through (to prevent the default trust agent string from showing)
for (String msg : trustGrantedMessages) {
message = msg;
if (!TextUtils.isEmpty(message)) {
@@ -509,17 +507,38 @@
}
}
- if (message != null) {
- mLogger.logShowTrustGrantedMessage(message.toString());
- }
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.showTrustGrantedMessage(message);
+ mLogger.logTrustGrantedWithFlags(flags, userId, message);
+ if (userId == getCurrentUser()) {
+ final TrustGrantFlags trustGrantFlags = new TrustGrantFlags(flags);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onTrustGrantedForCurrentUser(
+ shouldDismissKeyguardOnTrustGrantedWithCurrentUser(trustGrantFlags),
+ trustGrantFlags, message);
+ }
}
}
}
+ mLogger.logTrustChanged(wasTrusted, enabled, userId);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onTrustChanged(userId);
+ }
+ }
+ }
+
+ /**
+ * Whether the trust granted call with its passed flags should dismiss keyguard.
+ * It's assumed that the trust was granted for the current user.
+ */
+ private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) {
+ final boolean isBouncerShowing = mPrimaryBouncerIsOrWillBeShowing || mUdfpsBouncerShowing;
+ return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested())
+ && (mDeviceInteractive || flags.temporaryAndRenewable())
+ && (isBouncerShowing || flags.dismissKeyguardRequested());
}
@Override
@@ -732,8 +751,8 @@
*/
public void requestFaceAuthOnOccludingApp(boolean request) {
mOccludingAppRequestingFace = request;
- updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED);
+ int action = mOccludingAppRequestingFace ? BIOMETRIC_ACTION_UPDATE : BIOMETRIC_ACTION_STOP;
+ updateFaceListeningState(action, FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED);
}
/**
@@ -792,17 +811,17 @@
new BiometricAuthenticated(true, isStrongBiometric));
// Update/refresh trust state only if user can skip bouncer
if (getUserCanSkipBouncer(userId)) {
- mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FINGERPRINT);
+ mTrustManager.unlockedByBiometricForUser(userId, FINGERPRINT);
}
// Don't send cancel if authentication succeeds
mFingerprintCancelSignal = null;
+ mLogger.logFingerprintSuccess(userId, isStrongBiometric);
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_FP_AUTHENTICATED);
- mLogger.logFingerprintSuccess(userId, isStrongBiometric);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT,
+ cb.onBiometricAuthenticated(userId, FINGERPRINT,
isStrongBiometric);
}
}
@@ -831,10 +850,11 @@
+ " triggered while waiting for cancellation, removing watchdog");
mHandler.removeCallbacks(mFpCancelNotReceived);
}
+ mLogger.d("handleFingerprintAuthFailed");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+ cb.onBiometricAuthFailed(FINGERPRINT);
}
}
if (isUdfpsSupported()) {
@@ -859,7 +879,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAcquired(BiometricSourceType.FINGERPRINT, acquireInfo);
+ cb.onBiometricAcquired(FINGERPRINT, acquireInfo);
}
}
}
@@ -872,13 +892,7 @@
mHandler.removeCallbacks(mFpCancelNotReceived);
}
try {
- final int userId;
- try {
- userId = ActivityManager.getService().getCurrentUser().id;
- } catch (RemoteException e) {
- mLogger.logException(e, "Failed to get current user id");
- return;
- }
+ final int userId = mUserTracker.getUserId();
if (userId != authUserId) {
mLogger.logFingerprintAuthForWrongUser(authUserId);
return;
@@ -899,7 +913,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FINGERPRINT);
+ cb.onBiometricHelp(msgId, helpString, FINGERPRINT);
}
}
}
@@ -951,7 +965,7 @@
if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged = !mFingerprintLockedOutPermanent;
mFingerprintLockedOutPermanent = true;
- mLogger.d("Fingerprint locked out - requiring strong auth");
+ mLogger.d("Fingerprint permanently locked out - requiring stronger auth");
mLockPatternUtils.requireStrongAuth(
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
}
@@ -960,21 +974,23 @@
|| msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged |= !mFingerprintLockedOut;
mFingerprintLockedOut = true;
+ mLogger.d("Fingerprint temporarily locked out - requiring stronger auth");
if (isUdfpsEnrolled()) {
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
stopListeningForFace(FACE_AUTH_STOPPED_FP_LOCKED_OUT);
}
+ mLogger.logFingerprintError(msgId, errString);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT);
+ cb.onBiometricError(msgId, errString, FINGERPRINT);
}
}
if (lockedOutStateChanged) {
- notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+ notifyLockedOutStateChanged(FINGERPRINT);
}
}
@@ -1002,7 +1018,7 @@
}
if (changed) {
- notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+ notifyLockedOutStateChanged(FINGERPRINT);
}
}
@@ -1025,7 +1041,7 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricRunningStateChanged(isFingerprintDetectionRunning(),
- BiometricSourceType.FINGERPRINT);
+ FINGERPRINT);
}
}
}
@@ -1038,7 +1054,7 @@
new BiometricAuthenticated(true, isStrongBiometric));
// Update/refresh trust state only if user can skip bouncer
if (getUserCanSkipBouncer(userId)) {
- mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
+ mTrustManager.unlockedByBiometricForUser(userId, FACE);
}
// Don't send cancel if authentication succeeds
mFaceCancelSignal = null;
@@ -1049,7 +1065,7 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricAuthenticated(userId,
- BiometricSourceType.FACE,
+ FACE,
isStrongBiometric);
}
}
@@ -1071,7 +1087,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAuthFailed(BiometricSourceType.FACE);
+ cb.onBiometricAuthFailed(FACE);
}
}
handleFaceHelp(BIOMETRIC_HELP_FACE_NOT_RECOGNIZED,
@@ -1084,7 +1100,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo);
+ cb.onBiometricAcquired(FACE, acquireInfo);
}
}
}
@@ -1096,13 +1112,7 @@
mLogger.d("Aborted successful auth because device is going to sleep.");
return;
}
- final int userId;
- try {
- userId = ActivityManager.getService().getCurrentUser().id;
- } catch (RemoteException e) {
- mLogger.logException(e, "Failed to get current user id");
- return;
- }
+ final int userId = mUserTracker.getUserId();
if (userId != authUserId) {
mLogger.logFaceAuthForWrongUser(authUserId);
return;
@@ -1125,7 +1135,7 @@
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
- cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FACE);
+ cb.onBiometricHelp(msgId, helpString, FACE);
}
}
}
@@ -1193,12 +1203,12 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricError(msgId, errString,
- BiometricSourceType.FACE);
+ FACE);
}
}
if (lockedOutStateChanged) {
- notifyLockedOutStateChanged(BiometricSourceType.FACE);
+ notifyLockedOutStateChanged(FACE);
}
}
@@ -1212,7 +1222,7 @@
FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET), getBiometricLockoutDelay());
if (changed) {
- notifyLockedOutStateChanged(BiometricSourceType.FACE);
+ notifyLockedOutStateChanged(FACE);
}
}
@@ -1235,7 +1245,7 @@
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onBiometricRunningStateChanged(isFaceDetectionRunning(),
- BiometricSourceType.FACE);
+ FACE);
}
}
}
@@ -1376,7 +1386,35 @@
}
public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
- return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric);
+ // StrongAuthTracker#isUnlockingWithBiometricAllowed includes
+ // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
+ // however the strong auth tracker does not include the temporary lockout
+ // mFingerprintLockedOut.
+ return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric)
+ && !mFingerprintLockedOut;
+ }
+
+ /**
+ * Whether fingerprint is allowed ot be used for unlocking based on the strongAuthTracker
+ * and temporary lockout state (tracked by FingerprintManager via error codes).
+ */
+ public boolean isUnlockingWithFingerprintAllowed() {
+ return isUnlockingWithBiometricAllowed(FINGERPRINT);
+ }
+
+ /**
+ * Whether the given biometric is allowed based on strongAuth & lockout states.
+ */
+ public boolean isUnlockingWithBiometricAllowed(
+ @NonNull BiometricSourceType biometricSourceType) {
+ switch (biometricSourceType) {
+ case FINGERPRINT:
+ return isUnlockingWithBiometricAllowed(true);
+ case FACE:
+ return isUnlockingWithBiometricAllowed(false);
+ default:
+ return false;
+ }
}
public boolean isUserInLockdown(int userId) {
@@ -1398,11 +1436,6 @@
return isEncrypted || isLockDown;
}
- public boolean userNeedsStrongAuth() {
- return mStrongAuthTracker.getStrongAuthForUser(getCurrentUser())
- != LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
- }
-
private boolean containsFlag(int haystack, int needle) {
return (haystack & needle) != 0;
}
@@ -1572,12 +1605,6 @@
}
};
- private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback
- = (sensorId, userId, isStrongBiometric) -> {
- // Trigger the fingerprint success path so the bouncer can be shown
- handleFingerprintAuthenticated(userId, isStrongBiometric);
- };
-
/**
* Propagates a pointer down event to keyguard.
*/
@@ -1952,6 +1979,7 @@
@Inject
protected KeyguardUpdateMonitor(
Context context,
+ UserTracker userTracker,
@Main Looper mainLooper,
BroadcastDispatcher broadcastDispatcher,
SecureSettings secureSettings,
@@ -1984,6 +2012,7 @@
FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) {
mContext = context;
mSubscriptionManager = subscriptionManager;
+ mUserTracker = userTracker;
mTelephonyListenerManager = telephonyListenerManager;
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged,
@@ -2215,7 +2244,7 @@
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
mIsPrimaryUser = mUserManager.isPrimaryUser();
- int user = ActivityManager.getCurrentUser();
+ int user = mUserTracker.getUserId();
mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
updateSecondaryLockscreenRequirement(user);
@@ -2645,27 +2674,25 @@
&& (!mKeyguardGoingAway || !mDeviceInteractive)
&& mIsPrimaryUser
&& biometricEnabledForUser;
-
- final boolean shouldListenBouncerState = !(mFingerprintLockedOut
- && mPrimaryBouncerIsOrWillBeShowing && mCredentialAttempted);
-
- final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
+ final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
+ final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
+ final boolean shouldListenBouncerState =
+ !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
final boolean shouldListenUdfpsState = !isUdfps
|| (!userCanSkipBouncer
- && !isEncryptedOrLockdownForUser
+ && !strongerAuthRequired
&& userDoesNotHaveTrust);
boolean shouldListenSideFpsState = true;
- if (isSfpsSupported() && isSfpsEnrolled()) {
+ if (isSideFps) {
shouldListenSideFpsState =
mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
}
boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
- && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut()
+ && shouldListenBouncerState && shouldListenUdfpsState
&& shouldListenSideFpsState;
-
maybeLogListenerModelData(
new KeyguardFingerprintListenModel(
System.currentTimeMillis(),
@@ -2677,7 +2704,6 @@
mCredentialAttempted,
mDeviceInteractive,
mIsDreaming,
- isEncryptedOrLockdownForUser,
fingerprintDisabledForUser,
mFingerprintLockedOut,
mGoingToSleep,
@@ -2688,6 +2714,7 @@
mIsPrimaryUser,
shouldListenSideFpsState,
shouldListenForFingerprintAssistant,
+ strongerAuthRequired,
mSwitchingUser,
isUdfps,
userDoesNotHaveTrust));
@@ -2715,16 +2742,13 @@
final boolean isEncryptedOrTimedOut =
containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT)
|| containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT);
-
- // TODO: always disallow when fp is already locked out?
- final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
-
+ final boolean fpLockedOut = isFingerprintLockedOut();
final boolean canBypass = mKeyguardBypassController != null
&& mKeyguardBypassController.canBypass();
// There's no reason to ask the HAL for authentication when the user can dismiss the
- // bouncer, unless we're bypassing and need to auto-dismiss the lock screen even when
- // TrustAgents or biometrics are keeping the device unlocked.
- final boolean becauseCannotSkipBouncer = !getUserCanSkipBouncer(user) || canBypass;
+ // bouncer because the user is trusted, unless we're bypassing and need to auto-dismiss
+ // the lock screen even when TrustAgents are keeping the device unlocked.
+ final boolean userNotTrustedOrDetectionIsNeeded = !getUserHasTrust(user) || canBypass;
// Scan even when encrypted or timeout to show a preemptive bouncer when bypassing.
// Lock-down mode shouldn't scan, since it is more explicit.
@@ -2741,11 +2765,12 @@
strongAuthAllowsScanning = false;
}
- // If the face has recently been authenticated do not attempt to authenticate again.
- final boolean faceAuthenticated = getIsFaceAuthenticated();
+ // If the face or fp has recently been authenticated do not attempt to authenticate again.
+ final boolean faceAndFpNotAuthenticated = !getUserUnlockedWithBiometric(user);
final boolean faceDisabledForUser = isFaceDisabled(user);
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
+ final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown();
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -2755,13 +2780,13 @@
|| mOccludingAppRequestingFace
|| awakeKeyguard
|| shouldListenForFaceAssistant
- || mAuthController.isUdfpsFingerDown()
+ || isUdfpsFingerDown
|| mUdfpsBouncerShowing)
- && !mSwitchingUser && !faceDisabledForUser && becauseCannotSkipBouncer
+ && !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded
&& !mKeyguardGoingAway && biometricEnabledForUser
&& strongAuthAllowsScanning && mIsPrimaryUser
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
- && !faceAuthenticated
+ && faceAndFpNotAuthenticated
&& !mGoingToSleep
// We only care about fp locked out state and not face because we still trigger
// face auth even when face is locked out to show the user a message that face
@@ -2775,10 +2800,9 @@
user,
shouldListen,
mAuthInterruptActive,
- becauseCannotSkipBouncer,
biometricEnabledForUser,
mPrimaryBouncerFullyShown,
- faceAuthenticated,
+ faceAndFpNotAuthenticated,
faceDisabledForUser,
isFaceLockedOut(),
fpLockedOut,
@@ -2791,7 +2815,9 @@
strongAuthAllowsScanning,
mSecureCameraLaunched,
mSwitchingUser,
- mUdfpsBouncerShowing));
+ mUdfpsBouncerShowing,
+ isUdfpsFingerDown,
+ userNotTrustedOrDetectionIsNeeded));
return shouldListen;
}
@@ -2838,15 +2864,22 @@
// Waiting for restart via handleFingerprintError().
return;
}
- mLogger.v("startListeningForFingerprint()");
if (unlockPossible) {
mFingerprintCancelSignal = new CancellationSignal();
- if (isEncryptedOrLockdown(userId)) {
- mFpm.detectFingerprint(mFingerprintCancelSignal, mFingerprintDetectionCallback,
+ if (!isUnlockingWithFingerprintAllowed()) {
+ mLogger.v("startListeningForFingerprint - detect");
+ mFpm.detectFingerprint(
+ mFingerprintCancelSignal,
+ (sensorId, user, isStrongBiometric) -> {
+ mLogger.d("fingerprint detected");
+ // Trigger the fingerprint success path so the bouncer can be shown
+ handleFingerprintAuthenticated(user, isStrongBiometric);
+ },
userId);
} else {
+ mLogger.v("startListeningForFingerprint - authenticate");
mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
mFingerprintAuthenticationCallback, null /* handler */,
FingerprintManager.SENSOR_ID_ANY, userId, 0 /* flags */);
@@ -3063,11 +3096,15 @@
}
}
+ // Immediately stop previous biometric listening states.
+ // Resetting lockout states updates the biometric listening states.
if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+ stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING);
handleFaceLockoutReset(mFaceManager.getLockoutModeForUser(
mFaceSensorProperties.get(0).sensorId, userId));
}
if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+ stopListeningForFingerprint();
handleFingerprintLockoutReset(mFpm.getLockoutModeForUser(
mFingerprintSensorProperties.get(0).sensorId, userId));
}
@@ -3474,7 +3511,7 @@
@AnyThread
public void setSwitchingUser(boolean switching) {
mSwitchingUser = switching;
- // Since this comes in on a binder thread, we need to post if first
+ // Since this comes in on a binder thread, we need to post it first
mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_USER_SWITCHING));
}
@@ -3566,8 +3603,8 @@
Assert.isMainThread();
mUserFingerprintAuthenticated.clear();
mUserFaceAuthenticated.clear();
- mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT, unlockedUser);
- mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, unlockedUser);
+ mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser);
+ mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser);
mLogger.d("clearBiometricRecognized");
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -3841,7 +3878,7 @@
pw.println(" " + subId + "=" + mServiceStates.get(subId));
}
if (mFpm != null && mFpm.isHardwareDetected()) {
- final int userId = ActivityManager.getCurrentUser();
+ final int userId = mUserTracker.getUserId();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
pw.println(" Fingerprint state (user=" + userId + ")");
@@ -3879,7 +3916,7 @@
}
}
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
- final int userId = ActivityManager.getCurrentUser();
+ final int userId = mUserTracker.getUserId();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
pw.println(" Face authentication state (user=" + userId + ")");
@@ -3894,6 +3931,8 @@
pw.println(" listening: actual=" + mFaceRunningState
+ " expected=(" + (shouldListenForFace() ? 1 : 0));
pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
+ pw.println(" isNonStrongBiometricAllowedAfterIdleTimeout="
+ + mStrongAuthTracker.isNonStrongBiometricAllowedAfterIdleTimeout(userId));
pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
pw.println(" mFaceLockedOutPermanent=" + mFaceLockedOutPermanent);
pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index c06e1dc..1d58fc9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -19,6 +19,7 @@
import android.telephony.TelephonyManager;
import android.view.WindowManagerPolicyConstants;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -174,14 +175,14 @@
public void onTrustManagedChanged(int userId) { }
/**
- * Called after trust was granted with non-zero flags.
+ * Called after trust was granted.
+ * @param dismissKeyguard whether the keyguard should be dismissed as a result of the
+ * trustGranted
+ * @param message optional message the trust agent has provided to show that should indicate
+ * why trust was granted.
*/
- public void onTrustGrantedWithFlags(int flags, int userId) { }
-
- /**
- * Called when setting the trust granted message.
- */
- public void showTrustGrantedMessage(@Nullable CharSequence message) { }
+ public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+ @NonNull TrustGrantFlags flags, @Nullable String message) { }
/**
* Called when a biometric has been acquired.
diff --git a/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java b/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java
index 777bd19..3392a1c 100644
--- a/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java
+++ b/packages/SystemUI/src/com/android/keyguard/SecurityMessageDisplay.java
@@ -23,9 +23,10 @@
/** Set text color for the next security message. */
default void setNextMessageColor(ColorStateList colorState) {}
- void setMessage(CharSequence msg);
-
- void setMessage(int resId);
+ /**
+ * Sets a message to the underlying text view.
+ */
+ void setMessage(CharSequence msg, boolean animate);
void formatMessage(int resId, Object... formatArgs);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java
new file mode 100644
index 0000000..d33732c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java
@@ -0,0 +1,98 @@
+/*
+ * 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.keyguard;
+
+import android.service.trust.TrustAgentService;
+
+import java.util.Objects;
+
+/**
+ * Translating {@link android.service.trust.TrustAgentService.GrantTrustFlags} to a more
+ * parsable object. These flags are requested by a TrustAgent.
+ */
+public class TrustGrantFlags {
+ final int mFlags;
+
+ public TrustGrantFlags(int flags) {
+ this.mFlags = flags;
+ }
+
+ /** {@link TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER} */
+ public boolean isInitiatedByUser() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
+ }
+
+ /**
+ * Trust agent is requesting to dismiss the keyguard.
+ * See {@link TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD}.
+ *
+ * This does not guarantee that the keyguard is dismissed.
+ * KeyguardUpdateMonitor makes the final determination whether the keyguard should be dismissed.
+ * {@link KeyguardUpdateMonitorCallback#onTrustGrantedForCurrentUser(
+ * boolean, TrustGrantFlags, String).
+ */
+ public boolean dismissKeyguardRequested() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
+ }
+
+ /** {@link TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE} */
+ public boolean temporaryAndRenewable() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0;
+ }
+
+ /** {@link TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE} */
+ public boolean displayMessage() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof TrustGrantFlags)) {
+ return false;
+ }
+
+ return ((TrustGrantFlags) o).mFlags == this.mFlags;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFlags);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ sb.append(mFlags);
+ sb.append("]=");
+
+ if (isInitiatedByUser()) {
+ sb.append("initiatedByUser|");
+ }
+ if (dismissKeyguardRequested()) {
+ sb.append("dismissKeyguard|");
+ }
+ if (temporaryAndRenewable()) {
+ sb.append("temporaryAndRenewable|");
+ }
+ if (displayMessage()) {
+ sb.append("displayMessage|");
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 9a0bfc1..122c521 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -29,24 +29,25 @@
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.Observer;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManager.DockEventListener;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.settings.CurrentUserObservable;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
import javax.inject.Inject;
@@ -69,7 +70,8 @@
private final ContentResolver mContentResolver;
private final SettingsWrapper mSettingsWrapper;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
- private final CurrentUserObservable mCurrentUserObservable;
+ private final UserTracker mUserTracker;
+ private final Executor mMainExecutor;
/**
* Observe settings changes to know when to switch the clock face.
@@ -80,7 +82,7 @@
public void onChange(boolean selfChange, Collection<Uri> uris,
int flags, int userId) {
if (Objects.equals(userId,
- mCurrentUserObservable.getCurrentUser().getValue())) {
+ mUserTracker.getUserId())) {
reload();
}
}
@@ -89,7 +91,13 @@
/**
* Observe user changes and react by potentially loading the custom clock for the new user.
*/
- private final Observer<Integer> mCurrentUserObserver = (newUserId) -> reload();
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ reload();
+ }
+ };
private final PluginManager mPluginManager;
@Nullable private final DockManager mDockManager;
@@ -129,22 +137,24 @@
@Inject
public ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
- @Nullable DockManager dockManager, BroadcastDispatcher broadcastDispatcher) {
+ @Nullable DockManager dockManager, UserTracker userTracker,
+ @Main Executor mainExecutor) {
this(context, layoutInflater, pluginManager, colorExtractor,
- context.getContentResolver(), new CurrentUserObservable(broadcastDispatcher),
+ context.getContentResolver(), userTracker, mainExecutor,
new SettingsWrapper(context.getContentResolver()), dockManager);
}
@VisibleForTesting
ClockManager(Context context, LayoutInflater layoutInflater,
PluginManager pluginManager, SysuiColorExtractor colorExtractor,
- ContentResolver contentResolver, CurrentUserObservable currentUserObservable,
+ ContentResolver contentResolver, UserTracker userTracker, Executor mainExecutor,
SettingsWrapper settingsWrapper, DockManager dockManager) {
mContext = context;
mPluginManager = pluginManager;
mContentResolver = contentResolver;
mSettingsWrapper = settingsWrapper;
- mCurrentUserObservable = currentUserObservable;
+ mUserTracker = userTracker;
+ mMainExecutor = mainExecutor;
mDockManager = dockManager;
mPreviewClocks = new AvailableClocks();
@@ -226,7 +236,7 @@
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
false, mContentObserver, UserHandle.USER_ALL);
- mCurrentUserObservable.getCurrentUser().observeForever(mCurrentUserObserver);
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
if (mDockManager != null) {
mDockManager.addListener(mDockEventListener);
}
@@ -235,7 +245,7 @@
private void unregister() {
mPluginManager.removePluginListener(mPreviewClocks);
mContentResolver.unregisterContentObserver(mContentObserver);
- mCurrentUserObservable.getCurrentUser().removeObserver(mCurrentUserObserver);
+ mUserTracker.removeCallback(mUserChangedCallback);
if (mDockManager != null) {
mDockManager.removeListener(mDockEventListener);
}
@@ -363,7 +373,7 @@
ClockPlugin plugin = null;
if (ClockManager.this.isDocked()) {
final String name = mSettingsWrapper.getDockedClockFace(
- mCurrentUserObservable.getCurrentUser().getValue());
+ mUserTracker.getUserId());
if (name != null) {
plugin = mClocks.get(name);
if (plugin != null) {
@@ -372,7 +382,7 @@
}
}
final String name = mSettingsWrapper.getLockScreenCustomClockFace(
- mCurrentUserObservable.getCurrentUser().getValue());
+ mUserTracker.getUserId());
if (name != null) {
plugin = mClocks.get(name);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 9767313..676979c 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -17,17 +17,20 @@
package com.android.keyguard.dagger;
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.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.shared.clocks.DefaultClockProvider;
-import com.android.systemui.shared.plugins.PluginManager;
import dagger.Module;
import dagger.Provides;
@@ -42,14 +45,16 @@
@Application Context context,
PluginManager pluginManager,
@Main Handler handler,
- DefaultClockProvider defaultClockProvider,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ @Main Resources resources,
+ LayoutInflater layoutInflater) {
return new ClockRegistry(
context,
pluginManager,
handler,
featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
UserHandle.USER_ALL,
- defaultClockProvider);
+ new DefaultClockProvider(context, layoutInflater, resources),
+ context.getString(R.string.lockscreen_clock_id_fallback));
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
index 8fc8600..a7d4455 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -21,10 +21,7 @@
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -50,10 +47,4 @@
static StatusBarUserSwitcherContainer getUserSwitcherContainer(KeyguardStatusBarView view) {
return view.findViewById(R.id.user_switcher_container);
}
-
- /** */
- @Binds
- @KeyguardStatusBarViewScope
- abstract StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
- StatusBarUserSwitcherControllerImpl controller);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index 6264ce7..2bb75aa 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -17,7 +17,7 @@
package com.android.keyguard.logging
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.dagger.BiometricMessagesLog
+import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
import javax.inject.Inject
@@ -26,7 +26,7 @@
@SysUISingleton
class FaceMessageDeferralLogger
@Inject
-constructor(@BiometricMessagesLog private val logBuffer: LogBuffer) :
+constructor(@BiometricLog private val logBuffer: LogBuffer) :
BiometricMessageDeferralLogger(logBuffer, "FaceMessageDeferralLogger")
open class BiometricMessageDeferralLogger(
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
new file mode 100644
index 0000000..bc0bd8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.BiometricLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import com.android.systemui.plugins.log.LogLevel.INFO
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "BiometricUnlockLogger"
+
+/** Helper class for logging for [com.android.systemui.statusbar.phone.BiometricUnlockController] */
+@SysUISingleton
+class BiometricUnlockLogger @Inject constructor(@BiometricLog private val logBuffer: LogBuffer) {
+ fun i(@CompileTimeConstant msg: String) = log(msg, INFO)
+ fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+ fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
+
+ fun logStartWakeAndUnlock(mode: Int) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { int1 = mode },
+ { "startWakeAndUnlock(${wakeAndUnlockModeToString(int1)})" }
+ )
+ }
+
+ fun logUdfpsAttemptThresholdMet(consecutiveFailedAttempts: Int) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { int1 = consecutiveFailedAttempts },
+ { "udfpsAttemptThresholdMet consecutiveFailedAttempts=$int1" }
+ )
+ }
+
+ fun logCalculateModeForFingerprintUnlockingAllowed(
+ deviceInteractive: Boolean,
+ keyguardShowing: Boolean,
+ deviceDreaming: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = deviceInteractive
+ bool2 = keyguardShowing
+ bool3 = deviceDreaming
+ },
+ {
+ "calculateModeForFingerprint unlockingAllowed=true" +
+ " deviceInteractive=$bool1 isKeyguardShowing=$bool2" +
+ " deviceDreaming=$bool3"
+ }
+ )
+ }
+
+ fun logCalculateModeForFingerprintUnlockingNotAllowed(
+ strongBiometric: Boolean,
+ strongAuthFlags: Int,
+ nonStrongBiometricAllowed: Boolean,
+ deviceInteractive: Boolean,
+ keyguardShowing: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = strongAuthFlags
+ bool1 = strongBiometric
+ bool2 = nonStrongBiometricAllowed
+ bool3 = deviceInteractive
+ bool4 = keyguardShowing
+ },
+ {
+ "calculateModeForFingerprint unlockingAllowed=false" +
+ " strongBiometric=$bool1 strongAuthFlags=$int1" +
+ " nonStrongBiometricAllowed=$bool2" +
+ " deviceInteractive=$bool3 isKeyguardShowing=$bool4"
+ }
+ )
+ }
+
+ fun logCalculateModeForPassiveAuthUnlockingAllowed(
+ deviceInteractive: Boolean,
+ keyguardShowing: Boolean,
+ deviceDreaming: Boolean,
+ bypass: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = deviceInteractive
+ bool2 = keyguardShowing
+ bool3 = deviceDreaming
+ bool4 = bypass
+ },
+ {
+ "calculateModeForPassiveAuth unlockingAllowed=true" +
+ " deviceInteractive=$bool1 isKeyguardShowing=$bool2" +
+ " deviceDreaming=$bool3 bypass=$bool4"
+ }
+ )
+ }
+
+ fun logCalculateModeForPassiveAuthUnlockingNotAllowed(
+ strongBiometric: Boolean,
+ strongAuthFlags: Int,
+ nonStrongBiometricAllowed: Boolean,
+ deviceInteractive: Boolean,
+ keyguardShowing: Boolean,
+ bypass: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = if (strongBiometric) 1 else 0
+ int2 = strongAuthFlags
+ bool1 = nonStrongBiometricAllowed
+ bool2 = deviceInteractive
+ bool3 = keyguardShowing
+ bool4 = bypass
+ },
+ {
+ "calculateModeForPassiveAuth unlockingAllowed=false" +
+ " strongBiometric=${int1 == 1}" +
+ " strongAuthFlags=$int2 nonStrongBiometricAllowed=$bool1" +
+ " deviceInteractive=$bool2 isKeyguardShowing=$bool3 bypass=$bool4"
+ }
+ )
+ }
+}
+
+private fun wakeAndUnlockModeToString(mode: Int): String {
+ return when (mode) {
+ MODE_NONE -> "MODE_NONE"
+ MODE_WAKE_AND_UNLOCK -> "MODE_WAKE_AND_UNLOCK"
+ MODE_WAKE_AND_UNLOCK_PULSING -> "MODE_WAKE_AND_UNLOCK_PULSING"
+ MODE_SHOW_BOUNCER -> "MODE_SHOW_BOUNCER"
+ MODE_ONLY_WAKE -> "MODE_ONLY_WAKE"
+ MODE_UNLOCK_COLLAPSING -> "MODE_UNLOCK_COLLAPSING"
+ MODE_WAKE_AND_UNLOCK_FROM_DREAM -> "MODE_WAKE_AND_UNLOCK_FROM_DREAM"
+ MODE_DISMISS_BOUNCER -> "MODE_DISMISS_BOUNCER"
+ else -> "UNKNOWN{$mode}"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 32ce537..9e58500 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -18,14 +18,11 @@
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.plugins.log.LogLevel.DEBUG
import com.android.systemui.plugins.log.LogLevel.ERROR
import com.android.systemui.plugins.log.LogLevel.INFO
import com.android.systemui.plugins.log.LogLevel.VERBOSE
import com.android.systemui.plugins.log.LogLevel.WARNING
-import com.android.systemui.plugins.log.MessageInitializer
-import com.android.systemui.plugins.log.MessagePrinter
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -37,18 +34,16 @@
* an overkill.
*/
class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) {
- fun d(@CompileTimeConstant msg: String) = log(msg, DEBUG)
+ fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
- fun e(@CompileTimeConstant msg: String) = log(msg, ERROR)
+ fun e(@CompileTimeConstant msg: String) = buffer.log(TAG, ERROR, msg)
- fun v(@CompileTimeConstant msg: String) = log(msg, VERBOSE)
+ fun v(@CompileTimeConstant msg: String) = buffer.log(TAG, VERBOSE, msg)
- fun w(@CompileTimeConstant msg: String) = log(msg, WARNING)
+ fun w(@CompileTimeConstant msg: String) = buffer.log(TAG, WARNING, msg)
- fun log(msg: String, level: LogLevel) = buffer.log(TAG, level, msg)
-
- private fun debugLog(messageInitializer: MessageInitializer, messagePrinter: MessagePrinter) {
- buffer.log(TAG, DEBUG, messageInitializer, messagePrinter)
+ fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
+ buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
}
fun v(msg: String, arg: Any) {
@@ -61,17 +56,24 @@
// TODO: remove after b/237743330 is fixed
fun logStatusBarCalculatedAlpha(alpha: Float) {
- debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
+ buffer.log(TAG, DEBUG, { double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
}
// TODO: remove after b/237743330 is fixed
fun logStatusBarExplicitAlpha(alpha: Float) {
- debugLog({ double1 = alpha.toDouble() }, { "new mExplicitAlpha value: $double1" })
+ buffer.log(
+ TAG,
+ DEBUG,
+ { double1 = alpha.toDouble() },
+ { "new mExplicitAlpha value: $double1" }
+ )
}
// TODO: remove after b/237743330 is fixed
fun logStatusBarAlphaVisibility(visibility: Int, alpha: Float, state: String) {
- debugLog(
+ buffer.log(
+ TAG,
+ DEBUG,
{
int1 = visibility
double1 = alpha.toDouble()
@@ -80,4 +82,22 @@
{ "changing visibility to $int1 with alpha $double1 in state: $str1" }
)
}
+
+ @JvmOverloads
+ fun logBiometricMessage(
+ @CompileTimeConstant context: String,
+ msgId: Int? = null,
+ msg: String? = null
+ ) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = context
+ str2 = "$msgId"
+ str3 = msg
+ },
+ { "$str1 msgId: $str2 msg: $str3" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 81b8dfe..1f6441a 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -25,6 +25,7 @@
import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.KeyguardListenModel
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TrustGrantFlags
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.log.LogLevel.DEBUG
@@ -160,6 +161,13 @@
}, {"Fingerprint auth successful: userId: $int1, isStrongBiometric: $bool1"})
}
+ fun logFingerprintError(msgId: Int, originalErrMsg: String) {
+ logBuffer.log(TAG, DEBUG, {
+ str1 = originalErrMsg
+ int1 = msgId
+ }, { "Fingerprint error received: $str1 msgId= $int1" })
+ }
+
fun logInvalidSubId(subId: Int) {
logBuffer.log(TAG, INFO,
{ int1 = subId },
@@ -368,12 +376,16 @@
}, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
}
- fun logShowTrustGrantedMessage(
+ fun logTrustGrantedWithFlags(
+ flags: Int,
+ userId: Int,
message: String?
) {
logBuffer.log(TAG, DEBUG, {
+ int1 = flags
+ int2 = userId
str1 = message
- }, { "showTrustGrantedMessage message$str1" })
+ }, { "trustGrantedWithFlags[user=$int2] flags=${TrustGrantFlags(int1)} message=$str1" })
}
fun logTrustChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index a5fdc68..ef16a3a 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -61,6 +61,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.PluginDependencyProvider;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
@@ -70,8 +71,8 @@
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -357,6 +358,7 @@
@Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
@Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy;
@Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy;
+ @Inject Lazy<UserTracker> mUserTrackerLazy;
@Inject
public Dependency() {
@@ -564,6 +566,7 @@
mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get);
mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get);
mProviders.put(DialogLaunchAnimator.class, mDialogLaunchAnimatorLazy::get);
+ mProviders.put(UserTracker.class, mUserTrackerLazy::get);
Dependency.setInstance(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 76a7cad..8ae63c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -17,24 +17,25 @@
package com.android.systemui;
import android.app.AlertDialog;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.os.UserHandle;
-import android.util.Log;
+
+import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.settings.SecureSettings;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
import dagger.assisted.Assisted;
@@ -44,31 +45,66 @@
/**
* Manages notification when a guest session is resumed.
*/
-public class GuestResumeSessionReceiver extends BroadcastReceiver {
-
- private static final String TAG = GuestResumeSessionReceiver.class.getSimpleName();
+public class GuestResumeSessionReceiver {
@VisibleForTesting
public static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in";
@VisibleForTesting
public AlertDialog mNewSessionDialog;
+ private final Executor mMainExecutor;
private final UserTracker mUserTracker;
private final SecureSettings mSecureSettings;
- private final BroadcastDispatcher mBroadcastDispatcher;
private final ResetSessionDialog.Factory mResetSessionDialogFactory;
private final GuestSessionNotification mGuestSessionNotification;
+ @VisibleForTesting
+ public final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ cancelDialog();
+
+ UserInfo currentUser = mUserTracker.getUserInfo();
+ if (!currentUser.isGuest()) {
+ return;
+ }
+
+ int guestLoginState = mSecureSettings.getIntForUser(
+ SETTING_GUEST_HAS_LOGGED_IN, 0, newUser);
+
+ if (guestLoginState == 0) {
+ // set 1 to indicate, 1st login
+ guestLoginState = 1;
+ mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState,
+ newUser);
+ } else if (guestLoginState == 1) {
+ // set 2 to indicate, 2nd or later login
+ guestLoginState = 2;
+ mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState,
+ newUser);
+ }
+
+ mGuestSessionNotification.createPersistentNotification(currentUser,
+ (guestLoginState <= 1));
+
+ if (guestLoginState > 1) {
+ mNewSessionDialog = mResetSessionDialogFactory.create(newUser);
+ mNewSessionDialog.show();
+ }
+ }
+ };
+
@Inject
public GuestResumeSessionReceiver(
+ @Main Executor mainExecutor,
UserTracker userTracker,
SecureSettings secureSettings,
- BroadcastDispatcher broadcastDispatcher,
GuestSessionNotification guestSessionNotification,
ResetSessionDialog.Factory resetSessionDialogFactory) {
+ mMainExecutor = mainExecutor;
mUserTracker = userTracker;
mSecureSettings = secureSettings;
- mBroadcastDispatcher = broadcastDispatcher;
mGuestSessionNotification = guestSessionNotification;
mResetSessionDialogFactory = resetSessionDialogFactory;
}
@@ -77,49 +113,7 @@
* Register this receiver with the {@link BroadcastDispatcher}
*/
public void register() {
- IntentFilter f = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(this, f, null /* handler */, UserHandle.SYSTEM);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
-
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- cancelDialog();
-
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId == UserHandle.USER_NULL) {
- Log.e(TAG, intent + " sent to " + TAG + " without EXTRA_USER_HANDLE");
- return;
- }
-
- UserInfo currentUser = mUserTracker.getUserInfo();
- if (!currentUser.isGuest()) {
- return;
- }
-
- int guestLoginState = mSecureSettings.getIntForUser(
- SETTING_GUEST_HAS_LOGGED_IN, 0, userId);
-
- if (guestLoginState == 0) {
- // set 1 to indicate, 1st login
- guestLoginState = 1;
- mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId);
- } else if (guestLoginState == 1) {
- // set 2 to indicate, 2nd or later login
- guestLoginState = 2;
- mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId);
- }
-
- mGuestSessionNotification.createPersistentNotification(currentUser,
- (guestLoginState <= 1));
-
- if (guestLoginState > 1) {
- mNewSessionDialog = mResetSessionDialogFactory.create(userId);
- mNewSessionDialog.show();
- }
- }
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
}
private void cancelDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
index 95f666c..1bb0329 100644
--- a/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/PluginInflateContainer.java
@@ -21,8 +21,8 @@
import android.view.View;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.ViewProvider;
-import com.android.systemui.shared.plugins.PluginManager;
/**
* Define an interface or abstract class as follows that includes the
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 45f9385..02a6d7b 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -26,11 +26,7 @@
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -46,7 +42,6 @@
import android.os.Handler;
import android.os.SystemProperties;
import android.os.Trace;
-import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.util.DisplayUtils;
import android.util.Log;
@@ -69,7 +64,6 @@
import com.android.internal.util.Preconditions;
import com.android.settingslib.Utils;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.decor.CutoutDecorProviderFactory;
@@ -129,7 +123,6 @@
private DisplayManager mDisplayManager;
@VisibleForTesting
protected boolean mIsRegistered;
- private final BroadcastDispatcher mBroadcastDispatcher;
private final Context mContext;
private final Executor mMainExecutor;
private final TunerService mTunerService;
@@ -303,7 +296,6 @@
public ScreenDecorations(Context context,
@Main Executor mainExecutor,
SecureSettings secureSettings,
- BroadcastDispatcher broadcastDispatcher,
TunerService tunerService,
UserTracker userTracker,
PrivacyDotViewController dotViewController,
@@ -313,7 +305,6 @@
mContext = context;
mMainExecutor = mainExecutor;
mSecureSettings = secureSettings;
- mBroadcastDispatcher = broadcastDispatcher;
mTunerService = tunerService;
mUserTracker = userTracker;
mDotViewController = dotViewController;
@@ -599,10 +590,7 @@
mColorInversionSetting.onChange(false);
updateColorInversion(mColorInversionSetting.getValue());
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(mUserSwitchIntentReceiver, filter,
- mExecutor, UserHandle.ALL);
+ mUserTracker.addCallback(mUserChangedCallback, mExecutor);
mIsRegistered = true;
} else {
mMainExecutor.execute(() -> mTunerService.removeTunable(this));
@@ -611,7 +599,7 @@
mColorInversionSetting.setListening(false);
}
- mBroadcastDispatcher.unregisterReceiver(mUserSwitchIntentReceiver);
+ mUserTracker.removeCallback(mUserChangedCallback);
mIsRegistered = false;
}
}
@@ -898,18 +886,18 @@
}
}
- private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- int newUserId = ActivityManager.getCurrentUser();
- if (DEBUG) {
- Log.d(TAG, "UserSwitched newUserId=" + newUserId);
- }
- // update color inversion setting to the new user
- mColorInversionSetting.setUserId(newUserId);
- updateColorInversion(mColorInversionSetting.getValue());
- }
- };
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ if (DEBUG) {
+ Log.d(TAG, "UserSwitched newUserId=" + newUser);
+ }
+ // update color inversion setting to the new user
+ mColorInversionSetting.setUserId(newUser);
+ updateColorInversion(mColorInversionSetting.getValue());
+ }
+ };
private void updateColorInversion(int colorsInvertedValue) {
mTintColor = colorsInvertedValue != 0 ? Color.WHITE : Color.BLACK;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 83747b4..191ac76 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -115,7 +115,8 @@
setTheme(R.style.Theme_SystemUI);
if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
- IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+ IntentFilter bootCompletedFilter = new
+ IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED);
bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
// If SF GPU context priority is set to realtime, then SysUI should run at high.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 9f1c9b4..dab73e9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -52,6 +52,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.recents.Recents;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -183,15 +184,18 @@
private final AccessibilityManager mA11yManager;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private final NotificationShadeWindowController mNotificationShadeController;
+ private final ShadeController mShadeController;
private final StatusBarWindowCallback mNotificationShadeCallback;
private boolean mDismissNotificationShadeActionRegistered;
@Inject
public SystemActions(Context context,
NotificationShadeWindowController notificationShadeController,
+ ShadeController shadeController,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Optional<Recents> recentsOptional) {
mContext = context;
+ mShadeController = shadeController;
mRecentsOptional = recentsOptional;
mReceiver = new SystemActionsBroadcastReceiver();
mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
@@ -291,8 +295,11 @@
mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
- mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
- mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
+ if (mCentralSurfacesOptionalLazy.get().isPresent()) {
+ // These two actions require the CentralSurfaces instance.
+ mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
+ mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
+ }
mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
@@ -526,9 +533,7 @@
}
private void handleAccessibilityDismissNotificationShade() {
- mCentralSurfacesOptionalLazy.get().ifPresent(
- centralSurfaces -> centralSurfaces.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_NONE, false /* force */));
+ mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
}
private void handleDpadUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index 5616a00..621b99d 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -29,13 +29,15 @@
import android.util.Log
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
import com.android.systemui.people.widget.PeopleBackupHelper
/**
* Helper for backing up elements in SystemUI
*
- * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI.
- * The helper can be used to back up any element that is stored in [Context.getFilesDir].
+ * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI. The
+ * helper can be used to back up any element that is stored in [Context.getFilesDir] or
+ * [Context.getSharedPreferences].
*
* After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0,
* indicating that restoring is finished for a given user.
@@ -47,9 +49,11 @@
internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
+ private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
+ "systemui.keyguard.quickaffordance.shared_preferences"
val controlsDataLock = Any()
const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
- private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
+ const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
}
override fun onCreate(userHandle: UserHandle, operationType: Int) {
@@ -67,17 +71,27 @@
}
val keys = PeopleBackupHelper.getFilesToBackup()
- addHelper(PEOPLE_TILES_BACKUP_KEY, PeopleBackupHelper(
- this, userHandle, keys.toTypedArray()))
+ addHelper(
+ PEOPLE_TILES_BACKUP_KEY,
+ PeopleBackupHelper(this, userHandle, keys.toTypedArray())
+ )
+ addHelper(
+ KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY,
+ KeyguardQuickAffordanceBackupHelper(
+ context = this,
+ userId = userHandle.identifier,
+ ),
+ )
}
override fun onRestoreFinished() {
super.onRestoreFinished()
- val intent = Intent(ACTION_RESTORE_FINISHED).apply {
- `package` = packageName
- putExtra(Intent.EXTRA_USER_ID, userId)
- flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
- }
+ val intent =
+ Intent(ACTION_RESTORE_FINISHED).apply {
+ `package` = packageName
+ putExtra(Intent.EXTRA_USER_ID, userId)
+ flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ }
sendBroadcastAsUser(intent, UserHandle.SYSTEM, PERMISSION_SELF)
}
@@ -90,7 +104,9 @@
* @property lock a lock to hold while backing up and restoring the files.
* @property context the context of the [BackupAgent]
* @property fileNamesAndPostProcess a map from the filenames to back up and the post processing
+ * ```
* actions to take
+ * ```
*/
private class NoOverwriteFileBackupHelper(
val lock: Any,
@@ -115,23 +131,23 @@
data: BackupDataOutput?,
newState: ParcelFileDescriptor?
) {
- synchronized(lock) {
- super.performBackup(oldState, data, newState)
- }
+ synchronized(lock) { super.performBackup(oldState, data, newState) }
}
}
}
+
private fun getPPControlsFile(context: Context): () -> Unit {
return {
val filesDir = context.filesDir
val file = Environment.buildPath(filesDir, BackupHelper.CONTROLS)
if (file.exists()) {
- val dest = Environment.buildPath(filesDir,
- AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
+ val dest =
+ Environment.buildPath(filesDir, AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
file.copyTo(dest)
val jobScheduler = context.getSystemService(JobScheduler::class.java)
jobScheduler?.schedule(
- AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context))
+ AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context)
+ )
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
new file mode 100644
index 0000000..b52ddc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.battery
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.graphics.drawable.DrawableWrapper
+import android.util.PathParser
+import com.android.settingslib.graph.ThemedBatteryDrawable
+import com.android.systemui.R
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.SHIELD_LEFT_OFFSET
+import com.android.systemui.battery.BatterySpecs.SHIELD_STROKE
+import com.android.systemui.battery.BatterySpecs.SHIELD_TOP_OFFSET
+
+/**
+ * A battery drawable that accessorizes [ThemedBatteryDrawable] with additional information if
+ * necessary.
+ *
+ * For now, it adds a shield in the bottom-right corner when [displayShield] is true.
+ */
+class AccessorizedBatteryDrawable(
+ private val context: Context,
+ frameColor: Int,
+) : DrawableWrapper(ThemedBatteryDrawable(context, frameColor)) {
+ private val mainBatteryDrawable: ThemedBatteryDrawable
+ get() = drawable as ThemedBatteryDrawable
+
+ private val shieldPath = Path()
+ private val scaledShield = Path()
+ private val scaleMatrix = Matrix()
+
+ private var shieldLeftOffsetScaled = SHIELD_LEFT_OFFSET
+ private var shieldTopOffsetScaled = SHIELD_TOP_OFFSET
+
+ private var density = context.resources.displayMetrics.density
+
+ private val dualTone =
+ context.resources.getBoolean(com.android.internal.R.bool.config_batterymeterDualTone)
+
+ private val shieldTransparentOutlinePaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+ p.color = Color.TRANSPARENT
+ p.strokeWidth = ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+ p.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
+ p.style = Paint.Style.FILL_AND_STROKE
+ }
+
+ private val shieldPaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+ p.color = Color.MAGENTA
+ p.style = Paint.Style.FILL
+ p.isDither = true
+ }
+
+ init {
+ loadPaths()
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+ updateSizes()
+ }
+
+ var displayShield: Boolean = false
+
+ private fun updateSizes() {
+ val b = bounds
+ if (b.isEmpty) {
+ return
+ }
+
+ val mainWidth = BatterySpecs.getMainBatteryWidth(b.width().toFloat(), displayShield)
+ val mainHeight = BatterySpecs.getMainBatteryHeight(b.height().toFloat(), displayShield)
+
+ drawable?.setBounds(
+ b.left,
+ b.top,
+ /* right= */ b.left + mainWidth.toInt(),
+ /* bottom= */ b.top + mainHeight.toInt()
+ )
+
+ if (displayShield) {
+ val sx = b.right / BATTERY_WIDTH_WITH_SHIELD
+ val sy = b.bottom / BATTERY_HEIGHT_WITH_SHIELD
+ scaleMatrix.setScale(sx, sy)
+ shieldPath.transform(scaleMatrix, scaledShield)
+
+ shieldLeftOffsetScaled = sx * SHIELD_LEFT_OFFSET
+ shieldTopOffsetScaled = sy * SHIELD_TOP_OFFSET
+
+ val scaledStrokeWidth =
+ (sx * SHIELD_STROKE).coerceAtLeast(
+ ThemedBatteryDrawable.PROTECTION_MIN_STROKE_WIDTH
+ )
+ shieldTransparentOutlinePaint.strokeWidth = scaledStrokeWidth
+ }
+ }
+
+ override fun getIntrinsicHeight(): Int {
+ val height =
+ if (displayShield) {
+ BATTERY_HEIGHT_WITH_SHIELD
+ } else {
+ BATTERY_HEIGHT
+ }
+ return (height * density).toInt()
+ }
+
+ override fun getIntrinsicWidth(): Int {
+ val width =
+ if (displayShield) {
+ BATTERY_WIDTH_WITH_SHIELD
+ } else {
+ BATTERY_WIDTH
+ }
+ return (width * density).toInt()
+ }
+
+ override fun draw(c: Canvas) {
+ c.saveLayer(null, null)
+ // Draw the main battery icon
+ super.draw(c)
+
+ if (displayShield) {
+ c.translate(shieldLeftOffsetScaled, shieldTopOffsetScaled)
+ // We need a transparent outline around the shield, so first draw the transparent-ness
+ // then draw the shield
+ c.drawPath(scaledShield, shieldTransparentOutlinePaint)
+ c.drawPath(scaledShield, shieldPaint)
+ }
+ c.restore()
+ }
+
+ override fun getOpacity(): Int {
+ return PixelFormat.OPAQUE
+ }
+
+ override fun setAlpha(p0: Int) {
+ // Unused internally -- see [ThemedBatteryDrawable.setAlpha].
+ }
+
+ override fun setColorFilter(colorfilter: ColorFilter?) {
+ super.setColorFilter(colorFilter)
+ shieldPaint.colorFilter = colorFilter
+ }
+
+ /** Sets whether the battery is currently charging. */
+ fun setCharging(charging: Boolean) {
+ mainBatteryDrawable.charging = charging
+ }
+
+ /** Sets the current level (out of 100) of the battery. */
+ fun setBatteryLevel(level: Int) {
+ mainBatteryDrawable.setBatteryLevel(level)
+ }
+
+ /** Sets whether power save is enabled. */
+ fun setPowerSaveEnabled(powerSaveEnabled: Boolean) {
+ mainBatteryDrawable.powerSaveEnabled = powerSaveEnabled
+ }
+
+ /** Returns whether power save is currently enabled. */
+ fun getPowerSaveEnabled(): Boolean {
+ return mainBatteryDrawable.powerSaveEnabled
+ }
+
+ /** Sets the colors to use for the icon. */
+ fun setColors(fgColor: Int, bgColor: Int, singleToneColor: Int) {
+ shieldPaint.color = if (dualTone) fgColor else singleToneColor
+ mainBatteryDrawable.setColors(fgColor, bgColor, singleToneColor)
+ }
+
+ /** Notifies this drawable that the density might have changed. */
+ fun notifyDensityChanged() {
+ density = context.resources.displayMetrics.density
+ }
+
+ private fun loadPaths() {
+ val shieldPathString = context.resources.getString(R.string.config_batterymeterShieldPath)
+ shieldPath.set(PathParser.createPathFromPathData(shieldPathString))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 6a10d4a..03d999f 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -45,7 +45,6 @@
import androidx.annotation.StyleRes;
import androidx.annotation.VisibleForTesting;
-import com.android.settingslib.graph.ThemedBatteryDrawable;
import com.android.systemui.DualToneHandler;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -68,7 +67,7 @@
public static final int MODE_OFF = 2;
public static final int MODE_ESTIMATE = 3;
- private final ThemedBatteryDrawable mDrawable;
+ private final AccessorizedBatteryDrawable mDrawable;
private final ImageView mBatteryIconView;
private TextView mBatteryPercentView;
@@ -77,7 +76,10 @@
private int mLevel;
private int mShowPercentMode = MODE_DEFAULT;
private boolean mShowPercentAvailable;
+ private String mEstimateText = null;
private boolean mCharging;
+ private boolean mIsOverheated;
+ private boolean mDisplayShieldEnabled;
// Error state where we know nothing about the current battery state
private boolean mBatteryStateUnknown;
// Lazily-loaded since this is expected to be a rare-if-ever state
@@ -106,7 +108,7 @@
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
context.getColor(R.color.meter_background_color));
mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
- mDrawable = new ThemedBatteryDrawable(context, frameColor);
+ mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
atts.recycle();
mShowPercentAvailable = context.getResources().getBoolean(
@@ -170,12 +172,14 @@
if (mode == mShowPercentMode) return;
mShowPercentMode = mode;
updateShowPercent();
+ updatePercentText();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updatePercentView();
+ mDrawable.notifyDensityChanged();
}
public void setColorsFromContext(Context context) {
@@ -203,6 +207,17 @@
mDrawable.setPowerSaveEnabled(isPowerSave);
}
+ void onIsOverheatedChanged(boolean isOverheated) {
+ boolean valueChanged = mIsOverheated != isOverheated;
+ mIsOverheated = isOverheated;
+ if (valueChanged) {
+ updateContentDescription();
+ // The battery drawable is a different size depending on whether it's currently
+ // overheated or not, so we need to re-scale the view when overheated changes.
+ scaleBatteryMeterViews();
+ }
+ }
+
private TextView loadPercentView() {
return (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.battery_percentage_view, null);
@@ -227,13 +242,17 @@
mBatteryEstimateFetcher = fetcher;
}
+ void setDisplayShieldEnabled(boolean displayShieldEnabled) {
+ mDisplayShieldEnabled = displayShieldEnabled;
+ }
+
void updatePercentText() {
if (mBatteryStateUnknown) {
- setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
return;
}
if (mBatteryEstimateFetcher == null) {
+ setPercentTextAtCurrentLevel();
return;
}
@@ -245,10 +264,9 @@
return;
}
if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
+ mEstimateText = estimate;
mBatteryPercentView.setText(estimate);
- setContentDescription(getContext().getString(
- R.string.accessibility_battery_level_with_estimate,
- mLevel, estimate));
+ updateContentDescription();
} else {
setPercentTextAtCurrentLevel();
}
@@ -257,28 +275,49 @@
setPercentTextAtCurrentLevel();
}
} else {
- setContentDescription(
- getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
- : R.string.accessibility_battery_level, mLevel));
+ updateContentDescription();
}
}
private void setPercentTextAtCurrentLevel() {
- if (mBatteryPercentView == null) {
- return;
+ if (mBatteryPercentView != null) {
+ mEstimateText = null;
+ String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
+ // Setting text actually triggers a layout pass (because the text view is set to
+ // wrap_content width and TextView always relayouts for this). Avoid needless
+ // relayout if the text didn't actually change.
+ if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
+ mBatteryPercentView.setText(percentText);
+ }
}
- String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f);
- // Setting text actually triggers a layout pass (because the text view is set to
- // wrap_content width and TextView always relayouts for this). Avoid needless
- // relayout if the text didn't actually change.
- if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) {
- mBatteryPercentView.setText(percentText);
+ updateContentDescription();
+ }
+
+ private void updateContentDescription() {
+ Context context = getContext();
+
+ String contentDescription;
+ if (mBatteryStateUnknown) {
+ contentDescription = context.getString(R.string.accessibility_battery_unknown);
+ } else if (mShowPercentMode == MODE_ESTIMATE && !TextUtils.isEmpty(mEstimateText)) {
+ contentDescription = context.getString(
+ mIsOverheated
+ ? R.string.accessibility_battery_level_charging_paused_with_estimate
+ : R.string.accessibility_battery_level_with_estimate,
+ mLevel,
+ mEstimateText);
+ } else if (mIsOverheated) {
+ contentDescription =
+ context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
+ } else if (mCharging) {
+ contentDescription =
+ context.getString(R.string.accessibility_battery_level_charging, mLevel);
+ } else {
+ contentDescription = context.getString(R.string.accessibility_battery_level, mLevel);
}
- setContentDescription(
- getContext().getString(mCharging ? R.string.accessibility_battery_level_charging
- : R.string.accessibility_battery_level, mLevel));
+ setContentDescription(contentDescription);
}
void updateShowPercent() {
@@ -329,6 +368,7 @@
}
mBatteryStateUnknown = isUnknown;
+ updateContentDescription();
if (mBatteryStateUnknown) {
mBatteryIconView.setImageDrawable(getUnknownStateDrawable());
@@ -349,15 +389,43 @@
res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
float iconScaleFactor = typedValue.getFloat();
- int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height);
- int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width);
+ float mainBatteryHeight =
+ res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height) * iconScaleFactor;
+ float mainBatteryWidth =
+ res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor;
+
+ // If the battery is marked as overheated, we should display a shield indicating that the
+ // battery is being "defended".
+ boolean displayShield = mDisplayShieldEnabled && mIsOverheated;
+ float fullBatteryIconHeight =
+ BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield);
+ float fullBatteryIconWidth =
+ BatterySpecs.getFullBatteryWidth(mainBatteryWidth, displayShield);
+
+ int marginTop;
+ if (displayShield) {
+ // If the shield is displayed, we need some extra marginTop so that the bottom of the
+ // main icon is still aligned with the bottom of all the other system icons.
+ int shieldHeightAddition = Math.round(fullBatteryIconHeight - mainBatteryHeight);
+ // However, the other system icons have some embedded bottom padding that the battery
+ // doesn't have, so we shouldn't move the battery icon down by the full amount.
+ // See b/258672854.
+ marginTop = shieldHeightAddition
+ - res.getDimensionPixelSize(R.dimen.status_bar_battery_extra_vertical_spacing);
+ } else {
+ marginTop = 0;
+ }
+
int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);
LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
- (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
- scaledLayoutParams.setMargins(0, 0, 0, marginBottom);
+ Math.round(fullBatteryIconWidth),
+ Math.round(fullBatteryIconHeight));
+ scaledLayoutParams.setMargins(0, marginTop, 0, marginBottom);
+ mDrawable.setDisplayShield(displayShield);
mBatteryIconView.setLayoutParams(scaledLayoutParams);
+ mBatteryIconView.invalidateDrawable(mDrawable);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index ae9a323..f4ec33a 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -17,19 +17,23 @@
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
-import android.app.ActivityManager;
import android.content.ContentResolver;
+import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.View;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import androidx.annotation.NonNull;
+
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -42,12 +46,13 @@
public class BatteryMeterViewController extends ViewController<BatteryMeterView> {
private final ConfigurationController mConfigurationController;
private final TunerService mTunerService;
+ private final Handler mMainHandler;
private final ContentResolver mContentResolver;
private final BatteryController mBatteryController;
private final String mSlotBattery;
private final SettingObserver mSettingObserver;
- private final CurrentUserTracker mCurrentUserTracker;
+ private final UserTracker mUserTracker;
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -84,6 +89,21 @@
public void onBatteryUnknownStateChanged(boolean isUnknown) {
mView.onBatteryUnknownStateChanged(isUnknown);
}
+
+ @Override
+ public void onIsOverheatedChanged(boolean isOverheated) {
+ mView.onIsOverheatedChanged(isOverheated);
+ }
+ };
+
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mContentResolver.unregisterContentObserver(mSettingObserver);
+ registerShowBatteryPercentObserver(newUser);
+ mView.updateShowPercent();
+ }
};
// Some places may need to show the battery conditionally, and not obey the tuner
@@ -93,30 +113,26 @@
@Inject
public BatteryMeterViewController(
BatteryMeterView view,
+ UserTracker userTracker,
ConfigurationController configurationController,
TunerService tunerService,
- BroadcastDispatcher broadcastDispatcher,
@Main Handler mainHandler,
ContentResolver contentResolver,
+ FeatureFlags featureFlags,
BatteryController batteryController) {
super(view);
+ mUserTracker = userTracker;
mConfigurationController = configurationController;
mTunerService = tunerService;
+ mMainHandler = mainHandler;
mContentResolver = contentResolver;
mBatteryController = batteryController;
mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
+ mView.setDisplayShieldEnabled(featureFlags.isEnabled(Flags.BATTERY_SHIELD_ICON));
mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
- mSettingObserver = new SettingObserver(mainHandler);
- mCurrentUserTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- contentResolver.unregisterContentObserver(mSettingObserver);
- registerShowBatteryPercentObserver(newUserId);
- mView.updateShowPercent();
- }
- };
+ mSettingObserver = new SettingObserver(mMainHandler);
}
@Override
@@ -125,9 +141,9 @@
subscribeForTunerUpdates();
mBatteryController.addCallback(mBatteryStateChangeCallback);
- registerShowBatteryPercentObserver(ActivityManager.getCurrentUser());
+ registerShowBatteryPercentObserver(mUserTracker.getUserId());
registerGlobalBatteryUpdateObserver();
- mCurrentUserTracker.startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
mView.updateShowPercent();
}
@@ -138,7 +154,7 @@
unsubscribeFromTunerUpdates();
mBatteryController.removeCallback(mBatteryStateChangeCallback);
- mCurrentUserTracker.stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
mContentResolver.unregisterContentObserver(mSettingObserver);
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
new file mode 100644
index 0000000..6455a96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySpecs.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.battery
+
+import com.android.settingslib.graph.ThemedBatteryDrawable
+
+/** An object storing specs related to the battery icon in the status bar. */
+object BatterySpecs {
+
+ /** Width of the main battery icon, not including the shield. */
+ const val BATTERY_WIDTH = ThemedBatteryDrawable.WIDTH
+ /** Height of the main battery icon, not including the shield. */
+ const val BATTERY_HEIGHT = ThemedBatteryDrawable.HEIGHT
+
+ private const val SHIELD_WIDTH = 10f
+ private const val SHIELD_HEIGHT = 13f
+
+ /**
+ * Amount that the left side of the shield should be offset from the left side of the battery.
+ */
+ const val SHIELD_LEFT_OFFSET = 8f
+ /** Amount that the top of the shield should be offset from the top of the battery. */
+ const val SHIELD_TOP_OFFSET = 10f
+
+ const val SHIELD_STROKE = 4f
+
+ /** The full width of the battery icon, including the main battery icon *and* the shield. */
+ const val BATTERY_WIDTH_WITH_SHIELD = SHIELD_LEFT_OFFSET + SHIELD_WIDTH
+ /** The full height of the battery icon, including the main battery icon *and* the shield. */
+ const val BATTERY_HEIGHT_WITH_SHIELD = SHIELD_TOP_OFFSET + SHIELD_HEIGHT
+
+ /**
+ * Given the desired height of the main battery icon in pixels, returns the height that the full
+ * battery icon will take up in pixels.
+ *
+ * If there's no shield, this will just return [mainBatteryHeight]. Otherwise, the shield
+ * extends slightly below the bottom of the main battery icon so we need some extra height.
+ */
+ @JvmStatic
+ fun getFullBatteryHeight(mainBatteryHeight: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ mainBatteryHeight
+ } else {
+ val verticalScaleFactor = mainBatteryHeight / BATTERY_HEIGHT
+ verticalScaleFactor * BATTERY_HEIGHT_WITH_SHIELD
+ }
+ }
+
+ /**
+ * Given the desired width of the main battery icon in pixels, returns the width that the full
+ * battery icon will take up in pixels.
+ *
+ * If there's no shield, this will just return [mainBatteryWidth]. Otherwise, the shield extends
+ * past the right side of the main battery icon so we need some extra width.
+ */
+ @JvmStatic
+ fun getFullBatteryWidth(mainBatteryWidth: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ mainBatteryWidth
+ } else {
+ val horizontalScaleFactor = mainBatteryWidth / BATTERY_WIDTH
+ horizontalScaleFactor * BATTERY_WIDTH_WITH_SHIELD
+ }
+ }
+
+ /**
+ * Given the height of the full battery icon, return how tall the main battery icon should be.
+ *
+ * If there's no shield, this will just return [fullBatteryHeight]. Otherwise, the shield takes
+ * up some of the view's height so the main battery width will be just a portion of
+ * [fullBatteryHeight].
+ */
+ @JvmStatic
+ fun getMainBatteryHeight(fullBatteryHeight: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ fullBatteryHeight
+ } else {
+ return (BATTERY_HEIGHT / BATTERY_HEIGHT_WITH_SHIELD) * fullBatteryHeight
+ }
+ }
+
+ /**
+ * Given the width of the full battery icon, return how wide the main battery icon should be.
+ *
+ * If there's no shield, this will just return [fullBatteryWidth]. Otherwise, the shield takes
+ * up some of the view's width so the main battery width will be just a portion of
+ * [fullBatteryWidth].
+ */
+ @JvmStatic
+ fun getMainBatteryWidth(fullBatteryWidth: Float, displayShield: Boolean): Float {
+ return if (!displayShield) {
+ fullBatteryWidth
+ } else {
+ return (BATTERY_WIDTH / BATTERY_WIDTH_WITH_SHIELD) * fullBatteryWidth
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index b2a2a67..b962cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -107,6 +107,8 @@
if (shouldAnimateForTransition(lastState, newState)) {
iconView.playAnimation()
iconViewOverlay.playAnimation()
+ } else if (lastState == STATE_IDLE && newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+ iconView.playAnimation()
}
LottieColorUtils.applyDynamicColors(context, iconView)
LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 94f7158..68e1f72 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -129,6 +129,7 @@
private final float mTranslationY;
@VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
private final Set<Integer> mFailedModalities = new HashSet<Integer>();
+ private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
private final @Background DelayableExecutor mBackgroundExecutor;
@@ -497,9 +498,9 @@
.start();
});
}
- OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
- if (dispatcher != null) {
- dispatcher.registerOnBackInvokedCallback(
+ mOnBackInvokedDispatcher = findOnBackInvokedDispatcher();
+ if (mOnBackInvokedDispatcher != null) {
+ mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback);
}
}
@@ -600,11 +601,11 @@
@Override
public void onDetachedFromWindow() {
- OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
- if (dispatcher != null) {
- findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
- }
super.onDetachedFromWindow();
+ if (mOnBackInvokedDispatcher != null) {
+ mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback);
+ mOnBackInvokedDispatcher = null;
+ }
mWakefulnessLifecycle.removeObserver(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 76cd3f4..e43c0b9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -35,6 +35,8 @@
import android.view.inputmethod.InputMethodManager;
import android.widget.ImeAwareEditText;
import android.widget.TextView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
@@ -58,6 +60,8 @@
private ViewGroup mAuthCredentialHeader;
private ViewGroup mAuthCredentialInput;
private int mBottomInset = 0;
+ private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
+ private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
public AuthCredentialPasswordView(Context context,
AttributeSet attrs) {
@@ -79,8 +83,7 @@
return false;
}
if (event.getAction() == KeyEvent.ACTION_UP) {
- mContainerView.sendEarlyUserCanceled();
- mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ onBackInvoked();
}
return true;
});
@@ -88,6 +91,11 @@
setOnApplyWindowInsetsListener(this);
}
+ private void onBackInvoked() {
+ mContainerView.sendEarlyUserCanceled();
+ mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -100,6 +108,12 @@
mPasswordField.requestFocus();
mPasswordField.scheduleShowSoftInput();
+
+ mOnBackInvokedDispatcher = findOnBackInvokedDispatcher();
+ if (mOnBackInvokedDispatcher != null) {
+ mOnBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mBackCallback);
+ }
}
@Override
@@ -137,6 +151,15 @@
}
@Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mOnBackInvokedDispatcher != null) {
+ mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback);
+ mOnBackInvokedDispatcher = null;
+ }
+ }
+
+ @Override
protected void onCredentialVerified(@NonNull VerifyCredentialResponse response,
int timeoutMs) {
super.onCredentialVerified(response, timeoutMs);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 4363b88..0c1cb92 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -116,9 +116,9 @@
notificationShadeWindowController.setForcePluginOpen(false, this)
}
- fun showUnlockRipple(biometricSourceType: BiometricSourceType?) {
+ fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
if (!keyguardStateController.isShowing ||
- keyguardUpdateMonitor.userNeedsStrongAuth()) {
+ !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(biometricSourceType)) {
return
}
@@ -246,7 +246,7 @@
object : KeyguardUpdateMonitorCallback() {
override fun onBiometricAuthenticated(
userId: Int,
- biometricSourceType: BiometricSourceType?,
+ biometricSourceType: BiometricSourceType,
isStrongBiometric: Boolean
) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
@@ -255,14 +255,14 @@
showUnlockRipple(biometricSourceType)
}
- override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
+ override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
mView.retractDwellRipple()
}
}
override fun onBiometricAcquired(
- biometricSourceType: BiometricSourceType?,
+ biometricSourceType: BiometricSourceType,
acquireInfo: Int
) {
if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index c93fe6a..4b57d45 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -29,7 +29,7 @@
import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils
import com.android.systemui.animation.Interpolators
-import com.android.systemui.ripple.RippleShader
+import com.android.systemui.surfaceeffects.ripple.RippleShader
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index ad96612..bdad413 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -37,6 +37,9 @@
private float mDialogSuggestedAlpha = 1f;
private float mNotificationShadeExpansion = 0f;
+ // Used for Udfps ellipse detection when flag is true, set by AnimationViewController
+ boolean mUseExpandedOverlay = false;
+
// mAlpha takes into consideration the status bar expansion amount and dialog suggested alpha
private int mAlpha;
boolean mPauseAuth;
@@ -118,6 +121,24 @@
}
/**
+ * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
+ *
+ * @param bounds RectF based off screen coordinates in current orientation
+ */
+ RectF getBoundsRelativeToView(RectF bounds) {
+ int[] pos = getLocationOnScreen();
+
+ RectF output = new RectF(
+ bounds.left - pos[0],
+ bounds.top - pos[1],
+ bounds.right - pos[0],
+ bounds.bottom - pos[1]
+ );
+
+ return output;
+ }
+
+ /**
* Set the suggested alpha based on whether a dialog was recently shown or hidden.
* @param dialogSuggestedAlpha value from 0f to 1f.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index bc10868..5a81bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -61,11 +61,17 @@
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.biometrics.dagger.BiometricsBackground;
+import com.android.systemui.biometrics.udfps.InteractionEvent;
+import com.android.systemui.biometrics.udfps.NormalizedTouchData;
+import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
+import com.android.systemui.biometrics.udfps.TouchProcessor;
+import com.android.systemui.biometrics.udfps.TouchProcessorResult;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
@@ -141,6 +147,7 @@
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
@NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @Nullable private final TouchProcessor mTouchProcessor;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -149,7 +156,7 @@
// TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
@Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
@Nullable private final AlternateUdfpsTouchProvider mAlternateTouchProvider;
- @Nullable private UdfpsDisplayMode mUdfpsDisplayMode;
+ @Nullable private UdfpsDisplayModeProvider mUdfpsDisplayMode;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
@Nullable private VelocityTracker mVelocityTracker;
@@ -428,8 +435,99 @@
return portraitTouch;
}
+ private void tryDismissingKeyguard() {
+ if (!mOnFingerDown) {
+ playStartHaptic();
+ }
+ mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
+ mAttemptedToDismissKeyguard = true;
+ }
+
@VisibleForTesting
boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ return newOnTouch(requestId, event, fromUdfpsView);
+ } else {
+ return oldOnTouch(requestId, event, fromUdfpsView);
+ }
+ }
+
+ private boolean newOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
+ if (!fromUdfpsView) {
+ Log.e(TAG, "ignoring the touch injected from outside of UdfpsView");
+ return false;
+ }
+ if (mOverlay == null) {
+ Log.w(TAG, "ignoring onTouch with null overlay");
+ return false;
+ }
+ if (!mOverlay.matchesRequestId(requestId)) {
+ Log.w(TAG, "ignoring stale touch event: " + requestId + " current: "
+ + mOverlay.getRequestId());
+ return false;
+ }
+
+ final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId,
+ mOverlayParams);
+ if (result instanceof TouchProcessorResult.Failure) {
+ Log.w(TAG, ((TouchProcessorResult.Failure) result).getReason());
+ return false;
+ }
+
+ final TouchProcessorResult.ProcessedTouch processedTouch =
+ (TouchProcessorResult.ProcessedTouch) result;
+ final NormalizedTouchData data = processedTouch.getTouchData();
+
+ mActivePointerId = processedTouch.getPointerOnSensorId();
+ switch (processedTouch.getEvent()) {
+ case DOWN:
+ if (shouldTryToDismissKeyguard()) {
+ tryDismissingKeyguard();
+ }
+ onFingerDown(requestId,
+ data.getPointerId(),
+ data.getX(),
+ data.getY(),
+ data.getMinor(),
+ data.getMajor(),
+ data.getOrientation(),
+ data.getTime(),
+ data.getGestureStart(),
+ mStatusBarStateController.isDozing());
+ break;
+
+ case UP:
+ case CANCEL:
+ if (InteractionEvent.CANCEL.equals(processedTouch.getEvent())) {
+ Log.w(TAG, "This is a CANCEL event that's reported as an UP event!");
+ }
+ mAttemptedToDismissKeyguard = false;
+ onFingerUp(requestId,
+ mOverlay.getOverlayView(),
+ data.getPointerId(),
+ data.getX(),
+ data.getY(),
+ data.getMinor(),
+ data.getMajor(),
+ data.getOrientation(),
+ data.getTime(),
+ data.getGestureStart(),
+ mStatusBarStateController.isDozing());
+ mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION);
+ break;
+
+
+ default:
+ break;
+ }
+
+ // We should only consume touches that are within the sensor. By returning "false" for
+ // touches outside of the sensor, we let other UI components consume these events and act on
+ // them appropriately.
+ return processedTouch.getTouchData().isWithinSensor(mOverlayParams.getNativeSensorBounds());
+ }
+
+ private boolean oldOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
if (mOverlay == null) {
Log.w(TAG, "ignoring onTouch with null overlay");
return false;
@@ -459,7 +557,7 @@
mVelocityTracker.clear();
}
- boolean withinSensorArea =
+ final boolean withinSensorArea =
isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView);
if (withinSensorArea) {
Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0);
@@ -474,11 +572,7 @@
}
if ((withinSensorArea || fromUdfpsView) && shouldTryToDismissKeyguard()) {
Log.v(TAG, "onTouch | dismiss keyguard ACTION_DOWN");
- if (!mOnFingerDown) {
- playStartHaptic();
- }
- mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
- mAttemptedToDismissKeyguard = true;
+ tryDismissingKeyguard();
}
Trace.endSection();
@@ -491,17 +585,13 @@
? event.getPointerId(0)
: event.findPointerIndex(mActivePointerId);
if (idx == event.getActionIndex()) {
- boolean actionMoveWithinSensorArea =
+ final boolean actionMoveWithinSensorArea =
isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx),
fromUdfpsView);
if ((fromUdfpsView || actionMoveWithinSensorArea)
&& shouldTryToDismissKeyguard()) {
Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE");
- if (!mOnFingerDown) {
- playStartHaptic();
- }
- mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
- mAttemptedToDismissKeyguard = true;
+ tryDismissingKeyguard();
break;
}
// Map the touch to portrait mode if the device is in landscape mode.
@@ -626,7 +716,8 @@
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
@NonNull @BiometricsBackground Executor biometricsExecutor,
- @NonNull PrimaryBouncerInteractor primaryBouncerInteractor) {
+ @NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
+ @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -667,6 +758,9 @@
mBiometricExecutor = biometricsExecutor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
+ mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
+ ? singlePointerTouchProcessor : null;
+
mDumpManager.registerDumpable(TAG, this);
mOrientationListener = new BiometricDisplayListener(
@@ -872,7 +966,36 @@
return mOnFingerDown;
}
- private void onFingerDown(long requestId, int x, int y, float minor, float major) {
+ private void onFingerDown(
+ long requestId,
+ int x,
+ int y,
+ float minor,
+ float major) {
+ onFingerDown(
+ requestId,
+ MotionEvent.INVALID_POINTER_ID /* pointerId */,
+ x,
+ y,
+ minor,
+ major,
+ 0f /* orientation */,
+ 0L /* time */,
+ 0L /* gestureStart */,
+ false /* isAod */);
+ }
+
+ private void onFingerDown(
+ long requestId,
+ int pointerId,
+ float x,
+ float y,
+ float minor,
+ float major,
+ float orientation,
+ long time,
+ long gestureStart,
+ boolean isAod) {
mExecution.assertIsMainThread();
if (mOverlay == null) {
@@ -901,7 +1024,7 @@
mOnFingerDown = true;
if (mAlternateTouchProvider != null) {
mBiometricExecutor.execute(() -> {
- mAlternateTouchProvider.onPointerDown(requestId, x, y, minor, major);
+ mAlternateTouchProvider.onPointerDown(requestId, (int) x, (int) y, minor, major);
});
mFgExecutor.execute(() -> {
if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
@@ -909,7 +1032,13 @@
}
});
} else {
- mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, x, y, minor, major);
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y,
+ minor, major, orientation, time, gestureStart, isAod);
+ } else {
+ mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, (int) x,
+ (int) y, minor, major);
+ }
}
Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
final UdfpsView view = mOverlay.getOverlayView();
@@ -933,6 +1062,32 @@
}
private void onFingerUp(long requestId, @NonNull UdfpsView view) {
+ onFingerUp(
+ requestId,
+ view,
+ MotionEvent.INVALID_POINTER_ID /* pointerId */,
+ 0f /* x */,
+ 0f /* y */,
+ 0f /* minor */,
+ 0f /* major */,
+ 0f /* orientation */,
+ 0L /* time */,
+ 0L /* gestureStart */,
+ false /* isAod */);
+ }
+
+ private void onFingerUp(
+ long requestId,
+ @NonNull UdfpsView view,
+ int pointerId,
+ float x,
+ float y,
+ float minor,
+ float major,
+ float orientation,
+ long time,
+ long gestureStart,
+ boolean isAod) {
mExecution.assertIsMainThread();
mActivePointerId = -1;
mAcquiredReceived = false;
@@ -947,7 +1102,12 @@
}
});
} else {
- mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId);
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x,
+ y, minor, major, orientation, time, gestureStart, isAod);
+ } else {
+ mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId);
+ }
}
for (Callback cb : mCallbacks) {
cb.onFingerUp();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 0bb24f8..8db4927 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -49,6 +49,7 @@
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
@@ -103,6 +104,7 @@
private set
private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
+ private var sensorBounds: Rect = Rect()
private var overlayTouchListener: TouchExplorationStateChangeListener? = null
@@ -120,6 +122,10 @@
privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
// Avoid announcing window title.
accessibilityTitle = " "
+
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+ }
}
/** A helper if the [requestReason] was due to enrollment. */
@@ -160,6 +166,7 @@
fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
if (overlayView == null) {
overlayParams = params
+ sensorBounds = Rect(params.sensorBounds)
try {
overlayView = (inflater.inflate(
R.layout.udfps_view, null, false
@@ -178,6 +185,7 @@
}
windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
+ sensorRect = sensorBounds
touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled
overlayTouchListener = TouchExplorationStateChangeListener {
if (accessibilityManager.isTouchExplorationEnabled) {
@@ -194,6 +202,7 @@
overlayTouchListener!!
)
overlayTouchListener?.onTouchExplorationStateChanged(true)
+ useExpandedOverlay = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
}
} catch (e: RuntimeException) {
Log.e(TAG, "showUdfpsOverlay | failed to add window", e)
@@ -225,13 +234,14 @@
REASON_ENROLL_ENROLLING -> {
UdfpsEnrollViewController(
view.addUdfpsView(R.layout.udfps_enroll_view) {
- updateSensorLocation(overlayParams.sensorBounds)
+ updateSensorLocation(sensorBounds)
},
enrollHelper ?: throw IllegalStateException("no enrollment helper"),
statusBarStateController,
shadeExpansionStateManager,
dialogManager,
dumpManager,
+ featureFlags,
overlayParams.scaleFactor
)
}
@@ -420,7 +430,12 @@
}
// Original sensorBounds assume portrait mode.
- val rotatedSensorBounds = Rect(overlayParams.sensorBounds)
+ var rotatedBounds =
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ Rect(overlayParams.overlayBounds)
+ } else {
+ Rect(overlayParams.sensorBounds)
+ }
val rot = overlayParams.rotation
if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
@@ -434,18 +449,27 @@
} else {
Log.v(TAG, "Rotate UDFPS bounds " + Surface.rotationToString(rot))
RotationUtils.rotateBounds(
- rotatedSensorBounds,
+ rotatedBounds,
overlayParams.naturalDisplayWidth,
overlayParams.naturalDisplayHeight,
rot
)
+
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ RotationUtils.rotateBounds(
+ sensorBounds,
+ overlayParams.naturalDisplayWidth,
+ overlayParams.naturalDisplayHeight,
+ rot
+ )
+ }
}
}
- x = rotatedSensorBounds.left - paddingX
- y = rotatedSensorBounds.top - paddingY
- height = rotatedSensorBounds.height() + 2 * paddingX
- width = rotatedSensorBounds.width() + 2 * paddingY
+ x = rotatedBounds.left - paddingX
+ y = rotatedBounds.top - paddingY
+ height = rotatedBounds.height() + 2 * paddingX
+ width = rotatedBounds.width() + 2 * paddingY
return this
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index e5c4855..1cc4141 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
@@ -41,6 +42,9 @@
@NonNull private ImageView mFingerprintView;
@NonNull private ImageView mFingerprintProgressView;
+ private LayoutParams mProgressParams;
+ private float mProgressBarRadius;
+
public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mFingerprintDrawable = new UdfpsEnrollDrawable(mContext, attrs);
@@ -57,6 +61,32 @@
}
@Override
+ void onSensorRectUpdated(RectF bounds) {
+ if (mUseExpandedOverlay) {
+ RectF converted = getBoundsRelativeToView(bounds);
+
+ mProgressParams = new LayoutParams(
+ (int) (converted.width() + mProgressBarRadius * 2),
+ (int) (converted.height() + mProgressBarRadius * 2));
+ mProgressParams.setMargins(
+ (int) (converted.left - mProgressBarRadius),
+ (int) (converted.top - mProgressBarRadius),
+ (int) (converted.right + mProgressBarRadius),
+ (int) (converted.bottom + mProgressBarRadius)
+ );
+
+ mFingerprintProgressView.setLayoutParams(mProgressParams);
+ super.onSensorRectUpdated(converted);
+ } else {
+ super.onSensorRectUpdated(bounds);
+ }
+ }
+
+ void setProgressBarRadius(float radius) {
+ mProgressBarRadius = radius;
+ }
+
+ @Override
public UdfpsDrawable getDrawable() {
return mFingerprintDrawable;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index e01273f..4017665 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -21,6 +21,8 @@
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -57,6 +59,7 @@
@NonNull ShadeExpansionStateManager shadeExpansionStateManager,
@NonNull SystemUIDialogManager systemUIDialogManager,
@NonNull DumpManager dumpManager,
+ @NonNull FeatureFlags featureFlags,
float scaleFactor) {
super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
dumpManager);
@@ -64,6 +67,11 @@
R.integer.config_udfpsEnrollProgressBar));
mEnrollHelper = enrollHelper;
mView.setEnrollHelper(mEnrollHelper);
+ mView.setProgressBarRadius(mEnrollProgressBarRadius);
+
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ mView.mUseExpandedOverlay = true;
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index bc274a0..339b8ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.View;
@@ -75,6 +76,8 @@
private int mAnimationType = ANIMATION_NONE;
private boolean mFullyInflated;
+ private LayoutParams mParams;
+
public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mFingerprintDrawable = new UdfpsFpDrawable(context);
@@ -239,6 +242,22 @@
updateAlpha();
}
+ @Override
+ void onSensorRectUpdated(RectF bounds) {
+ super.onSensorRectUpdated(bounds);
+
+ if (mUseExpandedOverlay) {
+ mParams = new LayoutParams((int) bounds.width(), (int) bounds.height());
+ RectF converted = getBoundsRelativeToView(bounds);
+ mParams.setMargins(
+ (int) converted.left,
+ (int) converted.top,
+ (int) converted.right,
+ (int) converted.bottom
+ );
+ }
+ }
+
/**
* Animates in the bg protection circle behind the fp icon to highlight the icon.
*/
@@ -277,6 +296,7 @@
pw.println(" mUdfpsRequested=" + mUdfpsRequested);
pw.println(" mInterpolatedDarkAmount=" + mInterpolatedDarkAmount);
pw.println(" mAnimationType=" + mAnimationType);
+ pw.println(" mUseExpandedOverlay=" + mUseExpandedOverlay);
}
private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener =
@@ -291,7 +311,12 @@
updatePadding();
updateColor();
updateAlpha();
- parent.addView(view);
+
+ if (mUseExpandedOverlay) {
+ parent.addView(view, mParams);
+ } else {
+ parent.addView(view);
+ }
// requires call to invalidate to update the color
mLockScreenFp.addValueCallback(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 91967f9..63144fc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -52,7 +52,6 @@
import java.io.PrintWriter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
/** Class that coordinates non-HBM animations during keyguard authentication. */
@@ -82,6 +81,8 @@
systemUIDialogManager,
dumpManager
) {
+ private val useExpandedOverlay: Boolean =
+ featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
private var showingUdfpsBouncer = false
private var udfpsRequested = false
@@ -233,7 +234,13 @@
if (transitionToFullShadeProgress != 0f) {
return
}
- udfpsController.onTouch(event)
+
+ // Forwarding touches not needed with expanded overlay
+ if (useExpandedOverlay) {
+ return
+ } else {
+ udfpsController.onTouch(event)
+ }
}
}
@@ -322,6 +329,7 @@
keyguardViewManager.setAlternateBouncer(mAlternateBouncer)
lockScreenShadeTransitionController.udfpsKeyguardViewController = this
activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+ view.mUseExpandedOverlay = useExpandedOverlay
}
override fun onViewDetached() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
index 98d4c22..7f3846c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayParams.kt
@@ -7,17 +7,23 @@
/**
* Collection of parameters that define an under-display fingerprint sensor (UDFPS) overlay.
*
- * @property sensorBounds coordinates of the bounding box around the sensor, in natural orientation,
- * in pixels, for the current resolution.
- * @property naturalDisplayWidth width of the physical display, in natural orientation, in pixels,
- * for the current resolution.
- * @property naturalDisplayHeight height of the physical display, in natural orientation, in pixels,
- * for the current resolution.
- * @property scaleFactor ratio of a dimension in the current resolution to the corresponding
- * dimension in the native resolution.
- * @property rotation current rotation of the display.
+ * [sensorBounds] coordinates of the bounding box around the sensor in natural orientation, in
+ * pixels, for the current resolution.
+ *
+ * [overlayBounds] coordinates of the UI overlay in natural orientation, in pixels, for the current
+ * resolution.
+ *
+ * [naturalDisplayWidth] width of the physical display in natural orientation, in pixels, for the
+ * current resolution.
+ *
+ * [naturalDisplayHeight] height of the physical display in natural orientation, in pixels, for the
+ * current resolution.
+ *
+ * [scaleFactor] ratio of a dimension in the current resolution to the corresponding dimension in
+ * the native resolution.
+ *
+ * [rotation] current rotation of the display.
*/
-
data class UdfpsOverlayParams(
val sensorBounds: Rect = Rect(),
val overlayBounds: Rect = Rect(),
@@ -26,19 +32,23 @@
val scaleFactor: Float = 1f,
@Rotation val rotation: Int = Surface.ROTATION_0
) {
+
+ /** Same as [sensorBounds], but in native resolution. */
+ val nativeSensorBounds = Rect(sensorBounds).apply { scale(1f / scaleFactor) }
+
/** See [android.view.DisplayInfo.logicalWidth] */
- val logicalDisplayWidth
- get() = if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+ val logicalDisplayWidth =
+ if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
naturalDisplayHeight
} else {
naturalDisplayWidth
}
/** See [android.view.DisplayInfo.logicalHeight] */
- val logicalDisplayHeight
- get() = if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+ val logicalDisplayHeight =
+ if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
naturalDisplayWidth
} else {
naturalDisplayHeight
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index a15456d..4a8877e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -20,6 +20,7 @@
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PointF
+import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import android.util.Log
@@ -38,9 +39,12 @@
attrs: AttributeSet?
) : FrameLayout(context, attrs), DozeReceiver {
+ // Use expanded overlay when feature flag is true, set by UdfpsViewController
+ var useExpandedOverlay: Boolean = false
+
// sensorRect may be bigger than the sensor. True sensor dimensions are defined in
// overlayParams.sensorBounds
- private val sensorRect = RectF()
+ var sensorRect = Rect()
private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null
private val debugTextPaint = Paint().apply {
isAntiAlias = true
@@ -92,13 +96,19 @@
val paddingX = animationViewController?.paddingX ?: 0
val paddingY = animationViewController?.paddingY ?: 0
- sensorRect.set(
- paddingX.toFloat(),
- paddingY.toFloat(),
- (overlayParams.sensorBounds.width() + paddingX).toFloat(),
- (overlayParams.sensorBounds.height() + paddingY).toFloat()
- )
- animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+ // Updates sensor rect in relation to the overlay view
+ if (useExpandedOverlay) {
+ animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+ } else {
+ sensorRect.set(
+ paddingX,
+ paddingY,
+ (overlayParams.sensorBounds.width() + paddingX),
+ (overlayParams.sensorBounds.height() + paddingY)
+ )
+
+ animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+ }
}
fun onTouchOutsideView() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
new file mode 100644
index 0000000..001fed7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.dagger
+
+import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector
+import com.android.systemui.biometrics.udfps.EllipseOverlapDetector
+import com.android.systemui.biometrics.udfps.OverlapDetector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import dagger.Module
+import dagger.Provides
+
+/** Dagger module for all things UDFPS. TODO(b/260558624): Move to BiometricsModule. */
+@Module
+interface UdfpsModule {
+ companion object {
+
+ @Provides
+ @SysUISingleton
+ fun providesOverlapDetector(featureFlags: FeatureFlags): OverlapDetector {
+ return if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+ EllipseOverlapDetector()
+ } else {
+ BoundingBoxOverlapDetector()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
new file mode 100644
index 0000000..79a0acb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.graphics.Rect
+import com.android.systemui.dagger.SysUISingleton
+
+/** Returns whether the touch coordinates are within the sensor's bounding box. */
+@SysUISingleton
+class BoundingBoxOverlapDetector : OverlapDetector {
+ override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean =
+ touchData.isWithinSensor(nativeSensorBounds)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
new file mode 100644
index 0000000..8572242
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.graphics.Point
+import android.graphics.Rect
+import com.android.systemui.dagger.SysUISingleton
+import kotlin.math.cos
+import kotlin.math.pow
+import kotlin.math.sin
+
+/**
+ * Approximates the touch as an ellipse and determines whether the ellipse has a sufficient overlap
+ * with the sensor.
+ */
+@SysUISingleton
+class EllipseOverlapDetector(private val neededPoints: Int = 2) : OverlapDetector {
+
+ override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
+ val points = calculateSensorPoints(nativeSensorBounds)
+ return points.count { checkPoint(it, touchData) } >= neededPoints
+ }
+
+ private fun checkPoint(point: Point, touchData: NormalizedTouchData): Boolean {
+ // Calculate if sensor point is within ellipse
+ // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE -
+ // yS))^2 / b^2) <= 1
+ val a: Float = cos(touchData.orientation) * (point.x - touchData.x)
+ val b: Float = sin(touchData.orientation) * (point.y - touchData.y)
+ val c: Float = sin(touchData.orientation) * (point.x - touchData.x)
+ val d: Float = cos(touchData.orientation) * (point.y - touchData.y)
+ val result =
+ (a + b).pow(2) / (touchData.minor / 2).pow(2) +
+ (c - d).pow(2) / (touchData.major / 2).pow(2)
+
+ return result <= 1
+ }
+
+ private fun calculateSensorPoints(sensorBounds: Rect): List<Point> {
+ val sensorX = sensorBounds.centerX()
+ val sensorY = sensorBounds.centerY()
+ val cornerOffset: Int = sensorBounds.width() / 4
+ val sideOffset: Int = sensorBounds.width() / 3
+
+ return listOf(
+ Point(sensorX - cornerOffset, sensorY - cornerOffset),
+ Point(sensorX, sensorY - sideOffset),
+ Point(sensorX + cornerOffset, sensorY - cornerOffset),
+ Point(sensorX - sideOffset, sensorY),
+ Point(sensorX, sensorY),
+ Point(sensorX + sideOffset, sensorY),
+ Point(sensorX - cornerOffset, sensorY + cornerOffset),
+ Point(sensorX, sensorY + sideOffset),
+ Point(sensorX + cornerOffset, sensorY + cornerOffset)
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/InteractionEvent.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/InteractionEvent.kt
new file mode 100644
index 0000000..6e47dad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/InteractionEvent.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.view.MotionEvent
+
+/** Interaction event between a finger and the under-display fingerprint sensor (UDFPS). */
+enum class InteractionEvent {
+ /**
+ * A finger entered the sensor area. This can originate from either [MotionEvent.ACTION_DOWN] or
+ * [MotionEvent.ACTION_MOVE].
+ */
+ DOWN,
+
+ /**
+ * A finger left the sensor area. This can originate from either [MotionEvent.ACTION_UP] or
+ * [MotionEvent.ACTION_MOVE].
+ */
+ UP,
+
+ /**
+ * The touch reporting has stopped. This corresponds to [MotionEvent.ACTION_CANCEL]. This should
+ * not be confused with [UP]. If there was a finger on the sensor, it may or may not still be on
+ * the sensor.
+ */
+ CANCEL,
+
+ /**
+ * The interaction hasn't changed since the previous event. The can originate from any of
+ * [MotionEvent.ACTION_DOWN], [MotionEvent.ACTION_MOVE], or [MotionEvent.ACTION_UP] if one of
+ * these is true:
+ * - There was previously a finger on the sensor, and that finger is still on the sensor.
+ * - There was previously no finger on the sensor, and there still isn't.
+ */
+ UNCHANGED,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
new file mode 100644
index 0000000..62bedc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/NormalizedTouchData.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.graphics.Rect
+import android.view.MotionEvent
+
+/** Touch data in natural orientation and native resolution. */
+data class NormalizedTouchData(
+
+ /**
+ * Value obtained from [MotionEvent.getPointerId], or [MotionEvent.INVALID_POINTER_ID] if the ID
+ * is not available.
+ */
+ val pointerId: Int,
+
+ /** [MotionEvent.getRawX] mapped to natural orientation and native resolution. */
+ val x: Float,
+
+ /** [MotionEvent.getRawY] mapped to natural orientation and native resolution. */
+ val y: Float,
+
+ /** [MotionEvent.getTouchMinor] mapped to natural orientation and native resolution. */
+ val minor: Float,
+
+ /** [MotionEvent.getTouchMajor] mapped to natural orientation and native resolution. */
+ val major: Float,
+
+ /** [MotionEvent.getOrientation] mapped to natural orientation. */
+ val orientation: Float,
+
+ /** [MotionEvent.getEventTime]. */
+ val time: Long,
+
+ /** [MotionEvent.getDownTime]. */
+ val gestureStart: Long,
+) {
+
+ /**
+ * [nativeSensorBounds] contains the location and dimensions of the sensor area in native
+ * resolution and natural orientation.
+ *
+ * Returns whether the coordinates of the given pointer are within the sensor's bounding box.
+ */
+ fun isWithinSensor(nativeSensorBounds: Rect): Boolean {
+ return nativeSensorBounds.left <= x &&
+ nativeSensorBounds.right >= x &&
+ nativeSensorBounds.top <= y &&
+ nativeSensorBounds.bottom >= y
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt
new file mode 100644
index 0000000..0fec8ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.graphics.Rect
+
+/** Determines whether the touch has a sufficient overlap with the sensor. */
+interface OverlapDetector {
+ fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
new file mode 100644
index 0000000..338bf66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.graphics.PointF
+import android.util.RotationUtils
+import android.view.MotionEvent
+import android.view.MotionEvent.INVALID_POINTER_ID
+import android.view.Surface
+import com.android.systemui.biometrics.UdfpsOverlayParams
+import com.android.systemui.biometrics.udfps.TouchProcessorResult.Failure
+import com.android.systemui.biometrics.udfps.TouchProcessorResult.ProcessedTouch
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * TODO(b/259140693): Consider using an object pool of TouchProcessorResult to avoid allocations.
+ */
+@SysUISingleton
+class SinglePointerTouchProcessor @Inject constructor(val overlapDetector: OverlapDetector) :
+ TouchProcessor {
+
+ override fun processTouch(
+ event: MotionEvent,
+ previousPointerOnSensorId: Int,
+ overlayParams: UdfpsOverlayParams,
+ ): TouchProcessorResult {
+
+ fun preprocess(): PreprocessedTouch {
+ // TODO(b/253085297): Add multitouch support. pointerIndex can be > 0 for ACTION_MOVE.
+ val pointerIndex = 0
+ val touchData = event.normalize(pointerIndex, overlayParams)
+ val isGoodOverlap =
+ overlapDetector.isGoodOverlap(touchData, overlayParams.nativeSensorBounds)
+ return PreprocessedTouch(touchData, previousPointerOnSensorId, isGoodOverlap)
+ }
+
+ return when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> processActionDown(preprocess())
+ MotionEvent.ACTION_MOVE -> processActionMove(preprocess())
+ MotionEvent.ACTION_UP -> processActionUp(preprocess())
+ MotionEvent.ACTION_CANCEL ->
+ processActionCancel(event.normalize(pointerIndex = 0, overlayParams))
+ else ->
+ Failure("Unsupported MotionEvent." + MotionEvent.actionToString(event.actionMasked))
+ }
+ }
+}
+
+private data class PreprocessedTouch(
+ val data: NormalizedTouchData,
+ val previousPointerOnSensorId: Int,
+ val isGoodOverlap: Boolean,
+)
+
+private fun processActionDown(touch: PreprocessedTouch): TouchProcessorResult {
+ return if (touch.isGoodOverlap) {
+ ProcessedTouch(InteractionEvent.DOWN, pointerOnSensorId = touch.data.pointerId, touch.data)
+ } else {
+ val event =
+ if (touch.data.pointerId == touch.previousPointerOnSensorId) {
+ InteractionEvent.UP
+ } else {
+ InteractionEvent.UNCHANGED
+ }
+ ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
+ }
+}
+
+private fun processActionMove(touch: PreprocessedTouch): TouchProcessorResult {
+ val hadPointerOnSensor = touch.previousPointerOnSensorId != INVALID_POINTER_ID
+ val interactionEvent =
+ when {
+ touch.isGoodOverlap && !hadPointerOnSensor -> InteractionEvent.DOWN
+ !touch.isGoodOverlap && hadPointerOnSensor -> InteractionEvent.UP
+ else -> InteractionEvent.UNCHANGED
+ }
+ val pointerOnSensorId =
+ when (interactionEvent) {
+ InteractionEvent.UNCHANGED -> touch.previousPointerOnSensorId
+ InteractionEvent.DOWN -> touch.data.pointerId
+ else -> INVALID_POINTER_ID
+ }
+ return ProcessedTouch(interactionEvent, pointerOnSensorId, touch.data)
+}
+
+private fun processActionUp(touch: PreprocessedTouch): TouchProcessorResult {
+ return if (touch.isGoodOverlap) {
+ ProcessedTouch(InteractionEvent.UP, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
+ } else {
+ val event =
+ if (touch.previousPointerOnSensorId != INVALID_POINTER_ID) {
+ InteractionEvent.UP
+ } else {
+ InteractionEvent.UNCHANGED
+ }
+ ProcessedTouch(event, pointerOnSensorId = INVALID_POINTER_ID, touch.data)
+ }
+}
+
+private fun processActionCancel(data: NormalizedTouchData): TouchProcessorResult {
+ return ProcessedTouch(InteractionEvent.CANCEL, pointerOnSensorId = INVALID_POINTER_ID, data)
+}
+
+/**
+ * Returns the touch information from the given [MotionEvent] with the relevant fields mapped to
+ * natural orientation and native resolution.
+ */
+private fun MotionEvent.normalize(
+ pointerIndex: Int,
+ overlayParams: UdfpsOverlayParams
+): NormalizedTouchData {
+ val naturalTouch: PointF = rotateToNaturalOrientation(pointerIndex, overlayParams)
+ val nativeX = naturalTouch.x / overlayParams.scaleFactor
+ val nativeY = naturalTouch.y / overlayParams.scaleFactor
+ val nativeMinor: Float = getTouchMinor(pointerIndex) / overlayParams.scaleFactor
+ val nativeMajor: Float = getTouchMajor(pointerIndex) / overlayParams.scaleFactor
+ return NormalizedTouchData(
+ pointerId = getPointerId(pointerIndex),
+ x = nativeX,
+ y = nativeY,
+ minor = nativeMinor,
+ major = nativeMajor,
+ // TODO(b/259311354): touch orientation should be reported relative to Surface.ROTATION_O.
+ orientation = getOrientation(pointerIndex),
+ time = eventTime,
+ gestureStart = downTime,
+ )
+}
+
+/**
+ * Returns the [MotionEvent.getRawX] and [MotionEvent.getRawY] of the given pointer as if the device
+ * is in the [Surface.ROTATION_0] orientation.
+ */
+private fun MotionEvent.rotateToNaturalOrientation(
+ pointerIndex: Int,
+ overlayParams: UdfpsOverlayParams
+): PointF {
+ val touchPoint = PointF(getRawX(pointerIndex), getRawY(pointerIndex))
+ val rot = overlayParams.rotation
+ if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
+ RotationUtils.rotatePointF(
+ touchPoint,
+ RotationUtils.deltaRotation(rot, Surface.ROTATION_0),
+ overlayParams.logicalDisplayWidth.toFloat(),
+ overlayParams.logicalDisplayHeight.toFloat()
+ )
+ }
+ return touchPoint
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt
new file mode 100644
index 0000000..ffcebf9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.view.MotionEvent
+import com.android.systemui.biometrics.UdfpsOverlayParams
+
+/**
+ * Determines whether a finger entered or left the area of the under-display fingerprint sensor
+ * (UDFPS). Maps the touch information from a [MotionEvent] to the orientation and scale independent
+ * [NormalizedTouchData].
+ */
+interface TouchProcessor {
+
+ /**
+ * [event] touch event to be processed.
+ *
+ * [previousPointerOnSensorId] pointerId for the finger that was on the sensor prior to this
+ * event. See [MotionEvent.getPointerId]. If there was no finger on the sensor, this should be
+ * set to [MotionEvent.INVALID_POINTER_ID].
+ *
+ * [overlayParams] contains the location and dimensions of the sensor area, as well as the scale
+ * factor and orientation of the overlay. See [UdfpsOverlayParams].
+ *
+ * Returns [TouchProcessorResult.ProcessedTouch] on success, and [TouchProcessorResult.Failure]
+ * on failure.
+ */
+ fun processTouch(
+ event: MotionEvent,
+ previousPointerOnSensorId: Int,
+ overlayParams: UdfpsOverlayParams,
+ ): TouchProcessorResult
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessorResult.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessorResult.kt
new file mode 100644
index 0000000..be75bb0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessorResult.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.view.MotionEvent
+
+/** Contains all the possible returns types for [TouchProcessor.processTouch] */
+sealed class TouchProcessorResult {
+
+ /**
+ * [event] whether a finger entered or left the sensor area. See [InteractionEvent].
+ *
+ * [pointerOnSensorId] pointerId fof the finger that's currently on the sensor. See
+ * [MotionEvent.getPointerId]. If there is no finger on the sensor, the value is set to
+ * [MotionEvent.INVALID_POINTER_ID].
+ *
+ * [touchData] relevant data from the MotionEvent, mapped to natural orientation and native
+ * resolution. See [NormalizedTouchData].
+ */
+ data class ProcessedTouch(
+ val event: InteractionEvent,
+ val pointerOnSensorId: Int,
+ val touchData: NormalizedTouchData
+ ) : TouchProcessorResult()
+
+ /** [reason] the reason for the failure. */
+ data class Failure(val reason: String = "") : TouchProcessorResult()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 537cbc5..a0a892d 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -64,8 +64,9 @@
* from SystemUI. That way the number of calls to [BroadcastReceiver.onReceive] can be reduced for
* a given broadcast.
*
- * Use only for IntentFilters with actions and optionally categories. It does not support,
- * permissions, schemes, data types, data authorities or priority different than 0.
+ * Use only for IntentFilters with actions and optionally categories. It does not support schemes,
+ * data types, data authorities or priority different than 0.
+ *
* Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery).
* Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui
* and doesn't need to worry about being killed.
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 616e49c..1454210 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -31,7 +31,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleView
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index e82d0ea..3808ab7 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -30,7 +30,7 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.ripple.RippleShader.RippleShape;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
/**
* A WirelessChargingAnimation is a view containing view + animation for wireless charging.
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 1455699..36103f8 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -33,9 +33,9 @@
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.ripple.RippleAnimationConfig;
-import com.android.systemui.ripple.RippleShader.RippleShape;
-import com.android.systemui.ripple.RippleView;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
+import com.android.systemui.surfaceeffects.ripple.RippleView;
import java.text.NumberFormat;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index beaccba..e8e1f2e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -231,7 +231,8 @@
// check for false tap if it is a seekbar interaction
if (interactionType == MEDIA_SEEKBAR) {
- localResult[0] &= isFalseTap(LOW_PENALTY);
+ localResult[0] &= isFalseTap(mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+ ? FalsingManager.MODERATE_PENALTY : FalsingManager.LOW_PENALTY);
}
logDebug("False Gesture (type: " + interactionType + "): " + localResult[0]);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
index c4723e8..5c905df 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java
@@ -29,7 +29,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingPlugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.util.DeviceConfigProxy;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index c853671..fb37def 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -266,6 +266,7 @@
mExitAnimator.cancel();
}
reset();
+ mClipboardLogger.setClipSource(clipSource);
String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
@@ -525,21 +526,27 @@
static class ClipboardLogger {
private final UiEventLogger mUiEventLogger;
+ private String mClipSource;
private boolean mGuarded = false;
ClipboardLogger(UiEventLogger uiEventLogger) {
mUiEventLogger = uiEventLogger;
}
+ void setClipSource(String clipSource) {
+ mClipSource = clipSource;
+ }
+
void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) {
if (!mGuarded) {
mGuarded = true;
- mUiEventLogger.log(event);
+ mUiEventLogger.log(event, 0, mClipSource);
}
}
void reset() {
mGuarded = false;
+ mClipSource = null;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
index cece764..c194e66 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
@@ -20,7 +20,10 @@
import android.content.ClipDescription;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Build;
+import android.provider.DeviceConfig;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.R;
import javax.inject.Inject;
@@ -35,6 +38,12 @@
if (clipData != null && clipData.getDescription().getExtras() != null
&& clipData.getDescription().getExtras().getBoolean(
ClipDescription.EXTRA_IS_REMOTE_DEVICE)) {
+ if (Build.isDebuggable() && DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.CLIPBOARD_IGNORE_REMOTE_COPY_SOURCE,
+ false)) {
+ return true;
+ }
ComponentName remoteComponent = ComponentName.unflattenFromString(
context.getResources().getString(R.string.config_remoteCopyPackage));
if (remoteComponent != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index 4dfcd63..66e5d7c4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -30,6 +30,7 @@
import android.service.controls.ControlsProviderService
import androidx.annotation.WorkerThread
import com.android.settingslib.applications.DefaultAppInfo
+import com.android.systemui.R
import java.util.Objects
class ControlsServiceInfo(
@@ -59,7 +60,8 @@
* instead of using the controls rendered by SystemUI.
*
* The activity must be in the same package, exported, enabled and protected by the
- * [Manifest.permission.BIND_CONTROLS] permission.
+ * [Manifest.permission.BIND_CONTROLS] permission. Additionally, only packages declared in
+ * [R.array.config_controlsPreferredPackages] can declare activities for use as a panel.
*/
var panelActivity: ComponentName? = null
private set
@@ -70,6 +72,9 @@
fun resolvePanelActivity() {
if (resolved) return
resolved = true
+ val validPackages = context.resources
+ .getStringArray(R.array.config_controlsPreferredPackages)
+ if (componentName.packageName !in validPackages) return
panelActivity = _panelActivity?.let {
val resolveInfos = mPm.queryIntentActivitiesAsUser(
Intent().setComponent(it),
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt
new file mode 100644
index 0000000..3d10ab9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls
+
+import kotlinx.coroutines.flow.StateFlow
+
+/** Repository for Device controls related settings. */
+interface ControlsSettingsRepository {
+ /** Whether device controls activity can be shown above lockscreen for this user. */
+ val canShowControlsInLockscreen: StateFlow<Boolean>
+
+ /** Whether trivial controls can be actioned from the lockscreen for this user. */
+ val allowActionOnTrivialControlsInLockscreen: StateFlow<Boolean>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt
new file mode 100644
index 0000000..9dc422a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls
+
+import android.provider.Settings
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.SettingObserver
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * This implementation uses an `@Application` [CoroutineScope] to provide hot flows for the values
+ * of the tracked settings.
+ */
+@SysUISingleton
+class ControlsSettingsRepositoryImpl
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val userRepository: UserRepository,
+ private val secureSettings: SecureSettings
+) : ControlsSettingsRepository {
+
+ override val canShowControlsInLockscreen =
+ makeFlowForSetting(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS)
+
+ override val allowActionOnTrivialControlsInLockscreen =
+ makeFlowForSetting(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun makeFlowForSetting(setting: String): StateFlow<Boolean> {
+ return userRepository.selectedUserInfo
+ .distinctUntilChanged()
+ .flatMapLatest { userInfo ->
+ conflatedCallbackFlow {
+ val observer =
+ object : SettingObserver(secureSettings, null, setting, userInfo.id) {
+ override fun handleValueChanged(
+ value: Int,
+ observedChange: Boolean
+ ) {
+ trySend(value == 1)
+ }
+ }
+ observer.isListening = true
+ trySend(observer.value == 1)
+ awaitClose { observer.isListening = false }
+ }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+ }
+ .stateIn(
+ scope,
+ started = SharingStarted.Eagerly,
+ // When the observer starts listening, the flow will emit the current value
+ // so the initialValue here is irrelevant.
+ initialValue = false,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 31fadb1..2f49c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -24,6 +24,7 @@
import com.android.systemui.util.UserAwareController
import com.android.systemui.controls.management.ControlsFavoritingActivity
import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
import java.util.function.Consumer
/**
@@ -184,8 +185,8 @@
*/
fun countFavoritesForComponent(componentName: ComponentName): Int
- /** See [ControlsUiController.getPreferredStructure]. */
- fun getPreferredStructure(): StructureInfo
+ /** See [ControlsUiController.getPreferredSelectedItem]. */
+ fun getPreferredSelection(): SelectedItem
/**
* Interface for structure to pass data to [ControlsFavoritingActivity].
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 50ce9d4..80c5f66 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -33,11 +33,11 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.backup.BackupHelper
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
@@ -59,11 +59,10 @@
private val uiController: ControlsUiController,
private val bindingController: ControlsBindingController,
private val listingController: ControlsListingController,
- private val broadcastDispatcher: BroadcastDispatcher,
private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
dumpManager: DumpManager,
- userTracker: UserTracker
) : Dumpable, ControlsController {
companion object {
@@ -120,18 +119,15 @@
userChanging = false
}
- private val userSwitchReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action == Intent.ACTION_USER_SWITCHED) {
- userChanging = true
- val newUser =
- UserHandle.of(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, sendingUserId))
- if (currentUser == newUser) {
- userChanging = false
- return
- }
- setValuesForUser(newUser)
+ private val userTrackerCallback = object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ userChanging = true
+ val newUserHandle = UserHandle.of(newUser)
+ if (currentUser == newUserHandle) {
+ userChanging = false
+ return
}
+ setValuesForUser(newUserHandle)
}
}
@@ -233,12 +229,7 @@
dumpManager.registerDumpable(javaClass.name, this)
resetFavorites()
userChanging = false
- broadcastDispatcher.registerReceiver(
- userSwitchReceiver,
- IntentFilter(Intent.ACTION_USER_SWITCHED),
- executor,
- UserHandle.ALL
- )
+ userTracker.addCallback(userTrackerCallback, executor)
context.registerReceiver(
restoreFinishedReceiver,
IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
@@ -250,7 +241,7 @@
}
fun destroy() {
- broadcastDispatcher.unregisterReceiver(userSwitchReceiver)
+ userTracker.removeCallback(userTrackerCallback)
context.unregisterReceiver(restoreFinishedReceiver)
listingController.removeCallback(listingCallback)
}
@@ -556,8 +547,8 @@
)
}
- override fun getPreferredStructure(): StructureInfo {
- return uiController.getPreferredStructure(getFavorites())
+ override fun getPreferredSelection(): SelectedItem {
+ return uiController.getPreferredSelectedItem(getFavorites())
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt
index 34bfa13..c8090bf 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/StructureInfo.kt
@@ -31,4 +31,9 @@
val componentName: ComponentName,
val structure: CharSequence,
val controls: List<ControlInfo>
-)
+) {
+ companion object {
+ val EMPTY_COMPONENT = ComponentName("", "")
+ val EMPTY_STRUCTURE = StructureInfo(EMPTY_COMPONENT, "", mutableListOf())
+ }
+}
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 9e4a364..77d0496e4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -16,13 +16,10 @@
package com.android.systemui.controls.dagger
-import android.content.ContentResolver
import android.content.Context
-import android.database.ContentObserver
-import android.os.UserHandle
-import android.provider.Settings
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+import com.android.systemui.controls.ControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
@@ -31,12 +28,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.settings.SecureSettings
import dagger.Lazy
+import kotlinx.coroutines.flow.StateFlow
import java.util.Optional
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
/**
* Pseudo-component to inject into classes outside `com.android.systemui.controls`.
@@ -46,47 +41,26 @@
*/
@SysUISingleton
class ControlsComponent @Inject constructor(
- @ControlsFeatureEnabled private val featureEnabled: Boolean,
- private val context: Context,
- private val lazyControlsController: Lazy<ControlsController>,
- private val lazyControlsUiController: Lazy<ControlsUiController>,
- private val lazyControlsListingController: Lazy<ControlsListingController>,
- private val lockPatternUtils: LockPatternUtils,
- private val keyguardStateController: KeyguardStateController,
- private val userTracker: UserTracker,
- private val secureSettings: SecureSettings,
- private val optionalControlsTileResourceConfiguration:
- Optional<ControlsTileResourceConfiguration>
+ @ControlsFeatureEnabled private val featureEnabled: Boolean,
+ private val context: Context,
+ private val lazyControlsController: Lazy<ControlsController>,
+ private val lazyControlsUiController: Lazy<ControlsUiController>,
+ private val lazyControlsListingController: Lazy<ControlsListingController>,
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardStateController: KeyguardStateController,
+ private val userTracker: UserTracker,
+ controlsSettingsRepository: ControlsSettingsRepository,
+ optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration>
) {
- private val contentResolver: ContentResolver
- get() = context.contentResolver
- private val _canShowWhileLockedSetting = MutableStateFlow(false)
- val canShowWhileLockedSetting = _canShowWhileLockedSetting.asStateFlow()
+ val canShowWhileLockedSetting: StateFlow<Boolean> =
+ controlsSettingsRepository.canShowControlsInLockscreen
private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
optionalControlsTileResourceConfiguration.orElse(
ControlsTileResourceConfigurationImpl()
)
- val showWhileLockedObserver = object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- updateShowWhileLocked()
- }
- }
-
- init {
- if (featureEnabled) {
- secureSettings.registerContentObserverForUser(
- Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS),
- false, /* notifyForDescendants */
- showWhileLockedObserver,
- UserHandle.USER_ALL
- )
- updateShowWhileLocked()
- }
- }
-
fun getControlsController(): Optional<ControlsController> {
return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
}
@@ -127,11 +101,6 @@
return Visibility.AVAILABLE
}
- private fun updateShowWhileLocked() {
- _canShowWhileLockedSetting.value = secureSettings.getIntForUser(
- Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
- }
-
enum class Visibility {
AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE
}
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 6f58abd..9ae605e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -20,6 +20,8 @@
import android.content.pm.PackageManager
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsMetricsLoggerImpl
+import com.android.systemui.controls.ControlsSettingsRepository
+import com.android.systemui.controls.ControlsSettingsRepositoryImpl
import com.android.systemui.controls.controller.ControlsBindingController
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
import com.android.systemui.controls.controller.ControlsController
@@ -83,6 +85,11 @@
abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
@Binds
+ abstract fun provideSettingsManager(
+ manager: ControlsSettingsRepositoryImpl
+ ): ControlsSettingsRepository
+
+ @Binds
abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
index 2389ad1..753d5ad 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -64,7 +64,8 @@
val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
it.loadLabel() ?: ""
}
- listOfServices = serviceInfos.sortedWith(localeComparator)
+ listOfServices = serviceInfos.filter { it.panelActivity == null }
+ .sortedWith(localeComparator)
uiExecutor.execute(::notifyDataSetChanged)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index b11103a..7df0865 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -18,6 +18,7 @@
import android.app.ActivityOptions
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
@@ -33,21 +34,23 @@
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Activity for rearranging and removing controls for a given structure
*/
open class ControlsEditingActivity @Inject constructor(
+ @Main private val mainExecutor: Executor,
private val controller: ControlsControllerImpl,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val customIconCache: CustomIconCache,
private val uiController: ControlsUiController
) : ComponentActivity() {
@@ -66,12 +69,12 @@
private lateinit var subtitle: TextView
private lateinit var saveButton: View
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
@@ -104,7 +107,7 @@
super.onStart()
setUpList()
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, mainExecutor)
if (DEBUG) {
Log.d(TAG, "Registered onBackInvokedCallback")
@@ -115,7 +118,7 @@
override fun onStop() {
super.onStop()
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
if (DEBUG) {
Log.d(TAG, "Unregistered onBackInvokedCallback")
@@ -248,7 +251,7 @@
}
override fun onDestroy() {
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
super.onDestroy()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 9b2a728..3e97d31 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -20,6 +20,7 @@
import android.animation.AnimatorListenerAdapter
import android.app.ActivityOptions
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
@@ -39,7 +40,6 @@
import androidx.viewpager2.widget.ViewPager2
import com.android.systemui.Prefs
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.TooltipManager
import com.android.systemui.controls.controller.ControlsControllerImpl
@@ -47,7 +47,7 @@
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
import java.text.Collator
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -57,7 +57,7 @@
@Main private val executor: Executor,
private val controller: ControlsControllerImpl,
private val listingController: ControlsListingController,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val uiController: ControlsUiController
) : ComponentActivity() {
@@ -95,12 +95,12 @@
private var cancelLoadRunnable: Runnable? = null
private var isPagerLoaded = false
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
@@ -363,7 +363,7 @@
super.onStart()
listingController.addCallback(listingCallback)
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, executor)
if (DEBUG) {
Log.d(TAG, "Registered onBackInvokedCallback")
@@ -388,7 +388,7 @@
super.onStop()
listingController.removeCallback(listingCallback)
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
if (DEBUG) {
Log.d(TAG, "Unregistered onBackInvokedCallback")
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 115edd11..c6428ef 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -91,11 +91,12 @@
override var currentUserId = userTracker.userId
private set
- private val serviceListingCallback = ServiceListing.Callback {
+ private val serviceListingCallback = ServiceListing.Callback { list ->
+ Log.d(TAG, "ServiceConfig reloaded, count: ${list.size}")
+ val newServices = list.map { ControlsServiceInfo(userTracker.userContext, it) }
+ // After here, `list` is not captured, so we don't risk modifying it outside of the callback
backgroundExecutor.execute {
if (userChangeInProgress.get() > 0) return@execute
- Log.d(TAG, "ServiceConfig reloaded, count: ${it.size}")
- val newServices = it.map { ControlsServiceInfo(userTracker.userContext, it) }
if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
newServices.forEach(ControlsServiceInfo::resolvePanelActivity)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 47690a7..90bc5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -18,6 +18,7 @@
import android.app.ActivityOptions
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
@@ -33,13 +34,12 @@
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -51,7 +51,7 @@
@Background private val backExecutor: Executor,
private val listingController: ControlsListingController,
private val controlsController: ControlsController,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val uiController: ControlsUiController
) : ComponentActivity() {
@@ -62,12 +62,12 @@
}
private var backShouldExit = false
private lateinit var recyclerView: RecyclerView
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = listingController.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
@@ -129,7 +129,7 @@
override fun onStart() {
super.onStart()
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, executor)
recyclerView.alpha = 0.0f
recyclerView.adapter = AppAdapter(
@@ -161,7 +161,7 @@
override fun onStop() {
super.onStop()
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
if (DEBUG) {
Log.d(TAG, "Unregistered onBackInvokedCallback")
@@ -190,7 +190,7 @@
}
override fun onDestroy() {
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
super.onDestroy()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
index b376455..86bde5c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt
@@ -19,6 +19,7 @@
import android.app.AlertDialog
import android.app.Dialog
import android.content.ComponentName
+import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
@@ -32,18 +33,20 @@
import android.widget.TextView
import androidx.activity.ComponentActivity
import com.android.systemui.R
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.ui.RenderInfo
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
+import java.util.concurrent.Executor
import javax.inject.Inject
open class ControlsRequestDialog @Inject constructor(
+ @Main private val mainExecutor: Executor,
private val controller: ControlsController,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val controlsListingController: ControlsListingController
) : ComponentActivity(), DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
@@ -58,12 +61,12 @@
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {}
}
- private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
- override fun onUserSwitched(newUserId: Int) {
- if (newUserId != startingUser) {
- stopTracking()
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (newUser != startingUser) {
+ userTracker.removeCallback(this)
finish()
}
}
@@ -72,7 +75,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- currentUserTracker.startTracking()
+ userTracker.addCallback(userTrackerCallback, mainExecutor)
controlsListingController.addCallback(callback)
val requestUser = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL)
@@ -118,7 +121,7 @@
override fun onDestroy() {
dialog?.dismiss()
- currentUserTracker.stopTracking()
+ userTracker.removeCallback(userTrackerCallback)
controlsListingController.removeCallback(callback)
super.onDestroy()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index b8a0013..8472ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -24,9 +24,6 @@
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
-import android.database.ContentObserver
-import android.net.Uri
-import android.os.Handler
import android.os.UserHandle
import android.os.VibrationEffect
import android.provider.Settings.Secure
@@ -40,6 +37,7 @@
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -68,17 +66,17 @@
private val vibrator: VibratorHelper,
private val secureSettings: SecureSettings,
private val userContextProvider: UserContextProvider,
- @Main mainHandler: Handler
+ private val controlsSettingsRepository: ControlsSettingsRepository,
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private var pendingAction: Action? = null
private var actionsInProgress = mutableSetOf<String>()
private val isLocked: Boolean
get() = !keyguardStateController.isUnlocked()
- private var mAllowTrivialControls: Boolean = secureSettings.getIntForUser(
- Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
- private var mShowDeviceControlsInLockscreen: Boolean = secureSettings.getIntForUser(
- Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
+ private val allowTrivialControls: Boolean
+ get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
+ private val showDeviceControlsInLockscreen: Boolean
+ get() = controlsSettingsRepository.canShowControlsInLockscreen.value
override lateinit var activityContext: Context
companion object {
@@ -86,38 +84,6 @@
private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
}
- init {
- val lockScreenShowControlsUri =
- secureSettings.getUriFor(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)
- val showControlsUri =
- secureSettings.getUriFor(Secure.LOCKSCREEN_SHOW_CONTROLS)
- val controlsContentObserver = object : ContentObserver(mainHandler) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- super.onChange(selfChange, uri)
- when (uri) {
- lockScreenShowControlsUri -> {
- mAllowTrivialControls = secureSettings.getIntForUser(
- Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
- 0, UserHandle.USER_CURRENT) != 0
- }
- showControlsUri -> {
- mShowDeviceControlsInLockscreen = secureSettings
- .getIntForUser(Secure.LOCKSCREEN_SHOW_CONTROLS,
- 0, UserHandle.USER_CURRENT) != 0
- }
- }
- }
- }
- secureSettings.registerContentObserverForUser(
- lockScreenShowControlsUri,
- false /* notifyForDescendants */, controlsContentObserver, UserHandle.USER_ALL
- )
- secureSettings.registerContentObserverForUser(
- showControlsUri,
- false /* notifyForDescendants */, controlsContentObserver, UserHandle.USER_ALL
- )
- }
-
override fun closeDialogs() {
dialog?.dismiss()
dialog = null
@@ -224,7 +190,7 @@
@AnyThread
@VisibleForTesting
fun bouncerOrRun(action: Action) {
- val authRequired = action.authIsRequired || !mAllowTrivialControls
+ val authRequired = action.authIsRequired || !allowTrivialControls
if (keyguardStateController.isShowing() && authRequired) {
if (isLocked) {
@@ -282,7 +248,7 @@
PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
if (attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
- (mShowDeviceControlsInLockscreen && mAllowTrivialControls)) {
+ (showDeviceControlsInLockscreen && allowTrivialControls)) {
return
}
val builder = AlertDialog
@@ -304,7 +270,7 @@
true
}
- if (mShowDeviceControlsInLockscreen) {
+ if (showDeviceControlsInLockscreen) {
dialog = builder
.setTitle(R.string.controls_settings_trivial_controls_dialog_title)
.setMessage(R.string.controls_settings_trivial_controls_dialog_message)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index d3b5d0e..bd704c1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -27,10 +27,13 @@
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowInsets.Type
+import android.view.WindowManager
import androidx.activity.ComponentActivity
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.management.ControlsAnimations
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import javax.inject.Inject
/**
@@ -44,6 +47,7 @@
private val uiController: ControlsUiController,
private val broadcastDispatcher: BroadcastDispatcher,
private val dreamManager: IDreamManager,
+ private val featureFlags: FeatureFlags
) : ComponentActivity() {
private lateinit var parent: ViewGroup
@@ -52,6 +56,9 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+ window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+ }
setContentView(R.layout.controls_fullscreen)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index c1cfbcb..f5c5905 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -35,7 +35,7 @@
/**
* Returns the preferred activity to start, depending on if the user has favorited any
- * controls.
+ * controls or whether there are any app providing panels.
*/
fun resolveActivity(): Class<*>
@@ -53,9 +53,43 @@
)
/**
- * Returns the structure that is currently preferred by the user.
+ * Returns the element that is currently preferred by the user.
*
- * This structure will be the one that appears when the user first opens the controls activity.
+ * This element will be the one that appears when the user first opens the controls activity.
*/
- fun getPreferredStructure(structures: List<StructureInfo>): StructureInfo
+ fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
}
+
+sealed class SelectedItem {
+
+ abstract val name: CharSequence
+ abstract val hasControls: Boolean
+ abstract val componentName: ComponentName
+
+ /**
+ * Represents the currently selected item for a structure.
+ */
+ data class StructureItem(val structure: StructureInfo) : SelectedItem() {
+ override val name: CharSequence = structure.structure
+ override val hasControls: Boolean = structure.controls.isNotEmpty()
+ override val componentName: ComponentName = structure.componentName
+ }
+
+ /**
+ * Represents the currently selected item for a service that provides a panel activity.
+ *
+ * The [componentName] is that of the service, as that is the expected identifier that should
+ * not change (to always provide proper migration).
+ */
+ data class PanelItem(
+ val appName: CharSequence,
+ override val componentName:
+ ComponentName
+ ) : SelectedItem() {
+ override val name: CharSequence = appName
+ override val hasControls: Boolean = true
+ }
+ companion object {
+ val EMPTY_SELECTION: SelectedItem = StructureItem(StructureInfo.EMPTY_STRUCTURE)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 6cb0e8b..4c8e1ac 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -21,6 +21,7 @@
import android.animation.ObjectAnimator
import android.app.Activity
import android.app.ActivityOptions
+import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.Intent
@@ -36,18 +37,22 @@
import android.view.animation.DecelerateInterpolator
import android.widget.AdapterView
import android.widget.ArrayAdapter
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ListPopupWindow
import android.widget.Space
import android.widget.TextView
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.CustomIconCache
-import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.controller.StructureInfo.Companion.EMPTY_COMPONENT
+import com.android.systemui.controls.controller.StructureInfo.Companion.EMPTY_STRUCTURE
import com.android.systemui.controls.management.ControlAdapter
import com.android.systemui.controls.management.ControlsEditingActivity
import com.android.systemui.controls.management.ControlsFavoritingActivity
@@ -56,16 +61,21 @@
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.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.ShadeController
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.asIndenting
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.indentIfPossible
+import com.android.wm.shell.TaskViewFactory
import dagger.Lazy
+import java.io.PrintWriter
import java.text.Collator
+import java.util.Optional
import java.util.function.Consumer
import javax.inject.Inject
@@ -80,39 +90,34 @@
val controlsListingController: Lazy<ControlsListingController>,
val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
- private val shadeController: ShadeController,
private val iconCache: CustomIconCache,
private val controlsMetricsLogger: ControlsMetricsLogger,
private val keyguardStateController: KeyguardStateController,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
-) : ControlsUiController {
+ private val taskViewFactory: Optional<TaskViewFactory>,
+ dumpManager: DumpManager
+) : ControlsUiController, Dumpable {
companion object {
private const val PREF_COMPONENT = "controls_component"
- private const val PREF_STRUCTURE = "controls_structure"
+ private const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
+ private const val PREF_IS_PANEL = "controls_is_panel"
private const val FADE_IN_MILLIS = 200L
-
- private val EMPTY_COMPONENT = ComponentName("", "")
- private val EMPTY_STRUCTURE = StructureInfo(
- EMPTY_COMPONENT,
- "",
- mutableListOf<ControlInfo>()
- )
}
- private var selectedStructure: StructureInfo = EMPTY_STRUCTURE
+ private var selectedItem: SelectedItem = SelectedItem.EMPTY_SELECTION
private lateinit var allStructures: List<StructureInfo>
private val controlsById = mutableMapOf<ControlKey, ControlWithState>()
private val controlViewsById = mutableMapOf<ControlKey, ControlViewHolder>()
private lateinit var parent: ViewGroup
- private lateinit var lastItems: List<SelectionItem>
private var popup: ListPopupWindow? = null
private var hidden = true
private lateinit var onDismiss: Runnable
private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
private var retainCache = false
+ private var lastSelections = emptyList<SelectionItem>()
private val sharedPreferences
get() = userFileManager.getSharedPreferences(
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
@@ -120,6 +125,8 @@
userId = userTracker.userId
)
+ private var taskViewController: PanelTaskViewController? = null
+
private val collator = Collator.getInstance(context.resources.configuration.locales[0])
private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) {
it.getTitle()
@@ -128,10 +135,12 @@
private val onSeedingComplete = Consumer<Boolean> {
accepted ->
if (accepted) {
- selectedStructure = controlsController.get().getFavorites().maxByOrNull {
+ selectedItem = controlsController.get().getFavorites().maxByOrNull {
it.controls.size
- } ?: EMPTY_STRUCTURE
- updatePreferences(selectedStructure)
+ }?.let {
+ SelectedItem.StructureItem(it)
+ } ?: SelectedItem.EMPTY_SELECTION
+ updatePreferences(selectedItem)
}
reload(parent)
}
@@ -139,6 +148,10 @@
private lateinit var activityContext: Context
private lateinit var listingCallback: ControlsListingController.ControlsListingCallback
+ init {
+ dumpManager.registerDumpable(javaClass.name, this)
+ }
+
private fun createCallback(
onResult: (List<SelectionItem>) -> Unit
): ControlsListingController.ControlsListingCallback {
@@ -146,7 +159,15 @@
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
val lastItems = serviceInfos.map {
val uid = it.serviceInfo.applicationInfo.uid
- SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName, uid)
+
+ SelectionItem(
+ it.loadLabel(),
+ "",
+ it.loadIcon(),
+ it.componentName,
+ uid,
+ it.panelActivity
+ )
}
uiExecutor.execute {
parent.removeAllViews()
@@ -160,11 +181,13 @@
override fun resolveActivity(): Class<*> {
val allStructures = controlsController.get().getFavorites()
- val selectedStructure = getPreferredStructure(allStructures)
+ val selected = getPreferredSelectedItem(allStructures)
+ val anyPanels = controlsListingController.get().getCurrentServices()
+ .none { it.panelActivity != null }
return if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
ControlsActivity::class.java
- } else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) {
+ } else if (!selected.hasControls && allStructures.size <= 1 && !anyPanels) {
ControlsProviderSelectorActivity::class.java
} else {
ControlsActivity::class.java
@@ -186,31 +209,49 @@
controlActionCoordinator.activityContext = activityContext
allStructures = controlsController.get().getFavorites()
- selectedStructure = getPreferredStructure(allStructures)
+ selectedItem = getPreferredSelectedItem(allStructures)
if (controlsController.get().addSeedingFavoritesCallback(onSeedingComplete)) {
listingCallback = createCallback(::showSeedingView)
- } else if (selectedStructure.controls.isEmpty() && allStructures.size <= 1) {
+ } else if (
+ selectedItem !is SelectedItem.PanelItem &&
+ !selectedItem.hasControls &&
+ allStructures.size <= 1
+ ) {
// only show initial view if there are really no favorites across any structure
- listingCallback = createCallback(::showInitialSetupView)
+ listingCallback = createCallback(::initialView)
} else {
- selectedStructure.controls.map {
- ControlWithState(selectedStructure.componentName, it, null)
- }.associateByTo(controlsById) {
- ControlKey(selectedStructure.componentName, it.ci.controlId)
+ val selected = selectedItem
+ if (selected is SelectedItem.StructureItem) {
+ selected.structure.controls.map {
+ ControlWithState(selected.structure.componentName, it, null)
+ }.associateByTo(controlsById) {
+ ControlKey(selected.structure.componentName, it.ci.controlId)
+ }
+ controlsController.get().subscribeToFavorites(selected.structure)
}
listingCallback = createCallback(::showControlsView)
- controlsController.get().subscribeToFavorites(selectedStructure)
}
controlsListingController.get().addCallback(listingCallback)
}
+ private fun initialView(items: List<SelectionItem>) {
+ if (items.any { it.isPanel }) {
+ // We have at least a panel, so we'll end up showing that.
+ showControlsView(items)
+ } else {
+ showInitialSetupView(items)
+ }
+ }
+
private fun reload(parent: ViewGroup) {
if (hidden) return
controlsListingController.get().removeCallback(listingCallback)
controlsController.get().unsubscribe()
+ taskViewController?.dismiss()
+ taskViewController = null
val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f)
fadeAnim.setInterpolator(AccelerateInterpolator(1.0f))
@@ -290,27 +331,90 @@
private fun showControlsView(items: List<SelectionItem>) {
controlViewsById.clear()
- val itemsByComponent = items.associateBy { it.componentName }
- val itemsWithStructure = mutableListOf<SelectionItem>()
- allStructures.mapNotNullTo(itemsWithStructure) {
+ val (panels, structures) = items.partition { it.isPanel }
+ val panelComponents = panels.map { it.componentName }.toSet()
+
+ val itemsByComponent = structures.associateBy { it.componentName }
+ .filterNot { it.key in panelComponents }
+ val panelsAndStructures = mutableListOf<SelectionItem>()
+ allStructures.mapNotNullTo(panelsAndStructures) {
itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
}
- itemsWithStructure.sortWith(localeComparator)
+ panelsAndStructures.addAll(panels)
- val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
+ panelsAndStructures.sortWith(localeComparator)
- controlsMetricsLogger.refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
+ lastSelections = panelsAndStructures
- createListView(selectionItem)
- createDropDown(itemsWithStructure, selectionItem)
+ val selectionItem = findSelectionItem(selectedItem, panelsAndStructures)
+ ?: if (panels.isNotEmpty()) {
+ // If we couldn't find a good selected item, but there's at least one panel,
+ // show a panel.
+ panels[0]
+ } else {
+ items[0]
+ }
+
+ maybeUpdateSelectedItem(selectionItem)
+
+ createControlsSpaceFrame()
+
+ if (taskViewFactory.isPresent && selectionItem.isPanel) {
+ createPanelView(selectionItem.panelComponentName!!)
+ } else if (!selectionItem.isPanel) {
+ controlsMetricsLogger
+ .refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
+ createListView(selectionItem)
+ } else {
+ Log.w(ControlsUiController.TAG, "Not TaskViewFactory to display panel $selectionItem")
+ }
+
+ createDropDown(panelsAndStructures, selectionItem)
createMenu()
}
- private fun createMenu() {
- val items = arrayOf(
- context.resources.getString(R.string.controls_menu_add),
- context.resources.getString(R.string.controls_menu_edit)
+ private fun createPanelView(componentName: ComponentName) {
+ val pendingIntent = PendingIntent.getActivity(
+ context,
+ 0,
+ Intent().setComponent(componentName),
+ PendingIntent.FLAG_IMMUTABLE
)
+
+ parent.requireViewById<View>(R.id.controls_scroll_view).visibility = View.GONE
+ val container = parent.requireViewById<FrameLayout>(R.id.controls_panel)
+ container.visibility = View.VISIBLE
+ container.post {
+ taskViewFactory.get().create(activityContext, uiExecutor) { taskView ->
+ taskViewController = PanelTaskViewController(
+ activityContext,
+ uiExecutor,
+ pendingIntent,
+ taskView,
+ onDismiss::run
+ ).also {
+ container.addView(taskView)
+ it.launchTaskView()
+ }
+ }
+ }
+ }
+
+ private fun createMenu() {
+ val isPanel = selectedItem is SelectedItem.PanelItem
+ val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
+ ?: EMPTY_STRUCTURE
+
+ val items = if (isPanel) {
+ arrayOf(
+ context.resources.getString(R.string.controls_menu_add),
+ )
+ } else {
+ arrayOf(
+ context.resources.getString(R.string.controls_menu_add),
+ context.resources.getString(R.string.controls_menu_edit)
+ )
+ }
var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
val anchor = parent.requireViewById<ImageView>(R.id.controls_more)
@@ -331,7 +435,13 @@
) {
when (pos) {
// 0: Add Control
- 0 -> startFavoritingActivity(selectedStructure)
+ 0 -> {
+ if (isPanel) {
+ startProviderSelectorActivity()
+ } else {
+ startFavoritingActivity(selectedStructure)
+ }
+ }
// 1: Edit controls
1 -> startEditingActivity(selectedStructure)
}
@@ -353,6 +463,9 @@
addAll(items)
}
+ val iconSize = context.resources
+ .getDimensionPixelSize(R.dimen.controls_header_app_icon_size)
+
/*
* Default spinner widget does not work with the window type required
* for this dialog. Use a textView with the ListPopupWindow to achieve
@@ -363,14 +476,21 @@
// override the default color on the dropdown drawable
(getBackground() as LayerDrawable).getDrawable(0)
.setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
- }
-
- if (items.size == 1) {
- spinner.setBackground(null)
- return
+ selected.icon.setBounds(0, 0, iconSize, iconSize)
+ compoundDrawablePadding = (iconSize / 2.4f).toInt()
+ setCompoundDrawablesRelative(selected.icon, null, null, null)
}
val anchor = parent.requireViewById<ViewGroup>(R.id.controls_header)
+ if (items.size == 1) {
+ spinner.setBackground(null)
+ anchor.setOnClickListener(null)
+ return
+ } else {
+ spinner.background = parent.context.resources
+ .getDrawable(R.drawable.control_spinner_background)
+ }
+
anchor.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View) {
popup = GlobalActionsPopupMenu(
@@ -398,14 +518,20 @@
})
}
- private fun createListView(selected: SelectionItem) {
- val inflater = LayoutInflater.from(context)
+ private fun createControlsSpaceFrame() {
+ val inflater = LayoutInflater.from(activityContext)
inflater.inflate(R.layout.controls_with_favorites, parent, true)
parent.requireViewById<ImageView>(R.id.controls_close).apply {
setOnClickListener { _: View -> onDismiss.run() }
visibility = View.VISIBLE
}
+ }
+
+ private fun createListView(selected: SelectionItem) {
+ if (selectedItem !is SelectedItem.StructureItem) return
+ val selectedStructure = (selectedItem as SelectedItem.StructureItem).structure
+ val inflater = LayoutInflater.from(activityContext)
val maxColumns = ControlAdapter.findMaxColumns(activityContext.resources)
@@ -453,35 +579,51 @@
}
}
- override fun getPreferredStructure(structures: List<StructureInfo>): StructureInfo {
- if (structures.isEmpty()) return EMPTY_STRUCTURE
+ override fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem {
+ val sp = sharedPreferences
- val component = sharedPreferences.getString(PREF_COMPONENT, null)?.let {
+ val component = sp.getString(PREF_COMPONENT, null)?.let {
ComponentName.unflattenFromString(it)
} ?: EMPTY_COMPONENT
- val structure = sharedPreferences.getString(PREF_STRUCTURE, "")
-
- return structures.firstOrNull {
- component == it.componentName && structure == it.structure
- } ?: structures.get(0)
+ val name = sp.getString(PREF_STRUCTURE_OR_APP_NAME, "")!!
+ val isPanel = sp.getBoolean(PREF_IS_PANEL, false)
+ return if (isPanel) {
+ SelectedItem.PanelItem(name, component)
+ } else {
+ if (structures.isEmpty()) return SelectedItem.EMPTY_SELECTION
+ SelectedItem.StructureItem(structures.firstOrNull {
+ component == it.componentName && name == it.structure
+ } ?: structures.get(0))
+ }
}
- private fun updatePreferences(si: StructureInfo) {
- if (si == EMPTY_STRUCTURE) return
+ private fun updatePreferences(si: SelectedItem) {
sharedPreferences.edit()
- .putString(PREF_COMPONENT, si.componentName.flattenToString())
- .putString(PREF_STRUCTURE, si.structure.toString())
- .commit()
+ .putString(PREF_COMPONENT, si.componentName.flattenToString())
+ .putString(PREF_STRUCTURE_OR_APP_NAME, si.name.toString())
+ .putBoolean(PREF_IS_PANEL, si is SelectedItem.PanelItem)
+ .commit()
+ }
+
+ private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
+ val newSelection = if (item.isPanel) {
+ SelectedItem.PanelItem(item.appName, item.componentName)
+ } else {
+ SelectedItem.StructureItem(allStructures.firstOrNull {
+ it.structure == item.structure && it.componentName == item.componentName
+ } ?: EMPTY_STRUCTURE)
+ }
+ return if (newSelection != selectedItem ) {
+ selectedItem = newSelection
+ updatePreferences(selectedItem)
+ true
+ } else {
+ false
+ }
}
private fun switchAppOrStructure(item: SelectionItem) {
- val newSelection = allStructures.first {
- it.structure == item.structure && it.componentName == item.componentName
- }
-
- if (newSelection != selectedStructure) {
- selectedStructure = newSelection
- updatePreferences(selectedStructure)
+ if (maybeUpdateSelectedItem(item)) {
reload(parent)
}
}
@@ -505,6 +647,8 @@
closeDialogs(true)
controlsController.get().unsubscribe()
+ taskViewController?.dismiss()
+ taskViewController = null
parent.removeAllViews()
controlsById.clear()
@@ -545,20 +689,46 @@
return row
}
- private fun findSelectionItem(si: StructureInfo, items: List<SelectionItem>): SelectionItem? =
- items.firstOrNull {
- it.componentName == si.componentName && it.structure == si.structure
+ private fun findSelectionItem(si: SelectedItem, items: List<SelectionItem>): SelectionItem? =
+ items.firstOrNull { it.matches(si) }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("ControlsUiControllerImpl:")
+ pw.asIndenting().indentIfPossible {
+ println("hidden: $hidden")
+ println("selectedItem: $selectedItem")
+ println("lastSelections: $lastSelections")
}
+ }
}
-private data class SelectionItem(
+@VisibleForTesting
+internal data class SelectionItem(
val appName: CharSequence,
val structure: CharSequence,
val icon: Drawable,
val componentName: ComponentName,
- val uid: Int
+ val uid: Int,
+ val panelComponentName: ComponentName?
) {
fun getTitle() = if (structure.isEmpty()) { appName } else { structure }
+
+ val isPanel: Boolean = panelComponentName != null
+
+ fun matches(selectedItem: SelectedItem): Boolean {
+ if (componentName != selectedItem.componentName) {
+ // Not the same component so they are not the same.
+ return false
+ }
+ if (isPanel || selectedItem is SelectedItem.PanelItem) {
+ // As they have the same component, if [this.isPanel] then we may be migrating from
+ // device controls API into panel. Want this to match, even if the selectedItem is not
+ // a panel. We don't want to match on app name because that can change with locale.
+ return true
+ }
+ // Return true if we find a structure with the correct name
+ return structure == (selectedItem as SelectedItem.StructureItem).structure.structure
+ }
}
private class ItemAdapter(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
new file mode 100644
index 0000000..7143be2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls.ui
+
+import android.app.ActivityOptions
+import android.app.ActivityTaskManager
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.util.boundsOnScreen
+import com.android.wm.shell.TaskView
+import java.util.concurrent.Executor
+
+class PanelTaskViewController(
+ private val activityContext: Context,
+ private val uiExecutor: Executor,
+ private val pendingIntent: PendingIntent,
+ private val taskView: TaskView,
+ private val hide: () -> Unit = {}
+) {
+
+ private var detailTaskId = INVALID_TASK_ID
+
+ private val fillInIntent =
+ Intent().apply {
+ // Apply flags to make behaviour match documentLaunchMode=always.
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+ }
+
+ private fun removeDetailTask() {
+ if (detailTaskId == INVALID_TASK_ID) return
+ ActivityTaskManager.getInstance().removeTask(detailTaskId)
+ detailTaskId = INVALID_TASK_ID
+ }
+
+ private val stateCallback =
+ object : TaskView.Listener {
+ override fun onInitialized() {
+
+ val options =
+ ActivityOptions.makeCustomAnimation(
+ activityContext,
+ 0 /* enterResId */,
+ 0 /* exitResId */
+ )
+ options.taskAlwaysOnTop = true
+
+ taskView.post {
+ taskView.startActivity(
+ pendingIntent,
+ fillInIntent,
+ options,
+ taskView.boundsOnScreen
+ )
+ }
+ }
+
+ override fun onTaskRemovalStarted(taskId: Int) {
+ detailTaskId = INVALID_TASK_ID
+ dismiss()
+ }
+
+ override fun onTaskCreated(taskId: Int, name: ComponentName?) {
+ detailTaskId = taskId
+ }
+
+ override fun onReleased() {
+ removeDetailTask()
+ }
+
+ override fun onBackPressedOnTaskRoot(taskId: Int) {
+ dismiss()
+ hide()
+ }
+ }
+
+ fun dismiss() {
+ taskView.release()
+ }
+
+ fun launchTaskView() {
+ taskView.setListener(uiExecutor, stateCallback)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index d60a222..3d8e4cb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -19,7 +19,6 @@
import android.content.BroadcastReceiver;
import com.android.systemui.GuestResetOrExitSessionReceiver;
-import com.android.systemui.GuestResumeSessionReceiver;
import com.android.systemui.media.dialog.MediaOutputDialogReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
@@ -106,15 +105,6 @@
*/
@Binds
@IntoMap
- @ClassKey(GuestResumeSessionReceiver.class)
- public abstract BroadcastReceiver bindGuestResumeSessionReceiver(
- GuestResumeSessionReceiver broadcastReceiver);
-
- /**
- *
- */
- @Binds
- @IntoMap
@ClassKey(GuestResetOrExitSessionReceiver.class)
public abstract BroadcastReceiver bindGuestResetOrExitSessionReceiver(
GuestResetOrExitSessionReceiver broadcastReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 139a8b7..43dfb5a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.dagger;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
@@ -54,6 +55,7 @@
import android.hardware.display.DisplayManager;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.input.InputManager;
import android.media.AudioManager;
import android.media.IAudioService;
import android.media.MediaRouter2Manager;
@@ -68,6 +70,7 @@
import android.os.ServiceManager;
import android.os.UserManager;
import android.os.Vibrator;
+import android.os.storage.StorageManager;
import android.permission.PermissionManager;
import android.safetycenter.SafetyCenterManager;
import android.service.dreams.DreamService;
@@ -109,6 +112,7 @@
/**
* Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
*/
+@SuppressLint("NonInjectedService")
@Module
public class FrameworkServicesModule {
@Provides
@@ -281,6 +285,12 @@
@Provides
@Singleton
+ static InputManager provideInputManager(Context context) {
+ return context.getSystemService(InputManager.class);
+ }
+
+ @Provides
+ @Singleton
static InputMethodManager provideInputMethodManager(Context context) {
return context.getSystemService(InputMethodManager.class);
}
@@ -462,7 +472,13 @@
@Provides
@Singleton
- static SubscriptionManager provideSubcriptionManager(Context context) {
+ static StorageManager provideStorageManager(Context context) {
+ return context.getSystemService(StorageManager.class);
+ }
+
+ @Provides
+ @Singleton
+ static SubscriptionManager provideSubscriptionManager(Context context) {
return context.getSystemService(SubscriptionManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index fe89c9a..9e8c0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -21,24 +21,21 @@
import com.android.systemui.dagger.qualifiers.InstrumentationTest;
import com.android.systemui.util.InitializationChecker;
-import javax.inject.Singleton;
-
import dagger.BindsInstance;
-import dagger.Component;
/**
* Base root component for Dagger injection.
*
+ * This class is not actually annotated as a Dagger component, since it is not used directly as one.
+ * Doing so generates unnecessary code bloat.
+ *
* See {@link ReferenceGlobalRootComponent} for the one actually used by AOSP.
*/
-@Singleton
-@Component(modules = {GlobalModule.class})
public interface GlobalRootComponent {
/**
* Builder for a GlobalRootComponent.
*/
- @Component.Builder
interface Builder {
@BindsInstance
Builder context(Context context);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index 7ab36e8..b30e0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -16,6 +16,8 @@
package com.android.systemui.dagger;
+import com.android.systemui.keyguard.KeyguardQuickAffordanceProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import dagger.Subcomponent;
@@ -27,6 +29,7 @@
@Subcomponent(modules = {
DefaultComponentBinder.class,
DependencyProvider.class,
+ NotificationInsetsModule.class,
QsFrameTranslateModule.class,
SystemUIBinder.class,
SystemUIModule.class,
@@ -42,4 +45,9 @@
interface Builder extends SysUIComponent.Builder {
ReferenceSysUIComponent build();
}
+
+ /**
+ * Member injection into the supplied argument.
+ */
+ void inject(KeyguardQuickAffordanceProvider keyguardQuickAffordanceProvider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index a14b0ee..6dc4f5c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -28,6 +28,7 @@
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.people.PeopleProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.FoldStateLogger;
@@ -65,6 +66,7 @@
@Subcomponent(modules = {
DefaultComponentBinder.class,
DependencyProvider.class,
+ NotificationInsetsModule.class,
QsFrameTranslateModule.class,
SystemUIBinder.class,
SystemUIModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 721c0ba..0fbe0ac 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -36,6 +36,7 @@
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
import com.android.systemui.power.PowerUI
+import com.android.systemui.reardisplay.RearDisplayDialogController
import com.android.systemui.recents.Recents
import com.android.systemui.settings.dagger.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
@@ -243,4 +244,11 @@
@IntoMap
@ClassKey(ChipbarCoordinator::class)
abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
+
+
+ /** Inject into RearDisplayDialogController) */
+ @Binds
+ @IntoMap
+ @ClassKey(RearDisplayDialogController::class)
+ abstract fun bindRearDisplayDialogController(sysui: RearDisplayDialogController): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 95919c6..b8e6673 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -33,6 +33,7 @@
import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
import com.android.systemui.biometrics.dagger.BiometricsModule;
+import com.android.systemui.biometrics.dagger.UdfpsModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.controls.dagger.ControlsModule;
@@ -156,6 +157,7 @@
TelephonyRepositoryModule.class,
TemporaryDisplayModule.class,
TunerModule.class,
+ UdfpsModule.class,
UserModule.class,
UtilModule.class,
NoteTaskModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
index d537d4b..000bbe6 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -54,6 +54,9 @@
private val receiverMap: Map<String, MutableList<DemoMode>>
init {
+ // Don't persist demo mode across restarts.
+ requestFinishDemoMode()
+
val m = mutableMapOf<String, MutableList<DemoMode>>()
DemoMode.COMMANDS.map { command ->
m.put(command, mutableListOf())
@@ -74,7 +77,6 @@
// content changes to know if the setting turned on or off
tracker.startTracking()
- // TODO: We should probably exit demo mode if we booted up with it on
isInDemoMode = tracker.isInDemoMode
val demoFilter = IntentFilter()
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index b69afeb..0c14ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -133,9 +133,9 @@
/**
* Appends fling event to the logs
*/
- public void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
+ public void traceFling(boolean expand, boolean aboveThreshold,
boolean screenOnFromTouch) {
- mLogger.logFling(expand, aboveThreshold, thresholdNeeded, screenOnFromTouch);
+ mLogger.logFling(expand, aboveThreshold, screenOnFromTouch);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 18c8e01..b5dbe21 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -96,13 +96,11 @@
fun logFling(
expand: Boolean,
aboveThreshold: Boolean,
- thresholdNeeded: Boolean,
screenOnFromTouch: Boolean
) {
buffer.log(TAG, DEBUG, {
bool1 = expand
bool2 = aboveThreshold
- bool3 = thresholdNeeded
bool4 = screenOnFromTouch
}, {
"Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " +
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 9a091e7..833ff3f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -22,7 +22,6 @@
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
import android.annotation.AnyThread;
-import android.app.ActivityManager;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorManager;
@@ -47,6 +46,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -96,6 +96,7 @@
private final SecureSettings mSecureSettings;
private final DevicePostureController mDevicePostureController;
private final AuthController mAuthController;
+ private final UserTracker mUserTracker;
private final boolean mScreenOffUdfpsEnabled;
// Sensors
@@ -149,7 +150,8 @@
ProximitySensor proximitySensor,
SecureSettings secureSettings,
AuthController authController,
- DevicePostureController devicePostureController
+ DevicePostureController devicePostureController,
+ UserTracker userTracker
) {
mSensorManager = sensorManager;
mConfig = config;
@@ -167,6 +169,7 @@
mDevicePostureController = devicePostureController;
mDevicePosture = mDevicePostureController.getDevicePosture();
mAuthController = authController;
+ mUserTracker = userTracker;
mUdfpsEnrolled =
mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
@@ -438,7 +441,7 @@
private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
- if (userId != ActivityManager.getCurrentUser()) {
+ if (userId != mUserTracker.getUserId()) {
return;
}
for (TriggerSensor s : mTriggerSensors) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index e8d7e46..f8bd1e7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -27,7 +27,7 @@
import com.android.systemui.plugins.DozeServicePlugin;
import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
new file mode 100644
index 0000000..12ceedd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTransitionListener.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.doze
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.CallbackController
+import javax.inject.Inject
+
+/** Receives doze transition events, and passes those events to registered callbacks. */
+@SysUISingleton
+class DozeTransitionListener @Inject constructor() :
+ DozeMachine.Part, CallbackController<DozeTransitionCallback> {
+ val callbacks = mutableSetOf<DozeTransitionCallback>()
+ var oldState = DozeMachine.State.UNINITIALIZED
+ var newState = DozeMachine.State.UNINITIALIZED
+
+ override fun transitionTo(oldState: DozeMachine.State, newState: DozeMachine.State) {
+ this.oldState = oldState
+ this.newState = newState
+ callbacks.forEach { it.onDozeTransition(oldState, newState) }
+ }
+
+ override fun addCallback(callback: DozeTransitionCallback) {
+ callbacks.add(callback)
+ }
+
+ override fun removeCallback(callback: DozeTransitionCallback) {
+ callbacks.remove(callback)
+ }
+}
+
+interface DozeTransitionCallback {
+ fun onDozeTransition(oldState: DozeMachine.State, newState: DozeMachine.State)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 32cb1c0..5daf1ce 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -29,12 +29,13 @@
import android.content.IntentFilter;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.text.format.Formatter;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.view.Display;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEvent;
@@ -45,6 +46,7 @@
import com.android.systemui.doze.DozeMachine.State;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.log.SessionTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -99,6 +101,7 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final AuthController mAuthController;
private final KeyguardStateController mKeyguardStateController;
+ private final UserTracker mUserTracker;
private final UiEventLogger mUiEventLogger;
private long mNotificationPulseTime;
@@ -109,6 +112,14 @@
private boolean mWantTouchScreenSensors;
private boolean mWantSensors;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mDozeSensors.onUserSwitched();
+ }
+ };
+
@VisibleForTesting
public enum DozingUpdateUiEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "Dozing updated due to notification.")
@@ -188,7 +199,8 @@
UiEventLogger uiEventLogger,
SessionTracker sessionTracker,
KeyguardStateController keyguardStateController,
- DevicePostureController devicePostureController) {
+ DevicePostureController devicePostureController,
+ UserTracker userTracker) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -200,7 +212,7 @@
mDozeSensors = new DozeSensors(mSensorManager, dozeParameters,
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
- secureSettings, authController, devicePostureController);
+ secureSettings, authController, devicePostureController, userTracker);
mDockManager = dockManager;
mProxCheck = proxCheck;
mDozeLog = dozeLog;
@@ -208,6 +220,7 @@
mAuthController = authController;
mUiEventLogger = uiEventLogger;
mKeyguardStateController = keyguardStateController;
+ mUserTracker = userTracker;
}
@Override
@@ -232,7 +245,7 @@
return;
}
mNotificationPulseTime = SystemClock.elapsedRealtime();
- if (!mConfig.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
+ if (!mConfig.pulseOnNotificationEnabled(mUserTracker.getUserId())) {
runIfNotNull(onPulseSuppressedListener);
mDozeLog.tracePulseDropped("pulseOnNotificationsDisabled");
return;
@@ -488,12 +501,14 @@
mBroadcastReceiver.register(mBroadcastDispatcher);
mDockManager.addListener(mDockEventListener);
mDozeHost.addCallback(mHostCallback);
+ mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
}
private void unregisterCallbacks() {
mBroadcastReceiver.unregister(mBroadcastDispatcher);
mDozeHost.removeCallback(mHostCallback);
mDockManager.removeListener(mDockEventListener);
+ mUserTracker.removeCallback(mUserChangedCallback);
}
private void stopListeningToAllTriggers() {
@@ -618,9 +633,6 @@
requestPulse(DozeLog.PULSE_REASON_INTENT, false, /* performedProxCheck */
null /* onPulseSuppressedListener */);
}
- if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
- mDozeSensors.onUserSwitched();
- }
}
public void register(BroadcastDispatcher broadcastDispatcher) {
@@ -628,7 +640,6 @@
return;
}
IntentFilter filter = new IntentFilter(PULSE_ACTION);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
broadcastDispatcher.registerReceiver(this, filter);
mRegistered = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
index 98cd2d7..069344f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java
@@ -35,6 +35,7 @@
import com.android.systemui.doze.DozeSensors;
import com.android.systemui.doze.DozeSuppressor;
import com.android.systemui.doze.DozeSuspendScreenStatePreventingAdapter;
+import com.android.systemui.doze.DozeTransitionListener;
import com.android.systemui.doze.DozeTriggers;
import com.android.systemui.doze.DozeUi;
import com.android.systemui.doze.DozeWallpaperState;
@@ -83,7 +84,7 @@
DozeUi dozeUi, DozeScreenState dozeScreenState,
DozeScreenBrightness dozeScreenBrightness, DozeWallpaperState dozeWallpaperState,
DozeDockHandler dozeDockHandler, DozeAuthRemover dozeAuthRemover,
- DozeSuppressor dozeSuppressor) {
+ DozeSuppressor dozeSuppressor, DozeTransitionListener dozeTransitionListener) {
return new DozeMachine.Part[]{
dozePauser,
dozeFalsingManagerAdapter,
@@ -94,7 +95,8 @@
dozeWallpaperState,
dozeDockHandler,
dozeAuthRemover,
- dozeSuppressor
+ dozeSuppressor,
+ dozeTransitionListener
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index d8dd6a2..0087c84 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -17,17 +17,19 @@
package com.android.systemui.dreams
import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.view.View
+import android.view.animation.Interpolator
+import androidx.annotation.FloatRange
import androidx.core.animation.doOnEnd
import com.android.systemui.animation.Interpolators
import com.android.systemui.dreams.complication.ComplicationHostViewController
import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
import com.android.systemui.statusbar.BlurUtils
-import java.util.function.Consumer
+import com.android.systemui.statusbar.CrossFadeHelper
import javax.inject.Inject
import javax.inject.Named
@@ -40,108 +42,239 @@
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
- private val mDreamInBlurAnimDuration: Int,
- @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int,
+ private val mDreamInBlurAnimDurationMs: Long,
+ @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY)
+ private val mDreamInBlurAnimDelayMs: Long,
@Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
- private val mDreamInComplicationsAnimDuration: Int,
+ private val mDreamInComplicationsAnimDurationMs: Long,
@Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
- private val mDreamInTopComplicationsAnimDelay: Int,
+ private val mDreamInTopComplicationsAnimDelayMs: Long,
@Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
- private val mDreamInBottomComplicationsAnimDelay: Int
+ private val mDreamInBottomComplicationsAnimDelayMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
+ private val mDreamOutTranslationYDistance: Int,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
+ private val mDreamOutTranslationYDurationMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
+ private val mDreamOutTranslationYDelayBottomMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
+ private val mDreamOutTranslationYDelayTopMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DURATION) private val mDreamOutAlphaDurationMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_BOTTOM)
+ private val mDreamOutAlphaDelayBottomMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_TOP) private val mDreamOutAlphaDelayTopMs: Long,
+ @Named(DreamOverlayModule.DREAM_OUT_BLUR_DURATION) private val mDreamOutBlurDurationMs: Long
) {
- var mEntryAnimations: AnimatorSet? = null
+ private var mAnimator: Animator? = null
+
+ /**
+ * Store the current alphas at the various positions. This is so that we may resume an animation
+ * at the current alpha.
+ */
+ private var mCurrentAlphaAtPosition = mutableMapOf<Int, Float>()
+
+ @FloatRange(from = 0.0, to = 1.0) private var mBlurProgress: Float = 0f
/** Starts the dream content and dream overlay entry animations. */
- fun startEntryAnimations(view: View) {
- cancelRunningEntryAnimations()
+ @JvmOverloads
+ fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
+ cancelAnimations()
- mEntryAnimations = AnimatorSet()
- mEntryAnimations?.apply {
- playTogether(
- buildDreamInBlurAnimator(view),
- buildDreamInTopComplicationsAnimator(),
- buildDreamInBottomComplicationsAnimator()
- )
- doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) }
- start()
- }
+ mAnimator =
+ animatorBuilder().apply {
+ playTogether(
+ blurAnimator(
+ view = view,
+ from = 1f,
+ to = 0f,
+ durationMs = mDreamInBlurAnimDurationMs,
+ delayMs = mDreamInBlurAnimDelayMs
+ ),
+ alphaAnimator(
+ from = 0f,
+ to = 1f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = mDreamInTopComplicationsAnimDelayMs,
+ position = ComplicationLayoutParams.POSITION_TOP
+ ),
+ alphaAnimator(
+ from = 0f,
+ to = 1f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = mDreamInBottomComplicationsAnimDelayMs,
+ position = ComplicationLayoutParams.POSITION_BOTTOM
+ )
+ )
+ doOnEnd {
+ mAnimator = null
+ mOverlayStateController.setEntryAnimationsFinished(true)
+ }
+ start()
+ }
+ }
+
+ /** Starts the dream content and dream overlay exit animations. */
+ @JvmOverloads
+ fun startExitAnimations(
+ view: View,
+ doneCallback: () -> Unit,
+ animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
+ ) {
+ cancelAnimations()
+
+ mAnimator =
+ animatorBuilder().apply {
+ playTogether(
+ blurAnimator(
+ view = view,
+ // Start the blurring wherever the entry animation ended, in
+ // case it was cancelled early.
+ from = mBlurProgress,
+ to = 1f,
+ durationMs = mDreamOutBlurDurationMs
+ ),
+ translationYAnimator(
+ from = 0f,
+ to = mDreamOutTranslationYDistance.toFloat(),
+ durationMs = mDreamOutTranslationYDurationMs,
+ delayMs = mDreamOutTranslationYDelayBottomMs,
+ position = ComplicationLayoutParams.POSITION_BOTTOM,
+ animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+ ),
+ translationYAnimator(
+ from = 0f,
+ to = mDreamOutTranslationYDistance.toFloat(),
+ durationMs = mDreamOutTranslationYDurationMs,
+ delayMs = mDreamOutTranslationYDelayTopMs,
+ position = ComplicationLayoutParams.POSITION_TOP,
+ animInterpolator = Interpolators.EMPHASIZED_ACCELERATE
+ ),
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = ComplicationLayoutParams.POSITION_BOTTOM,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamOutAlphaDurationMs,
+ delayMs = mDreamOutAlphaDelayBottomMs,
+ position = ComplicationLayoutParams.POSITION_BOTTOM
+ ),
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = ComplicationLayoutParams.POSITION_TOP,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamOutAlphaDurationMs,
+ delayMs = mDreamOutAlphaDelayTopMs,
+ position = ComplicationLayoutParams.POSITION_TOP
+ )
+ )
+ doOnEnd {
+ mAnimator = null
+ mOverlayStateController.setExitAnimationsRunning(false)
+ doneCallback()
+ }
+ start()
+ }
+ mOverlayStateController.setExitAnimationsRunning(true)
}
/** Cancels the dream content and dream overlay animations, if they're currently running. */
- fun cancelRunningEntryAnimations() {
- if (mEntryAnimations?.isRunning == true) {
- mEntryAnimations?.cancel()
- }
- mEntryAnimations = null
+ fun cancelAnimations() {
+ mAnimator =
+ mAnimator?.let {
+ it.cancel()
+ null
+ }
}
- private fun buildDreamInBlurAnimator(view: View): Animator {
- return ValueAnimator.ofFloat(1f, 0f).apply {
- duration = mDreamInBlurAnimDuration.toLong()
- startDelay = mDreamInBlurAnimDelay.toLong()
+ private fun blurAnimator(
+ view: View,
+ from: Float,
+ to: Float,
+ durationMs: Long,
+ delayMs: Long = 0
+ ): Animator {
+ return ValueAnimator.ofFloat(from, to).apply {
+ duration = durationMs
+ startDelay = delayMs
interpolator = Interpolators.LINEAR
addUpdateListener { animator: ValueAnimator ->
+ mBlurProgress = animator.animatedValue as Float
mBlurUtils.applyBlur(
- view.viewRootImpl,
- mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(),
- false /*opaque*/
+ viewRootImpl = view.viewRootImpl,
+ radius = mBlurUtils.blurRadiusOfRatio(mBlurProgress).toInt(),
+ opaque = false
)
}
}
}
- private fun buildDreamInTopComplicationsAnimator(): Animator {
- return ValueAnimator.ofFloat(0f, 1f).apply {
- duration = mDreamInComplicationsAnimDuration.toLong()
- startDelay = mDreamInTopComplicationsAnimDelay.toLong()
+ private fun alphaAnimator(
+ from: Float,
+ to: Float,
+ durationMs: Long,
+ delayMs: Long,
+ @Position position: Int
+ ): Animator {
+ return ValueAnimator.ofFloat(from, to).apply {
+ duration = durationMs
+ startDelay = delayMs
interpolator = Interpolators.LINEAR
addUpdateListener { va: ValueAnimator ->
- setTopElementsAlpha(va.animatedValue as Float)
+ setElementsAlphaAtPosition(
+ alpha = va.animatedValue as Float,
+ position = position,
+ fadingOut = to < from
+ )
}
}
}
- private fun buildDreamInBottomComplicationsAnimator(): Animator {
- return ValueAnimator.ofFloat(0f, 1f).apply {
- duration = mDreamInComplicationsAnimDuration.toLong()
- startDelay = mDreamInBottomComplicationsAnimDelay.toLong()
- interpolator = Interpolators.LINEAR
+ private fun translationYAnimator(
+ from: Float,
+ to: Float,
+ durationMs: Long,
+ delayMs: Long,
+ @Position position: Int,
+ animInterpolator: Interpolator
+ ): Animator {
+ return ValueAnimator.ofFloat(from, to).apply {
+ duration = durationMs
+ startDelay = delayMs
+ interpolator = animInterpolator
addUpdateListener { va: ValueAnimator ->
- setBottomElementsAlpha(va.animatedValue as Float)
+ setElementsTranslationYAtPosition(va.animatedValue as Float, position)
}
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
- mComplicationHostViewController
- .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
- .forEach(Consumer { v: View -> v.visibility = View.VISIBLE })
- }
- }
- )
}
}
- /** Sets alpha of top complications and the status bar. */
- private fun setTopElementsAlpha(alpha: Float) {
- mComplicationHostViewController
- .getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP)
- .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
- mStatusBarViewController.setAlpha(alpha)
- }
-
- /** Sets alpha of bottom complications. */
- private fun setBottomElementsAlpha(alpha: Float) {
- mComplicationHostViewController
- .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
- .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
- }
-
- private fun setAlphaAndEnsureVisible(view: View, alpha: Float) {
- if (alpha > 0 && view.visibility != View.VISIBLE) {
- view.visibility = View.VISIBLE
+ /** Sets alpha of complications at the specified position. */
+ private fun setElementsAlphaAtPosition(alpha: Float, position: Int, fadingOut: Boolean) {
+ mCurrentAlphaAtPosition[position] = alpha
+ mComplicationHostViewController.getViewsAtPosition(position).forEach { view ->
+ if (fadingOut) {
+ CrossFadeHelper.fadeOut(view, 1 - alpha, /* remap= */ false)
+ } else {
+ CrossFadeHelper.fadeIn(view, alpha, /* remap= */ false)
+ }
}
+ if (position == ComplicationLayoutParams.POSITION_TOP) {
+ mStatusBarViewController.setFadeAmount(alpha, fadingOut)
+ }
+ }
- view.alpha = alpha
+ /** Sets y translation of complications at the specified position. */
+ private fun setElementsTranslationYAtPosition(translationY: Float, position: Int) {
+ mComplicationHostViewController.getViewsAtPosition(position).forEach { v ->
+ v.translationY = translationY
+ }
+ if (position == ComplicationLayoutParams.POSITION_TOP) {
+ mStatusBarViewController.setTranslationY(translationY)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 5c6d248..9d7ad30 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -29,6 +29,8 @@
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
@@ -42,6 +44,7 @@
import com.android.systemui.util.ViewController;
import java.util.Arrays;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -194,7 +197,7 @@
}
mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
- mDreamOverlayAnimationsController.cancelRunningEntryAnimations();
+ mDreamOverlayAnimationsController.cancelAnimations();
}
View getContainerView() {
@@ -251,4 +254,17 @@
: aboutToShowBouncerProgress(expansion + 0.03f));
return MathUtils.lerp(-mDreamOverlayMaxTranslationY, 0, fraction);
}
+
+ /**
+ * Handle the dream waking up and run any necessary animations.
+ *
+ * @param onAnimationEnd Callback to trigger once animations are finished.
+ * @param callbackExecutor Executor to execute the callback on.
+ */
+ public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) {
+ mDreamOverlayAnimationsController.startExitAnimations(mView, () -> {
+ callbackExecutor.execute(onAnimationEnd);
+ return null;
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 8542412..e76d5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -213,6 +213,15 @@
mLifecycleRegistry.setCurrentState(state);
}
+ @Override
+ public void onWakeUp(@NonNull Runnable onCompletedCallback) {
+ mExecutor.execute(() -> {
+ if (mDreamOverlayContainerViewController != null) {
+ mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
+ }
+ });
+ }
+
/**
* Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
* called from the main executing thread. The window attributes closely mirror those that are
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index e80d0be..5f942b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -52,6 +52,7 @@
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
+ public static final int STATE_DREAM_EXIT_ANIMATIONS_RUNNING = 1 << 3;
private static final int OP_CLEAR_STATE = 1;
private static final int OP_SET_STATE = 2;
@@ -211,6 +212,14 @@
return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
}
+ /**
+ * Returns whether the dream content and dream overlay exit animations are running.
+ * @return {@code true} if animations are running, {@code false} otherwise.
+ */
+ public boolean areExitAnimationsRunning() {
+ return containsState(STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
+ }
+
private boolean containsState(int state) {
return (mState & state) != 0;
}
@@ -257,6 +266,15 @@
}
/**
+ * Sets whether dream content and dream overlay exit animations are running.
+ * @param running {@code true} if exit animations are running, {@code false} otherwise.
+ */
+ public void setExitAnimationsRunning(boolean running) {
+ modifyState(running ? OP_SET_STATE : OP_CLEAR_STATE,
+ STATE_DREAM_EXIT_ANIMATIONS_RUNNING);
+ }
+
+ /**
* Returns the available complication types.
*/
@Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index d17fbe3..f1bb156 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -37,6 +37,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -217,18 +218,29 @@
}
/**
- * Sets alpha of the dream overlay status bar.
+ * Sets fade of the dream overlay status bar.
*
* No-op if the dream overlay status bar should not be shown.
*/
- protected void setAlpha(float alpha) {
+ protected void setFadeAmount(float fadeAmount, boolean fadingOut) {
updateVisibility();
if (mView.getVisibility() != View.VISIBLE) {
return;
}
- mView.setAlpha(alpha);
+ if (fadingOut) {
+ CrossFadeHelper.fadeOut(mView, 1 - fadeAmount, /* remap= */ false);
+ } else {
+ CrossFadeHelper.fadeIn(mView, fadeAmount, /* remap= */ false);
+ }
+ }
+
+ /**
+ * Sets the y translation of the dream overlay status bar.
+ */
+ public void setTranslationY(float translationY) {
+ mView.setTranslationY(translationY);
}
private boolean shouldShowStatusBar() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 41f5578..b07efdf 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -197,11 +197,11 @@
*/
interface VisibilityController {
/**
- * Called to set the visibility of all shown and future complications.
+ * Called to set the visibility of all shown and future complications. Changes in visibility
+ * will always be animated.
* @param visibility The desired future visibility.
- * @param animate whether the change should be animated.
*/
- void setVisibility(@View.Visibility int visibility, boolean animate);
+ void setVisibility(@View.Visibility int visibility);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index 5694f6d..46ce7a9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -21,12 +21,9 @@
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Constraints;
@@ -34,6 +31,7 @@
import com.android.systemui.R;
import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.touch.TouchInsetManager;
import java.util.ArrayList;
@@ -213,6 +211,19 @@
}
});
+ if (mLayoutParams.constraintSpecified()) {
+ switch (direction) {
+ case ComplicationLayoutParams.DIRECTION_START:
+ case ComplicationLayoutParams.DIRECTION_END:
+ params.matchConstraintMaxWidth = mLayoutParams.getConstraint();
+ break;
+ case ComplicationLayoutParams.DIRECTION_UP:
+ case ComplicationLayoutParams.DIRECTION_DOWN:
+ params.matchConstraintMaxHeight = mLayoutParams.getConstraint();
+ break;
+ }
+ }
+
mView.setLayoutParams(params);
}
@@ -479,7 +490,6 @@
private final TouchInsetManager.TouchInsetSession mSession;
private final int mFadeInDuration;
private final int mFadeOutDuration;
- private ViewPropertyAnimator mViewPropertyAnimator;
/** */
@Inject
@@ -496,26 +506,16 @@
}
@Override
- public void setVisibility(int visibility, boolean animate) {
- final boolean appearing = visibility == View.VISIBLE;
-
- if (mViewPropertyAnimator != null) {
- mViewPropertyAnimator.cancel();
+ public void setVisibility(int visibility) {
+ if (visibility == View.VISIBLE) {
+ CrossFadeHelper.fadeIn(mLayout, mFadeInDuration, /* delay= */ 0);
+ } else {
+ CrossFadeHelper.fadeOut(
+ mLayout,
+ mFadeOutDuration,
+ /* delay= */ 0,
+ /* endRunnable= */ null);
}
-
- if (appearing) {
- mLayout.setVisibility(View.VISIBLE);
- }
-
- mViewPropertyAnimator = mLayout.animate()
- .alpha(appearing ? 1f : 0f)
- .setDuration(appearing ? mFadeInDuration : mFadeOutDuration)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mLayout.setVisibility(visibility);
- }
- });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
index a21eb19..1755cb92 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutParams.java
@@ -38,7 +38,7 @@
POSITION_START,
})
- @interface Position {}
+ public @interface Position {}
/** Align view with the top of parent or bottom of preceding {@link Complication}. */
public static final int POSITION_TOP = 1 << 0;
/** Align view with the bottom of parent or top of preceding {@link Complication}. */
@@ -52,6 +52,7 @@
private static final int LAST_POSITION = POSITION_END;
private static final int MARGIN_UNSPECIFIED = 0xFFFFFFFF;
+ private static final int CONSTRAINT_UNSPECIFIED = 0xFFFFFFFF;
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "DIRECTION_" }, value = {
@@ -81,6 +82,8 @@
private final int mMargin;
+ private final int mConstraint;
+
private final boolean mSnapToGuide;
// Do not allow specifying opposite positions
@@ -110,7 +113,8 @@
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
@Direction int direction, int weight) {
- this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, false);
+ this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, CONSTRAINT_UNSPECIFIED,
+ false);
}
/**
@@ -127,7 +131,27 @@
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
@Direction int direction, int weight, int margin) {
- this(width, height, position, direction, weight, margin, false);
+ this(width, height, position, direction, weight, margin, CONSTRAINT_UNSPECIFIED, false);
+ }
+
+ /**
+ * Constructs a {@link ComplicationLayoutParams}.
+ * @param width The width {@link android.view.View.MeasureSpec} for the view.
+ * @param height The height {@link android.view.View.MeasureSpec} for the view.
+ * @param position The place within the parent container where the view should be positioned.
+ * @param direction The direction the view should be laid out from either the parent container
+ * or preceding view.
+ * @param weight The weight that should be considered for this view when compared to other
+ * views. This has an impact on the placement of the view but not the rendering of
+ * the view.
+ * @param margin The margin to apply between complications.
+ * @param constraint The max width or height the complication is allowed to spread, depending on
+ * its direction. For horizontal directions, this would be applied on width,
+ * and for vertical directions, height.
+ */
+ public ComplicationLayoutParams(int width, int height, @Position int position,
+ @Direction int direction, int weight, int margin, int constraint) {
+ this(width, height, position, direction, weight, margin, constraint, false);
}
/**
@@ -148,7 +172,8 @@
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
@Direction int direction, int weight, boolean snapToGuide) {
- this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, snapToGuide);
+ this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, CONSTRAINT_UNSPECIFIED,
+ snapToGuide);
}
/**
@@ -162,6 +187,9 @@
* views. This has an impact on the placement of the view but not the rendering of
* the view.
* @param margin The margin to apply between complications.
+ * @param constraint The max width or height the complication is allowed to spread, depending on
+ * its direction. For horizontal directions, this would be applied on width,
+ * and for vertical directions, height.
* @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
* will be automatically set to align with a predetermined guide for that
* side. For example, if the complication is aligned to the top end and
@@ -169,7 +197,7 @@
* from the end of the parent to the guide.
*/
public ComplicationLayoutParams(int width, int height, @Position int position,
- @Direction int direction, int weight, int margin, boolean snapToGuide) {
+ @Direction int direction, int weight, int margin, int constraint, boolean snapToGuide) {
super(width, height);
if (!validatePosition(position)) {
@@ -187,6 +215,8 @@
mMargin = margin;
+ mConstraint = constraint;
+
mSnapToGuide = snapToGuide;
}
@@ -199,6 +229,7 @@
mDirection = source.mDirection;
mWeight = source.mWeight;
mMargin = source.mMargin;
+ mConstraint = source.mConstraint;
mSnapToGuide = source.mSnapToGuide;
}
@@ -269,6 +300,21 @@
}
/**
+ * Returns whether the horizontal or vertical constraint has been specified.
+ */
+ public boolean constraintSpecified() {
+ return mConstraint != CONSTRAINT_UNSPECIFIED;
+ }
+
+ /**
+ * Returns the horizontal or vertical constraint of the complication, depending its direction.
+ * For horizontal directions, this is the max width, and for vertical directions, max height.
+ */
+ public int getConstraint() {
+ return mConstraint;
+ }
+
+ /**
* Returns whether the complication's dimension perpendicular to direction should be
* automatically set.
*/
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 cedd850a..ee00512 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java
@@ -32,7 +32,9 @@
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
+import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsActivity;
@@ -42,6 +44,8 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.util.ViewController;
+import java.util.List;
+
import javax.inject.Inject;
import javax.inject.Named;
@@ -76,16 +80,25 @@
private final DreamOverlayStateController mDreamOverlayStateController;
private final ControlsComponent mControlsComponent;
- private boolean mControlServicesAvailable = false;
+ private boolean mOverlayActive = false;
// Callback for when the home controls service availability changes.
private final ControlsListingController.ControlsListingCallback mControlsCallback =
- serviceInfos -> {
- boolean available = !serviceInfos.isEmpty();
+ services -> updateHomeControlsComplication();
- if (available != mControlServicesAvailable) {
- mControlServicesAvailable = available;
- updateComplicationAvailability();
+ private final DreamOverlayStateController.Callback mOverlayStateCallback =
+ new DreamOverlayStateController.Callback() {
+ @Override
+ public void onStateChanged() {
+ if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) {
+ return;
+ }
+
+ mOverlayActive = !mOverlayActive;
+
+ if (mOverlayActive) {
+ updateHomeControlsComplication();
+ }
}
};
@@ -102,18 +115,29 @@
public void start() {
mControlsComponent.getControlsListingController().ifPresent(
c -> c.addCallback(mControlsCallback));
+ mDreamOverlayStateController.addCallback(mOverlayStateCallback);
}
- private void updateComplicationAvailability() {
+ private void updateHomeControlsComplication() {
+ mControlsComponent.getControlsListingController().ifPresent(c -> {
+ if (isHomeControlsAvailable(c.getCurrentServices())) {
+ mDreamOverlayStateController.addComplication(mComplication);
+ } else {
+ mDreamOverlayStateController.removeComplication(mComplication);
+ }
+ });
+ }
+
+ private boolean isHomeControlsAvailable(List<ControlsServiceInfo> controlsServices) {
+ if (controlsServices.isEmpty()) {
+ return false;
+ }
+
final boolean hasFavorites = mControlsComponent.getControlsController()
.map(c -> !c.getFavorites().isEmpty())
.orElse(false);
- if (!hasFavorites || !mControlServicesAvailable
- || mControlsComponent.getVisibility() == UNAVAILABLE) {
- mDreamOverlayStateController.removeComplication(mComplication);
- } else {
- mDreamOverlayStateController.addComplication(mComplication);
- }
+ final ControlsComponent.Visibility visibility = mControlsComponent.getVisibility();
+ return hasFavorites && visibility != UNAVAILABLE;
}
}
@@ -128,7 +152,7 @@
@Inject
DreamHomeControlsChipViewHolder(
DreamHomeControlsChipViewController dreamHomeControlsChipViewController,
- @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
+ @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view,
@Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams
) {
mView = view;
@@ -151,7 +175,7 @@
/**
* Controls behavior of the dream complication.
*/
- static class DreamHomeControlsChipViewController extends ViewController<ImageView> {
+ static class DreamHomeControlsChipViewController extends ViewController<View> {
private static final String TAG = "DreamHomeControlsCtrl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -180,7 +204,7 @@
@Inject
DreamHomeControlsChipViewController(
- @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
+ @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view,
ActivityStarter activityStarter,
Context context,
ControlsComponent controlsComponent,
@@ -195,9 +219,10 @@
@Override
protected void onViewAttached() {
- mView.setImageResource(mControlsComponent.getTileImageId());
- mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
- mView.setOnClickListener(this::onClickHomeControls);
+ final ImageView chip = mView.findViewById(R.id.home_controls_chip);
+ chip.setImageResource(mControlsComponent.getTileImageId());
+ chip.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
+ chip.setOnClickListener(this::onClickHomeControls);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
index c9fecc9..09cc7c5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/ComplicationHostViewModule.java
@@ -41,6 +41,7 @@
public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration";
public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration";
public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout";
+ public static final String COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay";
/**
* Generates a {@link ConstraintLayout}, which can host
@@ -75,6 +76,16 @@
}
/**
+ * Provides the delay to wait for before fading out complications.
+ */
+ @Provides
+ @Named(COMPLICATIONS_FADE_OUT_DELAY)
+ @DreamOverlayComponent.DreamOverlayScope
+ static int providesComplicationsFadeOutDelay(@Main Resources resources) {
+ return resources.getInteger(R.integer.complicationFadeOutDelayMs);
+ }
+
+ /**
* Provides the fade in duration for complications.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
index 7d9f105..5290e44 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockTimeComplicationModule.java
@@ -45,11 +45,12 @@
@Provides
@Named(DREAM_CLOCK_TIME_COMPLICATION_VIEW)
static View provideComplicationView(LayoutInflater layoutInflater) {
- final TextClock view = Preconditions.checkNotNull((TextClock)
+ final View view = Preconditions.checkNotNull(
layoutInflater.inflate(R.layout.dream_overlay_complication_clock_time,
null, false),
"R.layout.dream_overlay_complication_clock_time did not properly inflated");
- view.setFontVariationSettings(TAG_WEIGHT + WEIGHT);
+ ((TextClock) view.findViewById(R.id.time_view)).setFontVariationSettings(
+ TAG_WEIGHT + WEIGHT);
return view;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
index cf05d2d..a7aa97f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -19,7 +19,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import android.view.LayoutInflater;
-import android.widget.ImageView;
+import android.view.View;
import com.android.systemui.R;
import com.android.systemui.dreams.complication.DreamHomeControlsComplication;
@@ -74,8 +74,8 @@
@Provides
@DreamHomeControlsComplicationScope
@Named(DREAM_HOME_CONTROLS_CHIP_VIEW)
- static ImageView provideHomeControlsChipView(LayoutInflater layoutInflater) {
- return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
+ static View provideHomeControlsChipView(LayoutInflater layoutInflater) {
+ return layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip,
null, false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 7d2ce51..9b954f5f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -47,10 +47,10 @@
String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params";
int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1;
- int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 0;
- int DREAM_MEDIA_COMPLICATION_WEIGHT = -1;
- int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 1;
- int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 0;
+ int DREAM_SMARTSPACE_COMPLICATION_WEIGHT = 2;
+ int DREAM_MEDIA_COMPLICATION_WEIGHT = 0;
+ int DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT = 4;
+ int DREAM_MEDIA_ENTRY_COMPLICATION_WEIGHT = 3;
/**
* Provides layout parameters for the clock time complication.
@@ -60,10 +60,11 @@
static ComplicationLayoutParams provideClockTimeLayoutParams() {
return new ComplicationLayoutParams(0,
ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP
+ ComplicationLayoutParams.POSITION_BOTTOM
| ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
- DREAM_CLOCK_TIME_COMPLICATION_WEIGHT);
+ ComplicationLayoutParams.DIRECTION_UP,
+ DREAM_CLOCK_TIME_COMPLICATION_WEIGHT,
+ 0 /*margin*/);
}
/**
@@ -71,10 +72,10 @@
*/
@Provides
@Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideHomeControlsChipLayoutParams(@Main Resources res) {
+ static ComplicationLayoutParams provideHomeControlsChipLayoutParams() {
return new ComplicationLayoutParams(
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width),
- res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height),
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
ComplicationLayoutParams.POSITION_BOTTOM
| ComplicationLayoutParams.POSITION_START,
ComplicationLayoutParams.DIRECTION_END,
@@ -101,14 +102,15 @@
*/
@Provides
@Named(DREAM_SMARTSPACE_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideSmartspaceLayoutParams() {
- return new ComplicationLayoutParams(0,
+ static ComplicationLayoutParams provideSmartspaceLayoutParams(@Main Resources res) {
+ return new ComplicationLayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_TOP
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ComplicationLayoutParams.POSITION_BOTTOM
| ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_DOWN,
+ ComplicationLayoutParams.DIRECTION_END,
DREAM_SMARTSPACE_COMPLICATION_WEIGHT,
- 0,
- true /*snapToGuide*/);
+ res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_padding),
+ res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_max_width));
}
}
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 f9dca08..101f4a4 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -44,7 +44,7 @@
DreamOverlayComponent.class,
})
public interface DreamModule {
- String DREAM_ONLY_ENABLED_FOR_SYSTEM_USER = "dream_only_enabled_for_system_user";
+ String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user";
String DREAM_SUPPORTED = "dream_supported";
@@ -70,10 +70,10 @@
/** */
@Provides
- @Named(DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
- static boolean providesDreamOnlyEnabledForSystemUser(@Main Resources resources) {
+ @Named(DREAM_ONLY_ENABLED_FOR_DOCK_USER)
+ static boolean providesDreamOnlyEnabledForDockUser(@Main Resources resources) {
return resources.getBoolean(
- com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser);
+ com.android.internal.R.bool.config_dreamsOnlyEnabledForDockUser);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index cb012fa..ed0e1d9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -55,6 +55,22 @@
"dream_in_top_complications_anim_delay";
public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
"dream_in_bottom_complications_anim_delay";
+ public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE =
+ "dream_out_complications_translation_y";
+ public static final String DREAM_OUT_TRANSLATION_Y_DURATION =
+ "dream_out_complications_translation_y_duration";
+ public static final String DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM =
+ "dream_out_complications_translation_y_delay_bottom";
+ public static final String DREAM_OUT_TRANSLATION_Y_DELAY_TOP =
+ "dream_out_complications_translation_y_delay_top";
+ public static final String DREAM_OUT_ALPHA_DURATION =
+ "dream_out_complications_alpha_duration";
+ public static final String DREAM_OUT_ALPHA_DELAY_BOTTOM =
+ "dream_out_complications_alpha_delay_bottom";
+ public static final String DREAM_OUT_ALPHA_DELAY_TOP =
+ "dream_out_complications_alpha_delay_top";
+ public static final String DREAM_OUT_BLUR_DURATION =
+ "dream_out_blur_duration";
/** */
@Provides
@@ -127,8 +143,8 @@
*/
@Provides
@Named(DREAM_IN_BLUR_ANIMATION_DURATION)
- static int providesDreamInBlurAnimationDuration(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
+ static long providesDreamInBlurAnimationDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
}
/**
@@ -136,8 +152,8 @@
*/
@Provides
@Named(DREAM_IN_BLUR_ANIMATION_DELAY)
- static int providesDreamInBlurAnimationDelay(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
+ static long providesDreamInBlurAnimationDelay(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
}
/**
@@ -145,8 +161,8 @@
*/
@Provides
@Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
- static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
+ static long providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
}
/**
@@ -154,8 +170,8 @@
*/
@Provides
@Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
- static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
+ static long providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
}
/**
@@ -163,8 +179,69 @@
*/
@Provides
@Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
- static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
- return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+ static long providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
+ return (long) resources.getInteger(
+ R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+ }
+
+ /**
+ * Provides the number of pixels to translate complications when waking up from dream.
+ */
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DISTANCE)
+ @DreamOverlayComponent.DreamOverlayScope
+ static int providesDreamOutComplicationsTranslationY(@Main Resources resources) {
+ return resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DURATION)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsTranslationYDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDurationMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsTranslationYDelayBottom(@Main Resources resources) {
+ return (long) resources.getInteger(
+ R.integer.config_dreamOverlayOutTranslationYDelayBottomMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsTranslationYDelayTop(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDelayTopMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_ALPHA_DURATION)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsAlphaDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDurationMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_ALPHA_DELAY_BOTTOM)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsAlphaDelayBottom(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayBottomMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_ALPHA_DELAY_TOP)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutComplicationsAlphaDelayTop(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayTopMs);
+ }
+
+ @Provides
+ @Named(DREAM_OUT_BLUR_DURATION)
+ @DreamOverlayComponent.DreamOverlayScope
+ static long providesDreamOutBlurDuration(@Main Resources resources) {
+ return (long) resources.getInteger(R.integer.config_dreamOverlayOutBlurDurationMs);
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
index 3087cdf..e276e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
@@ -16,22 +16,26 @@
package com.android.systemui.dreams.touch;
+import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DELAY;
import static com.android.systemui.dreams.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_RESTORE_TIMEOUT;
-import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.touch.TouchInsetManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayDeque;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
@@ -49,33 +53,58 @@
private static final String TAG = "HideComplicationHandler";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final Complication.VisibilityController mVisibilityController;
private final int mRestoreTimeout;
+ private final int mFadeOutDelay;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final Handler mHandler;
- private final Executor mExecutor;
+ private final DelayableExecutor mExecutor;
+ private final DreamOverlayStateController mOverlayStateController;
private final TouchInsetManager mTouchInsetManager;
+ private final Complication.VisibilityController mVisibilityController;
+ private boolean mHidden = false;
+ @Nullable
+ private Runnable mHiddenCallback;
+ private final ArrayDeque<Runnable> mCancelCallbacks = new ArrayDeque<>();
+
private final Runnable mRestoreComplications = new Runnable() {
@Override
public void run() {
- mVisibilityController.setVisibility(View.VISIBLE, true);
+ mVisibilityController.setVisibility(View.VISIBLE);
+ mHidden = false;
+ }
+ };
+
+ private final Runnable mHideComplications = new Runnable() {
+ @Override
+ public void run() {
+ if (mOverlayStateController.areExitAnimationsRunning()) {
+ // Avoid interfering with the exit animations.
+ return;
+ }
+ mVisibilityController.setVisibility(View.INVISIBLE);
+ mHidden = true;
+ if (mHiddenCallback != null) {
+ mHiddenCallback.run();
+ mHiddenCallback = null;
+ }
}
};
@Inject
HideComplicationTouchHandler(Complication.VisibilityController visibilityController,
@Named(COMPLICATIONS_RESTORE_TIMEOUT) int restoreTimeout,
+ @Named(COMPLICATIONS_FADE_OUT_DELAY) int fadeOutDelay,
TouchInsetManager touchInsetManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- @Main Executor executor,
- @Main Handler handler) {
+ @Main DelayableExecutor executor,
+ DreamOverlayStateController overlayStateController) {
mVisibilityController = visibilityController;
mRestoreTimeout = restoreTimeout;
+ mFadeOutDelay = fadeOutDelay;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
- mHandler = handler;
mTouchInsetManager = touchInsetManager;
mExecutor = executor;
+ mOverlayStateController = overlayStateController;
}
@Override
@@ -87,7 +116,8 @@
final boolean bouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
// If other sessions are interested in this touch, do not fade out elements.
- if (session.getActiveSessionCount() > 1 || bouncerShowing) {
+ if (session.getActiveSessionCount() > 1 || bouncerShowing
+ || mOverlayStateController.areExitAnimationsRunning()) {
if (DEBUG) {
Log.d(TAG, "not fading. Active session count: " + session.getActiveSessionCount()
+ ". Bouncer showing: " + bouncerShowing);
@@ -115,8 +145,11 @@
touchCheck.addListener(() -> {
try {
if (!touchCheck.get()) {
- mHandler.removeCallbacks(mRestoreComplications);
- mVisibilityController.setVisibility(View.INVISIBLE, true);
+ // Cancel all pending callbacks.
+ while (!mCancelCallbacks.isEmpty()) mCancelCallbacks.pop().run();
+ mCancelCallbacks.add(
+ mExecutor.executeDelayed(
+ mHideComplications, mFadeOutDelay));
} else {
// If a touch occurred inside the dream overlay touch insets, do not
// handle the touch.
@@ -130,7 +163,23 @@
|| motionEvent.getAction() == MotionEvent.ACTION_UP) {
// End session and initiate delayed reappearance of the complications.
session.pop();
- mHandler.postDelayed(mRestoreComplications, mRestoreTimeout);
+ runAfterHidden(() -> mCancelCallbacks.add(
+ mExecutor.executeDelayed(mRestoreComplications,
+ mRestoreTimeout)));
+ }
+ });
+ }
+
+ /**
+ * Triggers a runnable after complications have been hidden. Will override any previously set
+ * runnable currently waiting for hide to happen.
+ */
+ private void runAfterHidden(Runnable runnable) {
+ mExecutor.execute(() -> {
+ if (mHidden) {
+ runnable.run();
+ } else {
+ mHiddenCallback = runnable;
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 609bd76..a2f65ba 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -22,7 +22,6 @@
import com.android.systemui.CoreStartable
import com.android.systemui.R
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
-import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_HIGH
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
import com.android.systemui.dump.nano.SystemUIProtoDump
import com.android.systemui.plugins.log.LogBuffer
@@ -148,12 +147,12 @@
}
private fun dumpCritical(pw: PrintWriter, args: ParsedArgs) {
- dumpManager.dumpDumpables(pw, args.rawArgs)
+ dumpManager.dumpCritical(pw, args.rawArgs)
dumpConfig(pw)
}
private fun dumpNormal(pw: PrintWriter, args: ParsedArgs) {
- dumpManager.dumpBuffers(pw, args.tailLength)
+ dumpManager.dumpNormal(pw, args.rawArgs, args.tailLength)
logBufferEulogizer.readEulogyIfPresent(pw)
}
@@ -349,14 +348,12 @@
companion object {
const val PRIORITY_ARG = "--dump-priority"
const val PRIORITY_ARG_CRITICAL = "CRITICAL"
- const val PRIORITY_ARG_HIGH = "HIGH"
const val PRIORITY_ARG_NORMAL = "NORMAL"
const val PROTO = "--proto"
}
}
-private val PRIORITY_OPTIONS =
- arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_HIGH, PRIORITY_ARG_NORMAL)
+private val PRIORITY_OPTIONS = arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_NORMAL)
private val COMMANDS = arrayOf(
"bugreport-critical",
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index ae78089..c982131 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -40,20 +40,54 @@
private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
/**
- * Register a dumpable to be called during a bug report. The dumpable will be called during the
- * CRITICAL section of the bug report, so don't dump an excessive amount of stuff here.
+ * Registers a dumpable to be called during the CRITICAL section of the bug report.
+ *
+ * The CRITICAL section gets very high priority during a dump, but also a very limited amount of
+ * time to do the dumping. So, please don't dump an excessive amount of stuff using CRITICAL.
+ *
+ * See [registerDumpable].
+ */
+ fun registerCriticalDumpable(name: String, module: Dumpable) {
+ registerDumpable(name, module, DumpPriority.CRITICAL)
+ }
+
+ /**
+ * Registers a dumpable to be called during the NORMAL section of the bug report.
+ *
+ * The NORMAL section gets a lower priority during a dump, but also more time. This should be
+ * used by [LogBuffer] instances, [ProtoDumpable] instances, and any [Dumpable] instances that
+ * dump a lot of information.
+ */
+ fun registerNormalDumpable(name: String, module: Dumpable) {
+ registerDumpable(name, module, DumpPriority.NORMAL)
+ }
+
+ /**
+ * Register a dumpable to be called during a bug report.
*
* @param name The name to register the dumpable under. This is typically the qualified class
* name of the thing being dumped (getClass().getName()), but can be anything as long as it
* doesn't clash with an existing registration.
+ * @param priority the priority level of this dumpable, which affects at what point in the bug
+ * report this gets dump. By default, the dumpable will be called during the CRITICAL section of
+ * the bug report, so don't dump an excessive amount of stuff here.
+ *
+ * TODO(b/259973758): Replace all calls to this method with calls to [registerCriticalDumpable]
+ * or [registerNormalDumpable] instead.
*/
@Synchronized
- fun registerDumpable(name: String, module: Dumpable) {
+ @JvmOverloads
+ @Deprecated("Use registerCriticalDumpable or registerNormalDumpable instead")
+ fun registerDumpable(
+ name: String,
+ module: Dumpable,
+ priority: DumpPriority = DumpPriority.CRITICAL,
+ ) {
if (!canAssignToNameLocked(name, module)) {
throw IllegalArgumentException("'$name' is already registered")
}
- dumpables[name] = RegisteredDumpable(name, module)
+ dumpables[name] = RegisteredDumpable(name, module, priority)
}
/**
@@ -81,7 +115,10 @@
if (!canAssignToNameLocked(name, buffer)) {
throw IllegalArgumentException("'$name' is already registered")
}
- buffers[name] = RegisteredDumpable(name, buffer)
+
+ // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
+ // data.
+ buffers[name] = RegisteredDumpable(name, buffer, DumpPriority.NORMAL)
}
/**
@@ -140,7 +177,35 @@
}
/**
- * Dumps all registered dumpables to [pw]
+ * Dumps all registered dumpables with critical priority to [pw]
+ */
+ @Synchronized
+ fun dumpCritical(pw: PrintWriter, args: Array<String>) {
+ for (dumpable in dumpables.values) {
+ if (dumpable.priority == DumpPriority.CRITICAL) {
+ dumpDumpable(dumpable, pw, args)
+ }
+ }
+ }
+
+ /**
+ * To [pw], dumps (1) all registered dumpables with normal priority; and (2) all [LogBuffer]s.
+ */
+ @Synchronized
+ fun dumpNormal(pw: PrintWriter, args: Array<String>, tailLength: Int = 0) {
+ for (dumpable in dumpables.values) {
+ if (dumpable.priority == DumpPriority.NORMAL) {
+ dumpDumpable(dumpable, pw, args)
+ }
+ }
+
+ for (buffer in buffers.values) {
+ dumpBuffer(buffer, pw, tailLength)
+ }
+ }
+
+ /**
+ * Dump all the instances of [Dumpable].
*/
@Synchronized
fun dumpDumpables(pw: PrintWriter, args: Array<String>) {
@@ -232,7 +297,15 @@
private data class RegisteredDumpable<T>(
val name: String,
- val dumpable: T
+ val dumpable: T,
+ val priority: DumpPriority,
)
-private const val TAG = "DumpManager"
+/**
+ * The priority level for a given dumpable, which affects at what point in the bug report this gets
+ * dumped.
+ */
+enum class DumpPriority {
+ CRITICAL,
+ NORMAL,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index ec3fdec..5dae0a2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -336,7 +336,6 @@
Log.i(TAG, "Android Restart Suppressed");
return;
}
- Log.i(TAG, "Restarting Android");
mRestarter.restart();
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
new file mode 100644
index 0000000..3d9f627
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import javax.inject.Inject
+
+/** Restarts SystemUI when the screen is locked. */
+class FeatureFlagsDebugRestarter
+@Inject
+constructor(
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+ private val systemExitRestarter: SystemExitRestarter,
+) : Restarter {
+
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change")
+ restartNow()
+ }
+ }
+
+ override fun restart() {
+ Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting on next screen off.")
+ if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
+ restartNow()
+ } else {
+ wakefulnessLifecycle.addObserver(observer)
+ }
+ }
+
+ private fun restartNow() {
+ systemExitRestarter.restart()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 6271334..7189f00 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -35,7 +35,7 @@
) : CoreStartable {
init {
- dumpManager.registerDumpable(FeatureFlagsDebug.TAG) { pw, args ->
+ dumpManager.registerCriticalDumpable(FeatureFlagsDebug.TAG) { pw, args ->
featureFlags.dump(pw, args)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
new file mode 100644
index 0000000..a3f0f66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+/** Restarts SystemUI when the device appears idle. */
+class FeatureFlagsReleaseRestarter
+@Inject
+constructor(
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+ private val batteryController: BatteryController,
+ @Background private val bgExecutor: DelayableExecutor,
+ private val systemExitRestarter: SystemExitRestarter
+) : Restarter {
+ var shouldRestart = false
+ var pendingRestart: Runnable? = null
+
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ maybeScheduleRestart()
+ }
+ }
+
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ maybeScheduleRestart()
+ }
+ }
+
+ override fun restart() {
+ Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting when plugged in and idle.")
+ if (!shouldRestart) {
+ // Don't bother scheduling twice.
+ shouldRestart = true
+ wakefulnessLifecycle.addObserver(observer)
+ batteryController.addCallback(batteryCallback)
+ maybeScheduleRestart()
+ }
+ }
+
+ private fun maybeScheduleRestart() {
+ if (
+ wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
+ ) {
+ if (pendingRestart == null) {
+ pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS)
+ }
+ } else if (pendingRestart != null) {
+ pendingRestart?.run()
+ pendingRestart = null
+ }
+ }
+
+ private fun restartNow() {
+ Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
+ systemExitRestarter.restart()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
index e7d8cc3..d088d74 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt
@@ -29,7 +29,7 @@
constructor(dumpManager: DumpManager, featureFlags: FeatureFlags) : CoreStartable {
init {
- dumpManager.registerDumpable(FeatureFlagsRelease.TAG) { pw, args ->
+ dumpManager.registerCriticalDumpable(FeatureFlagsRelease.TAG) { pw, args ->
featureFlags.dump(pw, args)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ec2a843..886f690 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -61,6 +61,9 @@
// TODO(b/254512517): Tracking Bug
val FSI_REQUIRES_KEYGUARD = unreleasedFlag(110, "fsi_requires_keyguard", teamfood = true)
+ // TODO(b/259130119): Tracking Bug
+ val FSI_ON_DND_UPDATE = unreleasedFlag(259130119, "fsi_on_dnd_update", teamfood = true)
+
// TODO(b/254512538): Tracking Bug
val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply", teamfood = true)
@@ -72,17 +75,34 @@
@JvmField
val NOTIFICATION_DISMISSAL_FADE =
unreleasedFlag(113, "notification_dismissal_fade", teamfood = true)
- val STABILITY_INDEX_FIX = unreleasedFlag(114, "stability_index_fix", teamfood = true)
+
+ // TODO(b/259558771): Tracking Bug
+ val STABILITY_INDEX_FIX = releasedFlag(114, "stability_index_fix")
+
+ // TODO(b/259559750): Tracking Bug
val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true)
@JvmField
val NOTIFICATION_GROUP_CORNER =
unreleasedFlag(116, "notification_group_corner", teamfood = true)
+ // TODO(b/259217907)
+ @JvmField
+ val NOTIFICATION_GROUP_DISMISSAL_ANIMATION =
+ unreleasedFlag(259217907, "notification_group_dismissal_animation", teamfood = true)
+
// TODO(b/257506350): Tracking Bug
val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
- // next id: 118
+ @JvmField
+ val SIMPLIFIED_APPEAR_FRACTION =
+ unreleasedFlag(259395680, "simplified_appear_fraction", teamfood = true)
+
+ // TODO(b/257315550): Tracking Bug
+ val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
+
+ val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
+ unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
// 200 - keyguard/lockscreen
// ** Flag retired **
@@ -112,28 +132,6 @@
@JvmField val MODERN_BOUNCER = releasedFlag(208, "modern_bouncer")
/**
- * Whether the user interactor and repository should use `UserSwitcherController`.
- *
- * If this is `false`, the interactor and repo skip the controller and directly access the
- * framework APIs.
- */
- // TODO(b/254513286): Tracking Bug
- val USER_INTERACTOR_AND_REPO_USE_CONTROLLER =
- unreleasedFlag(210, "user_interactor_and_repo_use_controller")
-
- /**
- * Whether `UserSwitcherController` should use the user interactor.
- *
- * When this is `true`, the controller does not directly access framework APIs. Instead, it goes
- * through the interactor.
- *
- * Note: do not set this to true if [.USER_INTERACTOR_AND_REPO_USE_CONTROLLER] is `true` as it
- * would created a cycle between controller -> interactor -> controller.
- */
- // TODO(b/254513102): Tracking Bug
- val USER_CONTROLLER_USES_INTERACTOR = releasedFlag(211, "user_controller_uses_interactor")
-
- /**
* Whether the clock on a wide lock screen should use the new "stepping" animation for moving
* the digits when the clock moves.
*/
@@ -155,16 +153,16 @@
/**
* Whether to enable the code powering customizable lock screen quick affordances.
*
- * Note that this flag does not enable individual implementations of quick affordances like the
- * new camera quick affordance. Look for individual flags for those.
+ * This flag enables any new prebuilt quick affordances as well.
*/
+ // TODO(b/255618149): Tracking Bug
@JvmField
val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false)
/** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
- // TODO(b/240196500): Tracking Bug
- @JvmField val ACTIVE_UNLOCK_CHIPBAR = unreleasedFlag(217, "active_unlock_chipbar")
+ // TODO(b/256513609): Tracking Bug
+ @JvmField val ACTIVE_UNLOCK_CHIPBAR = releasedFlag(217, "active_unlock_chipbar")
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@@ -191,9 +189,6 @@
"qs_user_detail_shortcut"
)
- // TODO(b/254512747): Tracking Bug
- val NEW_HEADER = releasedFlag(505, "new_header")
-
// TODO(b/254512383): Tracking Bug
@JvmField
val FULL_SCREEN_USER_SWITCHER =
@@ -212,19 +207,6 @@
unreleasedFlag(508, "qs_secondary_data_sub_info", teamfood = true)
// 600- status bar
- // TODO(b/254513246): Tracking Bug
- val STATUS_BAR_USER_SWITCHER =
- resourceBooleanFlag(602, R.bool.flag_user_switcher_chip, "status_bar_user_switcher")
-
- // TODO(b/254512623): Tracking Bug
- @Deprecated("Replaced by mobile and wifi specific flags.")
- val NEW_STATUS_BAR_PIPELINE_BACKEND =
- unreleasedFlag(604, "new_status_bar_pipeline_backend", teamfood = false)
-
- // TODO(b/254512660): Tracking Bug
- @Deprecated("Replaced by mobile and wifi specific flags.")
- val NEW_STATUS_BAR_PIPELINE_FRONTEND =
- unreleasedFlag(605, "new_status_bar_pipeline_frontend", teamfood = false)
// TODO(b/256614753): Tracking Bug
val NEW_STATUS_BAR_MOBILE_ICONS = unreleasedFlag(606, "new_status_bar_mobile_icons")
@@ -239,6 +221,13 @@
// TODO(b/256613548): Tracking Bug
val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend")
+ // TODO(b/256623670): Tracking Bug
+ @JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon")
+
+ // TODO(b/260881289): Tracking Bug
+ val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
+ unreleasedFlag(611, "new_status_bar_icons_debug_coloring")
+
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
@@ -291,6 +280,8 @@
// TODO(b/254513168): Tracking Bug
@JvmField val UMO_SURFACE_RIPPLE = unreleasedFlag(907, "umo_surface_ripple")
+ @JvmField val MEDIA_FALSING_PENALTY = unreleasedFlag(908, "media_falsing_media")
+
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -352,6 +343,12 @@
// TODO(b/256873975): Tracking Bug
@JvmField @Keep val WM_BUBBLE_BAR = unreleasedFlag(1111, "wm_bubble_bar")
+ // TODO(b/260271148): Tracking bug
+ @Keep
+ @JvmField
+ val WM_DESKTOP_WINDOWING_2 =
+ sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
+
// 1200 - predictive back
@Keep
@JvmField
@@ -372,15 +369,19 @@
@JvmField
val NEW_BACK_AFFORDANCE = unreleasedFlag(1203, "new_back_affordance", teamfood = false)
+ // TODO(b/255854141): Tracking Bug
+ @JvmField
+ val WM_ENABLE_PREDICTIVE_BACK_SYSUI =
+ unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
+
// 1300 - screenshots
// TODO(b/254512719): Tracking Bug
- @JvmField
- val SCREENSHOT_REQUEST_PROCESSOR =
- unreleasedFlag(1300, "screenshot_request_processor", teamfood = true)
+ @JvmField val SCREENSHOT_REQUEST_PROCESSOR = releasedFlag(1300, "screenshot_request_processor")
// TODO(b/254513155): Tracking Bug
@JvmField
- val SCREENSHOT_WORK_PROFILE_POLICY = unreleasedFlag(1301, "screenshot_work_profile_policy")
+ val SCREENSHOT_WORK_PROFILE_POLICY =
+ unreleasedFlag(1301, "screenshot_work_profile_policy", teamfood = true)
// 1400 - columbus
// TODO(b/254512756): Tracking Bug
@@ -396,10 +397,8 @@
unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
// 1700 - clipboard
- @JvmField
- val CLIPBOARD_OVERLAY_REFACTOR =
- unreleasedFlag(1700, "clipboard_overlay_refactor", teamfood = true)
- @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = unreleasedFlag(1701, "clipboard_remote_behavior")
+ @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
+ @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
// 1800 - shade container
@JvmField
@@ -414,4 +413,22 @@
// 2100 - Falsing Manager
@JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
+
+ // 2200 - udfps
+ // TODO(b/259264861): Tracking Bug
+ @JvmField val UDFPS_NEW_TOUCH_DETECTION = unreleasedFlag(2200, "udfps_new_touch_detection")
+ @JvmField val UDFPS_ELLIPSE_DEBUG_UI = unreleasedFlag(2201, "udfps_ellipse_debug")
+ @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
+
+ // 2300 - stylus
+ @JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
+
+ // 2400 - performance tools and debugging info
+ // TODO(b/238923086): Tracking Bug
+ @JvmField
+ val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
+ unreleasedFlag(2400, "warn_on_blocking_binder_transactions")
+
+ // TODO(b259590361): Tracking bug
+ val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 18d7bcf..8442230 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -15,7 +15,6 @@
*/
package com.android.systemui.flags
-import com.android.internal.statusbar.IStatusBarService
import dagger.Module
import dagger.Provides
import javax.inject.Named
@@ -32,15 +31,5 @@
fun providesAllFlags(): Map<Int, Flag<*>> {
return FlagsFactory.knownFlags.map { it.value.id to it.value }.toMap()
}
-
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object : Restarter {
- override fun restart() {
- barService.restart()
- }
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
new file mode 100644
index 0000000..f1b1be4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import javax.inject.Inject
+
+class SystemExitRestarter @Inject constructor() : Restarter {
+ override fun restart() {
+ System.exit(0)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 3ef5499..db2cd91 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -125,6 +125,7 @@
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.scrim.ScrimDrawable;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -201,6 +202,7 @@
protected final SecureSettings mSecureSettings;
protected final Resources mResources;
private final ConfigurationController mConfigurationController;
+ private final UserTracker mUserTracker;
private final UserManager mUserManager;
private final TrustManager mTrustManager;
private final IActivityManager mIActivityManager;
@@ -339,6 +341,7 @@
@NonNull VibratorHelper vibrator,
@Main Resources resources,
ConfigurationController configurationController,
+ UserTracker userTracker,
KeyguardStateController keyguardStateController,
UserManager userManager,
TrustManager trustManager,
@@ -370,6 +373,7 @@
mSecureSettings = secureSettings;
mResources = resources;
mConfigurationController = configurationController;
+ mUserTracker = userTracker;
mUserManager = userManager;
mTrustManager = trustManager;
mIActivityManager = iActivityManager;
@@ -1198,11 +1202,7 @@
}
protected UserInfo getCurrentUser() {
- try {
- return mIActivityManager.getCurrentUser();
- } catch (RemoteException re) {
- return null;
- }
+ return mUserTracker.getUserInfo();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java b/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java
deleted file mode 100644
index 622fa65..0000000
--- a/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 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.globalactions;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ScrollView;
-
-/**
- * When measured, this view sets the minimum height of its first child to be equal to its own
- * target height.
- *
- * This ensures fall-through click handlers can be placed on this view's child component.
- */
-public class MinHeightScrollView extends ScrollView {
- public MinHeightScrollView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- View firstChild = getChildAt(0);
- if (firstChild != null) {
- firstChild.setMinimumHeight(MeasureSpec.getSize(heightMeasureSpec));
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index f84a5e3..9235e10 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -375,7 +375,7 @@
public static final int INDICATION_TYPE_ALIGNMENT = 4;
public static final int INDICATION_TYPE_TRANSIENT = 5;
public static final int INDICATION_TYPE_TRUST = 6;
- public static final int INDICATION_TYPE_RESTING = 7;
+ public static final int INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE = 7;
public static final int INDICATION_TYPE_USER_LOCKED = 8;
public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11;
@@ -390,7 +390,7 @@
INDICATION_TYPE_ALIGNMENT,
INDICATION_TYPE_TRANSIENT,
INDICATION_TYPE_TRUST,
- INDICATION_TYPE_RESTING,
+ INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
INDICATION_TYPE_USER_LOCKED,
INDICATION_TYPE_REVERSE_CHARGING,
INDICATION_TYPE_BIOMETRIC_MESSAGE,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
new file mode 100644
index 0000000..4ae37c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.content.UriMatcher
+import android.content.pm.ProviderInfo
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.util.Log
+import com.android.systemui.SystemUIAppComponentFactoryBase
+import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import javax.inject.Inject
+import kotlinx.coroutines.runBlocking
+
+class KeyguardQuickAffordanceProvider :
+ ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
+
+ @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
+
+ private lateinit var contextAvailableCallback: ContextAvailableCallback
+
+ private val uriMatcher =
+ UriMatcher(UriMatcher.NO_MATCH).apply {
+ addURI(
+ Contract.AUTHORITY,
+ Contract.SlotTable.TABLE_NAME,
+ MATCH_CODE_ALL_SLOTS,
+ )
+ addURI(
+ Contract.AUTHORITY,
+ Contract.AffordanceTable.TABLE_NAME,
+ MATCH_CODE_ALL_AFFORDANCES,
+ )
+ addURI(
+ Contract.AUTHORITY,
+ Contract.SelectionTable.TABLE_NAME,
+ MATCH_CODE_ALL_SELECTIONS,
+ )
+ addURI(
+ Contract.AUTHORITY,
+ Contract.FlagsTable.TABLE_NAME,
+ MATCH_CODE_ALL_FLAGS,
+ )
+ }
+
+ override fun onCreate(): Boolean {
+ return true
+ }
+
+ override fun attachInfo(context: Context?, info: ProviderInfo?) {
+ contextAvailableCallback.onContextAvailable(checkNotNull(context))
+ super.attachInfo(context, info)
+ }
+
+ override fun setContextAvailableCallback(callback: ContextAvailableCallback) {
+ contextAvailableCallback = callback
+ }
+
+ override fun getType(uri: Uri): String? {
+ val prefix =
+ when (uriMatcher.match(uri)) {
+ MATCH_CODE_ALL_SLOTS,
+ MATCH_CODE_ALL_AFFORDANCES,
+ MATCH_CODE_ALL_FLAGS,
+ MATCH_CODE_ALL_SELECTIONS -> "vnd.android.cursor.dir/vnd."
+ else -> null
+ }
+
+ val tableName =
+ when (uriMatcher.match(uri)) {
+ MATCH_CODE_ALL_SLOTS -> Contract.SlotTable.TABLE_NAME
+ MATCH_CODE_ALL_AFFORDANCES -> Contract.AffordanceTable.TABLE_NAME
+ MATCH_CODE_ALL_SELECTIONS -> Contract.SelectionTable.TABLE_NAME
+ MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME
+ else -> null
+ }
+
+ if (prefix == null || tableName == null) {
+ return null
+ }
+
+ return "$prefix${Contract.AUTHORITY}.$tableName"
+ }
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
+ throw UnsupportedOperationException()
+ }
+
+ return insertSelection(values)
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array<out String>?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ sortOrder: String?,
+ ): Cursor? {
+ return when (uriMatcher.match(uri)) {
+ MATCH_CODE_ALL_AFFORDANCES -> runBlocking { queryAffordances() }
+ MATCH_CODE_ALL_SLOTS -> querySlots()
+ MATCH_CODE_ALL_SELECTIONS -> runBlocking { querySelections() }
+ MATCH_CODE_ALL_FLAGS -> queryFlags()
+ else -> null
+ }
+ }
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ ): Int {
+ Log.e(TAG, "Update is not supported!")
+ return 0
+ }
+
+ override fun delete(
+ uri: Uri,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ ): Int {
+ if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) {
+ throw UnsupportedOperationException()
+ }
+
+ return deleteSelection(uri, selectionArgs)
+ }
+
+ private fun insertSelection(values: ContentValues?): Uri? {
+ if (values == null) {
+ throw IllegalArgumentException("Cannot insert selection, no values passed in!")
+ }
+
+ if (!values.containsKey(Contract.SelectionTable.Columns.SLOT_ID)) {
+ throw IllegalArgumentException(
+ "Cannot insert selection, " +
+ "\"${Contract.SelectionTable.Columns.SLOT_ID}\" not specified!"
+ )
+ }
+
+ if (!values.containsKey(Contract.SelectionTable.Columns.AFFORDANCE_ID)) {
+ throw IllegalArgumentException(
+ "Cannot insert selection, " +
+ "\"${Contract.SelectionTable.Columns.AFFORDANCE_ID}\" not specified!"
+ )
+ }
+
+ val slotId = values.getAsString(Contract.SelectionTable.Columns.SLOT_ID)
+ val affordanceId = values.getAsString(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+
+ if (slotId.isNullOrEmpty()) {
+ throw IllegalArgumentException("Cannot insert selection, slot ID was empty!")
+ }
+
+ if (affordanceId.isNullOrEmpty()) {
+ throw IllegalArgumentException("Cannot insert selection, affordance ID was empty!")
+ }
+
+ val success =
+ interactor.select(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ )
+
+ return if (success) {
+ Log.d(TAG, "Successfully selected $affordanceId for slot $slotId")
+ context?.contentResolver?.notifyChange(Contract.SelectionTable.URI, null)
+ Contract.SelectionTable.URI
+ } else {
+ Log.d(TAG, "Failed to select $affordanceId for slot $slotId")
+ null
+ }
+ }
+
+ private suspend fun querySelections(): Cursor {
+ return MatrixCursor(
+ arrayOf(
+ Contract.SelectionTable.Columns.SLOT_ID,
+ Contract.SelectionTable.Columns.AFFORDANCE_ID,
+ Contract.SelectionTable.Columns.AFFORDANCE_NAME,
+ )
+ )
+ .apply {
+ val affordanceRepresentationsBySlotId = interactor.getSelections()
+ affordanceRepresentationsBySlotId.entries.forEach {
+ (slotId, affordanceRepresentations) ->
+ affordanceRepresentations.forEach { affordanceRepresentation ->
+ addRow(
+ arrayOf(
+ slotId,
+ affordanceRepresentation.id,
+ affordanceRepresentation.name,
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private suspend fun queryAffordances(): Cursor {
+ return MatrixCursor(
+ arrayOf(
+ Contract.AffordanceTable.Columns.ID,
+ Contract.AffordanceTable.Columns.NAME,
+ Contract.AffordanceTable.Columns.ICON,
+ Contract.AffordanceTable.Columns.IS_ENABLED,
+ Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS,
+ Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT,
+ Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME,
+ )
+ )
+ .apply {
+ interactor.getAffordancePickerRepresentations().forEach { representation ->
+ addRow(
+ arrayOf(
+ representation.id,
+ representation.name,
+ representation.iconResourceId,
+ if (representation.isEnabled) 1 else 0,
+ representation.instructions?.joinToString(
+ Contract.AffordanceTable.ENABLEMENT_INSTRUCTIONS_DELIMITER
+ ),
+ representation.actionText,
+ representation.actionComponentName,
+ )
+ )
+ }
+ }
+ }
+
+ private fun querySlots(): Cursor {
+ return MatrixCursor(
+ arrayOf(
+ Contract.SlotTable.Columns.ID,
+ Contract.SlotTable.Columns.CAPACITY,
+ )
+ )
+ .apply {
+ interactor.getSlotPickerRepresentations().forEach { representation ->
+ addRow(
+ arrayOf(
+ representation.id,
+ representation.maxSelectedAffordances,
+ )
+ )
+ }
+ }
+ }
+
+ private fun queryFlags(): Cursor {
+ return MatrixCursor(
+ arrayOf(
+ Contract.FlagsTable.Columns.NAME,
+ Contract.FlagsTable.Columns.VALUE,
+ )
+ )
+ .apply {
+ interactor.getPickerFlags().forEach { flag ->
+ addRow(
+ arrayOf(
+ flag.name,
+ if (flag.value) {
+ 1
+ } else {
+ 0
+ },
+ )
+ )
+ }
+ }
+ }
+
+ private fun deleteSelection(
+ uri: Uri,
+ selectionArgs: Array<out String>?,
+ ): Int {
+ if (selectionArgs == null) {
+ throw IllegalArgumentException(
+ "Cannot delete selection, selection arguments not included!"
+ )
+ }
+
+ val (slotId, affordanceId) =
+ when (selectionArgs.size) {
+ 1 -> Pair(selectionArgs[0], null)
+ 2 -> Pair(selectionArgs[0], selectionArgs[1])
+ else ->
+ throw IllegalArgumentException(
+ "Cannot delete selection, selection arguments has wrong size, expected to" +
+ " have 1 or 2 arguments, had ${selectionArgs.size} instead!"
+ )
+ }
+
+ val deleted =
+ interactor.unselect(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ )
+
+ return if (deleted) {
+ Log.d(TAG, "Successfully unselected $affordanceId for slot $slotId")
+ context?.contentResolver?.notifyChange(uri, null)
+ 1
+ } else {
+ Log.d(TAG, "Failed to unselect $affordanceId for slot $slotId")
+ 0
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceProvider"
+ private const val MATCH_CODE_ALL_SLOTS = 1
+ private const val MATCH_CODE_ALL_AFFORDANCES = 2
+ private const val MATCH_CODE_ALL_SELECTIONS = 3
+ private const val MATCH_CODE_ALL_FLAGS = 4
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 8846bbd..c332a0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -249,6 +249,7 @@
synchronized (mFinishCallbacks) {
if (mFinishCallbacks.remove(transition) == null) return;
}
+ info.releaseAllSurfaces();
Slog.d(TAG, "Finish IRemoteAnimationRunner.");
finishCallback.onTransitionFinished(null /* wct */, null /* t */);
}
@@ -264,6 +265,8 @@
synchronized (mFinishCallbacks) {
origFinishCB = mFinishCallbacks.remove(transition);
}
+ info.releaseAllSurfaces();
+ t.close();
if (origFinishCB == null) {
// already finished (or not started yet), so do nothing.
return;
@@ -459,12 +462,15 @@
t.apply();
mBinder.setOccluded(true /* isOccluded */, true /* animate */);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ info.releaseAllSurfaces();
}
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
+ t.close();
+ info.releaseAllSurfaces();
}
};
@@ -476,12 +482,15 @@
t.apply();
mBinder.setOccluded(false /* isOccluded */, true /* animate */);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ info.releaseAllSurfaces();
}
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
+ t.close();
+ info.releaseAllSurfaces();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 2a73797..34ae1a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard;
import android.annotation.AnyThread;
-import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -52,6 +51,7 @@
import com.android.systemui.R;
import com.android.systemui.SystemUIAppComponentFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -140,6 +140,8 @@
public KeyguardBypassController mKeyguardBypassController;
@Inject
public KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Inject
+ UserTracker mUserTracker;
private CharSequence mMediaTitle;
private CharSequence mMediaArtist;
protected boolean mDozing;
@@ -355,7 +357,7 @@
synchronized (this) {
if (withinNHoursLocked(mNextAlarmInfo, ALARM_VISIBILITY_HOURS)) {
String pattern = android.text.format.DateFormat.is24HourFormat(getContext(),
- ActivityManager.getCurrentUser()) ? "HH:mm" : "h:mm";
+ mUserTracker.getUserId()) ? "HH:mm" : "h:mm";
mNextAlarm = android.text.format.DateFormat.format(pattern,
mNextAlarmInfo.getTriggerTime()).toString();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7cd3843..6ed5550 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -36,7 +36,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -124,6 +123,7 @@
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -137,6 +137,7 @@
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
@@ -263,6 +264,7 @@
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
+ private final UserTracker mUserTracker;
private final SysuiStatusBarStateController mStatusBarStateController;
private final Executor mUiBgExecutor;
private final ScreenOffAnimationController mScreenOffAnimationController;
@@ -409,6 +411,11 @@
private final int mDreamOpenAnimationDuration;
/**
+ * The duration in milliseconds of the dream close animation.
+ */
+ private final int mDreamCloseAnimationDuration;
+
+ /**
* The animation used for hiding keyguard. This is used to fetch the animation timings if
* WindowManager is not providing us with them.
*/
@@ -715,7 +722,7 @@
@Override
public void keyguardDone(boolean strongAuth, int targetUserId) {
- if (targetUserId != ActivityManager.getCurrentUser()) {
+ if (targetUserId != mUserTracker.getUserId()) {
return;
}
if (DEBUG) Log.d(TAG, "keyguardDone");
@@ -738,7 +745,7 @@
public void keyguardDonePending(boolean strongAuth, int targetUserId) {
Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
if (DEBUG) Log.d(TAG, "keyguardDonePending");
- if (targetUserId != ActivityManager.getCurrentUser()) {
+ if (targetUserId != mUserTracker.getUserId()) {
Trace.endSection();
return;
}
@@ -845,6 +852,7 @@
@Override
public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
mOccludeAnimationPlaying = true;
+ mScrimControllerLazy.get().setOccludeAnimationPlaying(true);
}
@Override
@@ -855,12 +863,13 @@
// Ensure keyguard state is set correctly if we're cancelled.
mCentralSurfaces.updateIsKeyguard();
+ mScrimControllerLazy.get().setOccludeAnimationPlaying(false);
}
@Override
public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
if (launchIsFullScreen) {
- mCentralSurfaces.instantCollapseNotificationPanel();
+ mShadeController.get().instantCollapseShade();
}
mOccludeAnimationPlaying = false;
@@ -868,6 +877,7 @@
// Hide the keyguard now that we're done launching the occluding activity over
// it.
mCentralSurfaces.updateIsKeyguard();
+ mScrimControllerLazy.get().setOccludeAnimationPlaying(false);
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
}
@@ -889,25 +899,32 @@
@NonNull
@Override
public LaunchAnimator.State createAnimatorState() {
- final int width = getLaunchContainer().getWidth();
- final int height = getLaunchContainer().getHeight();
-
- final float initialHeight = height / 3f;
- final float initialWidth = width / 3f;
+ final int fullWidth = getLaunchContainer().getWidth();
+ final int fullHeight = getLaunchContainer().getHeight();
if (mUpdateMonitor.isSecureCameraLaunchedOverKeyguard()) {
+ final float initialHeight = fullHeight / 3f;
+ final float initialWidth = fullWidth / 3f;
+
// Start the animation near the power button, at one-third size, since the
// camera was launched from the power button.
return new LaunchAnimator.State(
(int) (mPowerButtonY - initialHeight / 2f) /* top */,
(int) (mPowerButtonY + initialHeight / 2f) /* bottom */,
- (int) (width - initialWidth) /* left */,
- width /* right */,
+ (int) (fullWidth - initialWidth) /* left */,
+ fullWidth /* right */,
mWindowCornerRadius, mWindowCornerRadius);
} else {
- // Start the animation in the center of the screen, scaled down.
+ final float initialHeight = fullHeight / 2f;
+ final float initialWidth = fullWidth / 2f;
+
+ // Start the animation in the center of the screen, scaled down to half
+ // size.
return new LaunchAnimator.State(
- height / 2, height / 2, width / 2, width / 2,
+ (int) (fullHeight - initialHeight) / 2,
+ (int) (initialHeight + (fullHeight - initialHeight) / 2),
+ (int) (fullWidth - initialWidth) / 2,
+ (int) (initialWidth + (fullWidth - initialWidth) / 2),
mWindowCornerRadius, mWindowCornerRadius);
}
}
@@ -1054,7 +1071,8 @@
}
mUnoccludeAnimator = ValueAnimator.ofFloat(1f, 0f);
- mUnoccludeAnimator.setDuration(UNOCCLUDE_ANIMATION_DURATION);
+ mUnoccludeAnimator.setDuration(isDream ? mDreamCloseAnimationDuration
+ : UNOCCLUDE_ANIMATION_DURATION);
mUnoccludeAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE);
mUnoccludeAnimator.addUpdateListener(
animation -> {
@@ -1124,12 +1142,14 @@
private ScreenOnCoordinator mScreenOnCoordinator;
private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
+ private Lazy<ScrimController> mScrimControllerLazy;
/**
* Injected constructor. See {@link KeyguardModule}.
*/
public KeyguardViewMediator(
Context context,
+ UserTracker userTracker,
FalsingCollector falsingCollector,
LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
@@ -1153,8 +1173,10 @@
DreamOverlayStateController dreamOverlayStateController,
Lazy<ShadeController> shadeControllerLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
- Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+ Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+ Lazy<ScrimController> scrimControllerLazy) {
mContext = context;
+ mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
mLockPatternUtils = lockPatternUtils;
mBroadcastDispatcher = broadcastDispatcher;
@@ -1197,6 +1219,7 @@
mDreamOverlayStateController = dreamOverlayStateController;
mActivityLaunchAnimator = activityLaunchAnimator;
+ mScrimControllerLazy = scrimControllerLazy;
mPowerButtonY = context.getResources().getDimensionPixelSize(
R.dimen.physical_power_button_center_screen_location_y);
@@ -1204,6 +1227,8 @@
mDreamOpenAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_dreamOpenAnimationDuration);
+ mDreamCloseAnimationDuration = context.getResources().getInteger(
+ com.android.internal.R.integer.config_dreamCloseAnimationDuration);
}
public void userActivity() {
@@ -1233,7 +1258,7 @@
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
- KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());
+ KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
// Assume keyguard is showing (unless it's disabled) until we know for sure, unless Keyguard
// is disabled.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 78a7c9e..47ef0fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -47,12 +47,14 @@
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
@@ -86,6 +88,7 @@
@SysUISingleton
public static KeyguardViewMediator newKeyguardViewMediator(
Context context,
+ UserTracker userTracker,
FalsingCollector falsingCollector,
LockPatternUtils lockPatternUtils,
BroadcastDispatcher broadcastDispatcher,
@@ -111,9 +114,11 @@
DreamOverlayStateController dreamOverlayStateController,
Lazy<ShadeController> shadeController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
- Lazy<ActivityLaunchAnimator> activityLaunchAnimator) {
+ Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
+ Lazy<ScrimController> scrimControllerLazy) {
return new KeyguardViewMediator(
context,
+ userTracker,
falsingCollector,
lockPatternUtils,
broadcastDispatcher,
@@ -139,7 +144,8 @@
dreamOverlayStateController,
shadeController,
notificationShadeWindowController,
- activityLaunchAnimator);
+ activityLaunchAnimator,
+ scrimControllerLazy);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index a069582..73dbeab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -24,6 +24,8 @@
*/
object BuiltInKeyguardQuickAffordanceKeys {
// Please keep alphabetical order of const names to simplify future maintenance.
+ const val CAMERA = "camera"
+ const val FLASHLIGHT = "flashlight"
const val HOME_CONTROLS = "home"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
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
new file mode 100644
index 0000000..dbc376e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.camera.CameraGestureHelper
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+@SysUISingleton
+class CameraQuickAffordanceConfig
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val cameraGestureHelper: Lazy<CameraGestureHelper>,
+) : KeyguardQuickAffordanceConfig {
+
+ override val key: String
+ get() = BuiltInKeyguardQuickAffordanceKeys.CAMERA
+
+ override val pickerName: String
+ get() = context.getString(R.string.accessibility_camera_button)
+
+ override val pickerIconResourceId: Int
+ get() = com.android.internal.R.drawable.perm_group_camera
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
+ get() =
+ flowOf(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Resource(
+ com.android.internal.R.drawable.perm_group_camera,
+ ContentDescription.Resource(R.string.accessibility_camera_button)
+ )
+ )
+ )
+
+ override fun onTriggered(
+ expandable: Expandable?
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ cameraGestureHelper
+ .get()
+ .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
new file mode 100644
index 0000000..62fe80a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.ContentDescription
+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.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.statusbar.policy.FlashlightController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class FlashlightQuickAffordanceConfig
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val flashlightController: FlashlightController,
+) : KeyguardQuickAffordanceConfig {
+
+ private sealed class FlashlightState {
+
+ abstract fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState
+
+ object On : FlashlightState() {
+ override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_flashlight_icon_on,
+ ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+ ),
+ ActivationState.Active
+ )
+ }
+
+ object OffAvailable : FlashlightState() {
+ override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ Icon.Resource(
+ R.drawable.qs_flashlight_icon_off,
+ ContentDescription.Resource(R.string.quick_settings_flashlight_label)
+ ),
+ ActivationState.Inactive
+ )
+ }
+
+ object Unavailable : FlashlightState() {
+ override fun toLockScreenState(): KeyguardQuickAffordanceConfig.LockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ }
+
+ override val key: String
+ get() = BuiltInKeyguardQuickAffordanceKeys.FLASHLIGHT
+
+ override val pickerName: String
+ get() = context.getString(R.string.quick_settings_flashlight_label)
+
+ override val pickerIconResourceId: Int
+ get() = R.drawable.ic_flashlight_off
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ conflatedCallbackFlow {
+ val flashlightCallback =
+ object : FlashlightController.FlashlightListener {
+ override fun onFlashlightChanged(enabled: Boolean) {
+ trySendWithFailureLogging(
+ if (enabled) {
+ FlashlightState.On.toLockScreenState()
+ } else {
+ FlashlightState.OffAvailable.toLockScreenState()
+ },
+ TAG
+ )
+ }
+
+ override fun onFlashlightError() {
+ trySendWithFailureLogging(
+ FlashlightState.OffAvailable.toLockScreenState(),
+ TAG
+ )
+ }
+
+ override fun onFlashlightAvailabilityChanged(available: Boolean) {
+ trySendWithFailureLogging(
+ if (!available) {
+ FlashlightState.Unavailable.toLockScreenState()
+ } else {
+ if (flashlightController.isEnabled) {
+ FlashlightState.On.toLockScreenState()
+ } else {
+ FlashlightState.OffAvailable.toLockScreenState()
+ }
+ },
+ TAG
+ )
+ }
+ }
+
+ flashlightController.addCallback(flashlightCallback)
+
+ awaitClose { flashlightController.removeCallback(flashlightCallback) }
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ flashlightController.setFlashlight(
+ flashlightController.isAvailable && !flashlightController.isEnabled
+ )
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState =
+ if (flashlightController.isAvailable) {
+ KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+
+ companion object {
+ private const val TAG = "FlashlightQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index d6f521c..2558fab 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.content.Intent
import androidx.annotation.DrawableRes
+import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -45,7 +46,7 @@
class HomeControlsKeyguardQuickAffordanceConfig
@Inject
constructor(
- @Application context: Context,
+ @Application private val context: Context,
private val component: ControlsComponent,
) : KeyguardQuickAffordanceConfig {
@@ -66,6 +67,36 @@
}
}
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ if (!component.isEnabled()) {
+ return KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+
+ val currentServices =
+ component.getControlsListingController().getOrNull()?.getCurrentServices()
+ val hasFavorites =
+ component.getControlsController().getOrNull()?.getFavorites()?.isNotEmpty() == true
+ if (currentServices.isNullOrEmpty() || !hasFavorites) {
+ return KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+ instructions =
+ listOf(
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_message,
+ pickerName,
+ ),
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_home_instruction_1
+ ),
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_home_instruction_2
+ ),
+ ),
+ )
+ }
+
+ return KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ }
+
override fun onTriggered(
expandable: Expandable?,
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index bea9363..072cfb1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -17,23 +17,35 @@
package com.android.systemui.keyguard.data.quickaffordance
+import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.ElementsIntoSet
@Module
-object KeyguardDataQuickAffordanceModule {
- @Provides
- @ElementsIntoSet
- fun quickAffordanceConfigs(
- home: HomeControlsKeyguardQuickAffordanceConfig,
- quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
- qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
- ): Set<KeyguardQuickAffordanceConfig> {
- return setOf(
- home,
- quickAccessWallet,
- qrCodeScanner,
- )
+interface KeyguardDataQuickAffordanceModule {
+ @Binds
+ fun providerClientFactory(
+ impl: KeyguardQuickAffordanceProviderClientFactoryImpl,
+ ): KeyguardQuickAffordanceProviderClientFactory
+
+ companion object {
+ @Provides
+ @ElementsIntoSet
+ fun quickAffordanceConfigs(
+ flashlight: FlashlightQuickAffordanceConfig,
+ home: HomeControlsKeyguardQuickAffordanceConfig,
+ quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+ qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+ camera: CameraQuickAffordanceConfig,
+ ): Set<KeyguardQuickAffordanceConfig> {
+ return setOf(
+ camera,
+ flashlight,
+ home,
+ quickAccessWallet,
+ qrCodeScanner,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index fd40d1d..98b1a73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -21,6 +21,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import kotlinx.coroutines.flow.Flow
/** Defines interface that can act as data source for a single quick affordance model. */
@@ -41,6 +42,12 @@
val lockScreenState: Flow<LockScreenState>
/**
+ * Returns the [PickerScreenState] representing the affordance in the settings or selector
+ * experience.
+ */
+ suspend fun getPickerScreenState(): PickerScreenState = PickerScreenState.Default
+
+ /**
* Notifies that the affordance was clicked by the user.
*
* @param expandable An [Expandable] to use when animating dialogs or activities
@@ -49,6 +56,58 @@
fun onTriggered(expandable: Expandable?): OnTriggeredResult
/**
+ * Encapsulates the state of a quick affordance within the context of the settings or selector
+ * experience.
+ */
+ sealed class PickerScreenState {
+
+ /** The picker shows the item for selecting this affordance as it normally would. */
+ object Default : PickerScreenState()
+
+ /**
+ * The picker does not show an item for selecting this affordance as it is not supported on
+ * the device at all. For example, missing hardware requirements.
+ */
+ object UnavailableOnDevice : PickerScreenState()
+
+ /**
+ * The picker shows the item for selecting this affordance as disabled. Clicking on it will
+ * show the given instructions to the user. If [actionText] and [actionComponentName] are
+ * provided (optional) a button will be shown to open an activity to help the user complete
+ * the steps described in the instructions.
+ */
+ data class Disabled(
+ /** List of human-readable instructions for setting up the quick affordance. */
+ val instructions: List<String>,
+ /**
+ * Optional text to display on a button that the user can click to start a flow to go
+ * and set up the quick affordance and make it enabled.
+ */
+ val actionText: String? = null,
+ /**
+ * Optional component name to be able to build an `Intent` that opens an `Activity` for
+ * the user to be able to set up the quick affordance and make it enabled.
+ *
+ * This is either just an action for the `Intent` or a package name and action,
+ * separated by [Contract.AffordanceTable.COMPONENT_NAME_SEPARATOR] for convenience, you
+ * can use the [componentName] function.
+ */
+ val actionComponentName: String? = null,
+ ) : PickerScreenState() {
+ init {
+ check(instructions.isNotEmpty()) { "Instructions must not be empty!" }
+ check(
+ (actionText.isNullOrEmpty() && actionComponentName.isNullOrEmpty()) ||
+ (!actionText.isNullOrEmpty() && !actionComponentName.isNullOrEmpty())
+ ) {
+ "actionText and actionComponentName must either both be null/empty or both be" +
+ " non-empty!"
+ }
+ }
+ }
+ }
+
+ /**
* Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
* button on the lock-screen).
*/
@@ -83,4 +142,18 @@
val canShowWhileLocked: Boolean,
) : OnTriggeredResult()
}
+
+ companion object {
+ fun componentName(
+ packageName: String? = null,
+ action: String?,
+ ): String? {
+ return when {
+ action.isNullOrEmpty() -> null
+ !packageName.isNullOrEmpty() ->
+ "$packageName${Contract.AffordanceTable.COMPONENT_NAME_SEPARATOR}$action"
+ else -> action
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
new file mode 100644
index 0000000..72747f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer.Companion.BINDINGS
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Keeps quick affordance selections and legacy user settings in sync.
+ *
+ * "Legacy user settings" are user settings like: Settings > Display > Lock screen > "Show device
+ * controls" Settings > Display > Lock screen > "Show wallet"
+ *
+ * Quick affordance selections are the ones available through the new custom lock screen experience
+ * from Settings > Wallpaper & Style.
+ *
+ * This class keeps these in sync, mostly for backwards compatibility purposes and in order to not
+ * "forget" an existing legacy user setting when the device gets updated with a version of System UI
+ * that has the new customizable lock screen feature.
+ *
+ * The way it works is that, when [startSyncing] is called, the syncer starts coroutines to listen
+ * for changes in both legacy user settings and their respective affordance selections. Whenever one
+ * of each pair is changed, the other member of that pair is also updated to match. For example, if
+ * the user turns on "Show device controls", we automatically select the home controls affordance
+ * for the preferred slot. Conversely, when the home controls affordance is unselected by the user,
+ * we set the "Show device controls" setting to "off".
+ *
+ * The class can be configured by updating its list of triplets in the code under [BINDINGS].
+ */
+@SysUISingleton
+class KeyguardQuickAffordanceLegacySettingSyncer
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val secureSettings: SecureSettings,
+ private val selectionsManager: KeyguardQuickAffordanceLocalUserSelectionManager,
+) {
+ companion object {
+ private val BINDINGS =
+ listOf(
+ Binding(
+ settingsKey = Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+ ),
+ Binding(
+ settingsKey = Settings.Secure.LOCKSCREEN_SHOW_WALLET,
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET,
+ ),
+ Binding(
+ settingsKey = Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER,
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER,
+ ),
+ )
+ }
+
+ fun startSyncing(
+ bindings: List<Binding> = BINDINGS,
+ ): Job {
+ return scope.launch { bindings.forEach { binding -> startSyncing(this, binding) } }
+ }
+
+ private fun startSyncing(
+ scope: CoroutineScope,
+ binding: Binding,
+ ) {
+ secureSettings
+ .observerFlow(
+ names = arrayOf(binding.settingsKey),
+ userId = UserHandle.USER_ALL,
+ )
+ .map {
+ isSet(
+ settingsKey = binding.settingsKey,
+ )
+ }
+ .distinctUntilChanged()
+ .onEach { isSet ->
+ if (isSelected(binding.affordanceId) != isSet) {
+ if (isSet) {
+ select(
+ slotId = binding.slotId,
+ affordanceId = binding.affordanceId,
+ )
+ } else {
+ unselect(
+ affordanceId = binding.affordanceId,
+ )
+ }
+ }
+ }
+ .flowOn(backgroundDispatcher)
+ .launchIn(scope)
+
+ selectionsManager.selections
+ .map { it.values.flatten().toSet() }
+ .map { it.contains(binding.affordanceId) }
+ .distinctUntilChanged()
+ .onEach { isSelected ->
+ if (isSet(binding.settingsKey) != isSelected) {
+ set(binding.settingsKey, isSelected)
+ }
+ }
+ .flowOn(backgroundDispatcher)
+ .launchIn(scope)
+ }
+
+ private fun isSelected(
+ affordanceId: String,
+ ): Boolean {
+ return selectionsManager
+ .getSelections() // Map<String, List<String>>
+ .values // Collection<List<String>>
+ .flatten() // List<String>
+ .toSet() // Set<String>
+ .contains(affordanceId)
+ }
+
+ private fun select(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ val affordanceIdsAtSlotId = selectionsManager.getSelections()[slotId] ?: emptyList()
+ selectionsManager.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIdsAtSlotId + listOf(affordanceId),
+ )
+ }
+
+ private fun unselect(
+ affordanceId: String,
+ ) {
+ val currentSelections = selectionsManager.getSelections()
+ val slotIdsContainingAffordanceId =
+ currentSelections
+ .filter { (_, affordanceIds) -> affordanceIds.contains(affordanceId) }
+ .map { (slotId, _) -> slotId }
+
+ slotIdsContainingAffordanceId.forEach { slotId ->
+ val currentAffordanceIds = currentSelections[slotId] ?: emptyList()
+ val affordanceIdsAfterUnselecting =
+ currentAffordanceIds.toMutableList().apply { remove(affordanceId) }
+
+ selectionsManager.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIdsAfterUnselecting,
+ )
+ }
+ }
+
+ private fun isSet(
+ settingsKey: String,
+ ): Boolean {
+ return secureSettings.getIntForUser(
+ settingsKey,
+ 0,
+ UserHandle.USER_CURRENT,
+ ) != 0
+ }
+
+ private suspend fun set(
+ settingsKey: String,
+ isSet: Boolean,
+ ) {
+ withContext(backgroundDispatcher) {
+ secureSettings.putInt(
+ settingsKey,
+ if (isSet) 1 else 0,
+ )
+ }
+ }
+
+ data class Binding(
+ val settingsKey: String,
+ val slotId: String,
+ val affordanceId: String,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
new file mode 100644
index 0000000..0066785
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.content.IntentFilter
+import android.content.SharedPreferences
+import com.android.systemui.R
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?" for the user associated with the
+ * System UI process.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardQuickAffordanceLocalUserSelectionManager
+@Inject
+constructor(
+ @Application context: Context,
+ private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
+ broadcastDispatcher: BroadcastDispatcher,
+) : KeyguardQuickAffordanceSelectionManager {
+
+ private var sharedPrefs: SharedPreferences = instantiateSharedPrefs()
+
+ private val userId: Flow<Int> = conflatedCallbackFlow {
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(newUser, TAG)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ trySendWithFailureLogging(userTracker.userId, TAG)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+
+ private val defaults: Map<String, List<String>> by lazy {
+ context.resources
+ .getStringArray(R.array.config_keyguardQuickAffordanceDefaults)
+ .associate { item ->
+ val splitUp = item.split(SLOT_AFFORDANCES_DELIMITER)
+ check(splitUp.size == 2)
+ val slotId = splitUp[0]
+ val affordanceIds = splitUp[1].split(AFFORDANCE_DELIMITER)
+ slotId to affordanceIds
+ }
+ }
+
+ /**
+ * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
+ * initial value.
+ */
+ private val backupRestorationEvents: Flow<Unit> =
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
+ )
+
+ override val selections: Flow<Map<String, List<String>>> =
+ combine(
+ userId,
+ backupRestorationEvents.onStart {
+ // We emit an initial event to make sure that the combine emits at least once,
+ // even if we never get a Backup & Restore restoration event (which is the most
+ // common case anyway as restoration really only happens on initial device
+ // setup).
+ emit(Unit)
+ }
+ ) { _, _ -> }
+ .flatMapLatest {
+ conflatedCallbackFlow {
+ // We want to instantiate a new SharedPreferences instance each time either the
+ // user ID changes or we have a backup & restore restoration event. The reason
+ // is that our sharedPrefs instance needs to be replaced with a new one as it
+ // depends on the user ID and when the B&R job completes, the backing file is
+ // replaced but the existing instance still has a stale in-memory cache.
+ sharedPrefs = instantiateSharedPrefs()
+
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
+ trySend(getSelections())
+ }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ send(getSelections())
+
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+ }
+
+ override fun getSelections(): Map<String, List<String>> {
+ val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) }
+ val result =
+ slotKeys
+ .associate { key ->
+ val slotId = key.substring(KEY_PREFIX_SLOT.length)
+ val value = sharedPrefs.getString(key, null)
+ val affordanceIds =
+ if (!value.isNullOrEmpty()) {
+ value.split(AFFORDANCE_DELIMITER)
+ } else {
+ emptyList()
+ }
+ slotId to affordanceIds
+ }
+ .toMutableMap()
+
+ // If the result map is missing keys, it means that the system has never set anything for
+ // those slots. This is where we need examine our defaults and see if there should be a
+ // default value for the affordances in the slot IDs that are missing from the result.
+ //
+ // Once the user makes any selection for a slot, even when they select "None", this class
+ // will persist a key for that slot ID. In the case of "None", it will have a value of the
+ // empty string. This is why this system works.
+ defaults.forEach { (slotId, affordanceIds) ->
+ if (!result.containsKey(slotId)) {
+ result[slotId] = affordanceIds
+ }
+ }
+
+ return result
+ }
+
+ override fun setSelections(
+ slotId: String,
+ affordanceIds: List<String>,
+ ) {
+ val key = "$KEY_PREFIX_SLOT$slotId"
+ val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER)
+ sharedPrefs.edit().putString(key, value).apply()
+ }
+
+ private fun instantiateSharedPrefs(): SharedPreferences {
+ return userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId,
+ )
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordancePrimaryUserSelectionManager"
+ const val FILE_NAME = "quick_affordance_selections"
+ private const val KEY_PREFIX_SLOT = "slot_"
+ private const val SLOT_AFFORDANCES_DELIMITER = ":"
+ private const val AFFORDANCE_DELIMITER = ","
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt
new file mode 100644
index 0000000..727a813
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClientImpl
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+
+interface KeyguardQuickAffordanceProviderClientFactory {
+ fun create(): KeyguardQuickAffordanceProviderClient
+}
+
+class KeyguardQuickAffordanceProviderClientFactoryImpl
+@Inject
+constructor(
+ private val userTracker: UserTracker,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : KeyguardQuickAffordanceProviderClientFactory {
+ override fun create(): KeyguardQuickAffordanceProviderClient {
+ return KeyguardQuickAffordanceProviderClientImpl(
+ context = userTracker.userContext,
+ backgroundDispatcher = backgroundDispatcher,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt
new file mode 100644
index 0000000..8ffef25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?" for users associated with other System
+ * UI processes.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardQuickAffordanceRemoteUserSelectionManager
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val userTracker: UserTracker,
+ private val clientFactory: KeyguardQuickAffordanceProviderClientFactory,
+ private val userHandle: UserHandle,
+) : KeyguardQuickAffordanceSelectionManager {
+
+ private val userId: Flow<Int> = conflatedCallbackFlow {
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(newUser, TAG)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ trySendWithFailureLogging(userTracker.userId, TAG)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+
+ private val clientOrNull: StateFlow<KeyguardQuickAffordanceProviderClient?> =
+ userId
+ .distinctUntilChanged()
+ .map { selectedUserId ->
+ if (userHandle.isSystem && userHandle.identifier != selectedUserId) {
+ clientFactory.create()
+ } else {
+ null
+ }
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+
+ private val _selections: StateFlow<Map<String, List<String>>> =
+ clientOrNull
+ .flatMapLatest { client ->
+ client?.observeSelections()?.map { selections ->
+ buildMap<String, List<String>> {
+ selections.forEach { selection ->
+ val slotId = selection.slotId
+ val affordanceIds = (get(slotId) ?: emptyList()).toMutableList()
+ affordanceIds.add(selection.affordanceId)
+ put(slotId, affordanceIds)
+ }
+ }
+ }
+ ?: emptyFlow()
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = emptyMap(),
+ )
+
+ override val selections: Flow<Map<String, List<String>>> = _selections
+
+ override fun getSelections(): Map<String, List<String>> {
+ return _selections.value
+ }
+
+ override fun setSelections(slotId: String, affordanceIds: List<String>) {
+ clientOrNull.value?.let { client ->
+ scope.launch {
+ client.deleteAllSelections(slotId = slotId)
+ affordanceIds.forEach { affordanceId ->
+ client.insertSelection(slotId = slotId, affordanceId = affordanceId)
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceMultiUserSelectionManager"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
index 9c9354f..21fffed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -17,46 +17,33 @@
package com.android.systemui.keyguard.data.quickaffordance
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
/**
- * Manages and provides access to the current "selections" of keyguard quick affordances, answering
- * the question "which affordances should the keyguard show?".
+ * Defines interface for classes that manage and provide access to the current "selections" of
+ * keyguard quick affordances, answering the question "which affordances should the keyguard show?".
*/
-@SysUISingleton
-class KeyguardQuickAffordanceSelectionManager @Inject constructor() {
-
- // TODO(b/254858695): implement a persistence layer (database).
- private val _selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+interface KeyguardQuickAffordanceSelectionManager {
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
- val selections: Flow<Map<String, List<String>>> = _selections.asStateFlow()
+ val selections: Flow<Map<String, List<String>>>
/**
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
* descending priority order.
*/
- suspend fun getSelections(): Map<String, List<String>> {
- return _selections.value
- }
+ fun getSelections(): Map<String, List<String>>
/**
* Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
* IDs should be descending priority order.
*/
- suspend fun setSelections(
+ fun setSelections(
slotId: String,
affordanceIds: List<String>,
- ) {
- // Must make a copy of the map and update it, otherwise, the MutableStateFlow won't emit
- // when we set its value to the same instance of the original map, even if we change the
- // map by updating the value of one of its keys.
- val copy = _selections.value.toMutableMap()
- copy[slotId] = affordanceIds
- _selections.value = copy
+ )
+
+ companion object {
+ const val FILE_NAME = "quick_affordance_selections"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index 11f72ff..a96ce77 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -36,7 +36,7 @@
class QrCodeScannerKeyguardQuickAffordanceConfig
@Inject
constructor(
- @Application context: Context,
+ @Application private val context: Context,
private val controller: QRCodeScannerController,
) : KeyguardQuickAffordanceConfig {
@@ -75,6 +75,28 @@
}
}
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ return when {
+ !controller.isAvailableOnDevice ->
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ !controller.isAbleToOpenCameraApp ->
+ KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+ instructions =
+ listOf(
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_message,
+ pickerName,
+ ),
+ context.getString(
+ R.string
+ .keyguard_affordance_enablement_dialog_qr_scanner_instruction
+ ),
+ ),
+ )
+ else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ }
+ }
+
override fun onTriggered(
expandable: Expandable?,
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 303e6a1..beb20ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -18,10 +18,12 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
+import android.content.Intent
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsError
import android.service.quickaccesswallet.GetWalletCardsResponse
import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
import android.util.Log
import com.android.systemui.R
import com.android.systemui.animation.Expandable
@@ -31,25 +33,27 @@
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.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.componentName
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.wallet.controller.QuickAccessWalletController
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.suspendCancellableCoroutine
/** Quick access wallet quick affordance data source. */
@SysUISingleton
class QuickAccessWalletKeyguardQuickAffordanceConfig
@Inject
constructor(
- @Application context: Context,
+ @Application private val context: Context,
private val walletController: QuickAccessWalletController,
private val activityStarter: ActivityStarter,
) : KeyguardQuickAffordanceConfig {
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
- override val pickerName = context.getString(R.string.accessibility_wallet_button)
+ override val pickerName: String = context.getString(R.string.accessibility_wallet_button)
override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen
@@ -58,10 +62,11 @@
val callback =
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ val hasCards = response?.walletCards?.isNotEmpty() == true
trySendWithFailureLogging(
state(
isFeatureEnabled = walletController.isWalletEnabled,
- hasCard = response?.walletCards?.isNotEmpty() == true,
+ hasCard = hasCards,
tileIcon = walletController.walletClient.tileIcon,
),
TAG,
@@ -93,6 +98,44 @@
}
}
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ return when {
+ !walletController.isWalletEnabled ->
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ walletController.walletClient.tileIcon == null || queryCards().isEmpty() -> {
+ val componentName =
+ walletController.walletClient.createWalletSettingsIntent().toComponentName()
+ val actionText =
+ if (componentName != null) {
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_action_template,
+ pickerName,
+ )
+ } else {
+ null
+ }
+ KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+ instructions =
+ listOf(
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_message,
+ pickerName,
+ ),
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_wallet_instruction_1
+ ),
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_wallet_instruction_2
+ ),
+ ),
+ actionText = actionText,
+ actionComponentName = componentName,
+ )
+ }
+ else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default
+ }
+ }
+
override fun onTriggered(
expandable: Expandable?,
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
@@ -104,6 +147,24 @@
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
+ private suspend fun queryCards(): List<WalletCard> {
+ return suspendCancellableCoroutine { continuation ->
+ val callback =
+ object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ continuation.resumeWith(
+ Result.success(response?.walletCards ?: emptyList())
+ )
+ }
+
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ continuation.resumeWith(Result.success(emptyList()))
+ }
+ }
+ walletController.queryWalletCards(callback)
+ }
+ }
+
private fun state(
isFeatureEnabled: Boolean,
hasCard: Boolean,
@@ -125,6 +186,14 @@
}
}
+ private fun Intent?.toComponentName(): String? {
+ if (this == null) {
+ return null
+ }
+
+ return componentName(packageName = `package`, action = action)
+ }
+
companion object {
private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 95f614f..e3f5e90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -17,42 +17,94 @@
package com.android.systemui.keyguard.data.repository
+import android.content.Context
+import android.os.UserHandle
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.settings.UserTracker
+import java.io.PrintWriter
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
/** Abstracts access to application state related to keyguard quick affordances. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardQuickAffordanceRepository
@Inject
constructor(
+ @Application private val appContext: Context,
@Application private val scope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
- private val selectionManager: KeyguardQuickAffordanceSelectionManager,
+ private val localUserSelectionManager: KeyguardQuickAffordanceLocalUserSelectionManager,
+ private val remoteUserSelectionManager: KeyguardQuickAffordanceRemoteUserSelectionManager,
+ private val userTracker: UserTracker,
+ legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer,
private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
+ dumpManager: DumpManager,
+ userHandle: UserHandle,
) {
+ private val userId: Flow<Int> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(newUser, TAG)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ trySendWithFailureLogging(userTracker.userId, TAG)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+
+ private val selectionManager: StateFlow<KeyguardQuickAffordanceSelectionManager> =
+ userId
+ .distinctUntilChanged()
+ .map { selectedUserId ->
+ if (userHandle.identifier == selectedUserId) {
+ localUserSelectionManager
+ } else {
+ remoteUserSelectionManager
+ }
+ }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.Eagerly,
+ initialValue = localUserSelectionManager,
+ )
+
/**
* List of [KeyguardQuickAffordanceConfig] instances of the affordances at the slot with the
* given ID. The configs are sorted in descending priority order.
*/
val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> =
- selectionManager.selections
- .map { selectionsBySlotId ->
- selectionsBySlotId.mapValues { (_, selections) ->
- configs.filter { selections.contains(it.key) }
+ selectionManager
+ .flatMapLatest { selectionManager ->
+ selectionManager.selections.map { selectionsBySlotId ->
+ selectionsBySlotId.mapValues { (_, selections) ->
+ configs.filter { selections.contains(it.key) }
+ }
}
}
.stateIn(
@@ -61,12 +113,41 @@
initialValue = emptyMap(),
)
+ private val _slotPickerRepresentations: List<KeyguardSlotPickerRepresentation> by lazy {
+ fun parseSlot(unparsedSlot: String): Pair<String, Int> {
+ val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER)
+ check(split.size == 2)
+ val slotId = split[0]
+ val slotCapacity = split[1].toInt()
+ return slotId to slotCapacity
+ }
+
+ val unparsedSlots =
+ appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots)
+
+ val seenSlotIds = mutableSetOf<String>()
+ unparsedSlots.mapNotNull { unparsedSlot ->
+ val (slotId, slotCapacity) = parseSlot(unparsedSlot)
+ check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" }
+ seenSlotIds.add(slotId)
+ KeyguardSlotPickerRepresentation(
+ id = slotId,
+ maxSelectedAffordances = slotCapacity,
+ )
+ }
+ }
+
+ init {
+ legacySettingSyncer.startSyncing()
+ dumpManager.registerDumpable("KeyguardQuickAffordances", Dumpster())
+ }
+
/**
* Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
* slot with the given ID. The configs are sorted in descending priority order.
*/
- suspend fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
- val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList())
+ fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+ val selections = selectionManager.value.getSelections().getOrDefault(slotId, emptyList())
return configs.filter { selections.contains(it.key) }
}
@@ -74,8 +155,8 @@
* Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
* are sorted in descending priority order.
*/
- suspend fun getSelections(): Map<String, List<String>> {
- return selectionManager.getSelections()
+ fun getSelections(): Map<String, List<String>> {
+ return selectionManager.value.getSelections()
}
/**
@@ -86,27 +167,39 @@
slotId: String,
affordanceIds: List<String>,
) {
- scope.launch(backgroundDispatcher) {
- selectionManager.setSelections(
- slotId = slotId,
- affordanceIds = affordanceIds,
- )
- }
+ selectionManager.value.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIds,
+ )
}
/**
- * Returns the list of representation objects for all known affordances, regardless of what is
- * selected. This is useful for building experiences like the picker/selector or user settings
- * so the user can see everything that can be selected in a menu.
+ * Returns the list of representation objects for all known, device-available affordances,
+ * regardless of what is selected. This is useful for building experiences like the
+ * picker/selector or user settings so the user can see everything that can be selected in a
+ * menu.
*/
- fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
- return configs.map { config ->
- KeyguardQuickAffordancePickerRepresentation(
- id = config.key,
- name = config.pickerName,
- iconResourceId = config.pickerIconResourceId,
- )
- }
+ suspend fun getAffordancePickerRepresentations():
+ List<KeyguardQuickAffordancePickerRepresentation> {
+ return configs
+ .associateWith { config -> config.getPickerScreenState() }
+ .filterNot { (_, pickerState) ->
+ pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+ .map { (config, pickerState) ->
+ val disabledPickerState =
+ pickerState as? KeyguardQuickAffordanceConfig.PickerScreenState.Disabled
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config.key,
+ name = config.pickerName,
+ iconResourceId = config.pickerIconResourceId,
+ isEnabled =
+ pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.Default,
+ instructions = disabledPickerState?.instructions,
+ actionText = disabledPickerState?.actionText,
+ actionComponentName = disabledPickerState?.actionComponentName,
+ )
+ }
}
/**
@@ -115,14 +208,35 @@
* each slot and select which affordance(s) is/are installed in each slot on the keyguard.
*/
fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
- // TODO(b/256195304): source these from a config XML file.
- return listOf(
- KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- ),
- KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- ),
- )
+ return _slotPickerRepresentations
+ }
+
+ private inner class Dumpster : Dumpable {
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ val slotPickerRepresentations = getSlotPickerRepresentations()
+ val selectionsBySlotId = getSelections()
+ pw.println("Slots & selections:")
+ slotPickerRepresentations.forEach { slotPickerRepresentation ->
+ val slotId = slotPickerRepresentation.id
+ val capacity = slotPickerRepresentation.maxSelectedAffordances
+ val affordanceIds = selectionsBySlotId[slotId]
+
+ val selectionText =
+ if (!affordanceIds.isNullOrEmpty()) {
+ ": ${affordanceIds.joinToString(", ")}"
+ } else {
+ " is empty"
+ }
+
+ pw.println(" $slotId$selectionText (capacity = $capacity)")
+ }
+ pw.println("Available affordances on device:")
+ configs.forEach { config -> pw.println(" ${config.key} (\"${config.pickerName}\")") }
+ }
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceRepository"
+ private const val SLOT_CONFIG_DELIMITER = ":"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 9d5d8bb..796f2b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -23,9 +23,14 @@
import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.doze.DozeHost
+import com.android.systemui.doze.DozeMachine
+import com.android.systemui.doze.DozeTransitionCallback
+import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -108,6 +113,9 @@
*/
val dozeAmount: Flow<Float>
+ /** Doze state information, as it transitions */
+ val dozeTransitionModel: Flow<DozeTransitionModel>
+
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState>
@@ -154,6 +162,7 @@
biometricUnlockController: BiometricUnlockController,
private val keyguardStateController: KeyguardStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val dozeTransitionListener: DozeTransitionListener,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -286,6 +295,37 @@
awaitClose { statusBarStateController.removeCallback(callback) }
}
+ override val dozeTransitionModel: Flow<DozeTransitionModel> = conflatedCallbackFlow {
+ val callback =
+ object : DozeTransitionCallback {
+ override fun onDozeTransition(
+ oldState: DozeMachine.State,
+ newState: DozeMachine.State
+ ) {
+ trySendWithFailureLogging(
+ DozeTransitionModel(
+ from = dozeMachineStateToModel(oldState),
+ to = dozeMachineStateToModel(newState),
+ ),
+ TAG,
+ "doze transition model"
+ )
+ }
+ }
+
+ dozeTransitionListener.addCallback(callback)
+ trySendWithFailureLogging(
+ DozeTransitionModel(
+ from = dozeMachineStateToModel(dozeTransitionListener.oldState),
+ to = dozeMachineStateToModel(dozeTransitionListener.newState),
+ ),
+ TAG,
+ "initial doze transition model"
+ )
+
+ awaitClose { dozeTransitionListener.removeCallback(callback) }
+ }
+
override fun isKeyguardShowing(): Boolean {
return keyguardStateController.isShowing
}
@@ -407,6 +447,25 @@
}
}
+ private fun dozeMachineStateToModel(state: DozeMachine.State): DozeStateModel {
+ return when (state) {
+ DozeMachine.State.UNINITIALIZED -> DozeStateModel.UNINITIALIZED
+ DozeMachine.State.INITIALIZED -> DozeStateModel.INITIALIZED
+ DozeMachine.State.DOZE -> DozeStateModel.DOZE
+ DozeMachine.State.DOZE_SUSPEND_TRIGGERS -> DozeStateModel.DOZE_SUSPEND_TRIGGERS
+ DozeMachine.State.DOZE_AOD -> DozeStateModel.DOZE_AOD
+ DozeMachine.State.DOZE_REQUEST_PULSE -> DozeStateModel.DOZE_REQUEST_PULSE
+ DozeMachine.State.DOZE_PULSING -> DozeStateModel.DOZE_PULSING
+ DozeMachine.State.DOZE_PULSING_BRIGHT -> DozeStateModel.DOZE_PULSING_BRIGHT
+ DozeMachine.State.DOZE_PULSE_DONE -> DozeStateModel.DOZE_PULSE_DONE
+ DozeMachine.State.FINISH -> DozeStateModel.FINISH
+ DozeMachine.State.DOZE_AOD_PAUSED -> DozeStateModel.DOZE_AOD_PAUSED
+ DozeMachine.State.DOZE_AOD_PAUSING -> DozeStateModel.DOZE_AOD_PAUSING
+ DozeMachine.State.DOZE_AOD_DOCKED -> DozeStateModel.DOZE_AOD_DOCKED
+ else -> throw IllegalArgumentException("Invalid DozeMachine.State: state")
+ }
+ }
+
companion object {
private const val TAG = "KeyguardRepositoryImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
new file mode 100644
index 0000000..0e865ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.settings.UserFileManagerImpl
+
+/** Handles backup & restore for keyguard quick affordances. */
+class KeyguardQuickAffordanceBackupHelper(
+ context: Context,
+ userId: Int,
+) :
+ SharedPreferencesBackupHelper(
+ context,
+ if (UserFileManagerImpl.isPrimaryUser(userId)) {
+ KeyguardQuickAffordanceSelectionManager.FILE_NAME
+ } else {
+ UserFileManagerImpl.secondaryUserFile(
+ context = context,
+ fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME,
+ directoryName = UserFileManagerImpl.SHARED_PREFS,
+ userId = userId,
+ )
+ .also { UserFileManagerImpl.ensureParentDirExists(it) }
+ .toString()
+ }
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
index e5521c7..2dbacd5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -21,10 +21,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isSleepingOrStartingToSleep
-import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -39,27 +38,24 @@
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("AOD<->LOCKSCREEN") {
+) : TransitionInteractor(AodLockscreenTransitionInteractor::class.simpleName!!) {
override fun start() {
+ listenForTransitionToAodFromLockscreen()
+ listenForTransitionToLockscreenFromAod()
+ }
+
+ private fun listenForTransitionToAodFromLockscreen() {
scope.launch {
- /*
- * Listening to the startedKeyguardTransitionStep (last started step) allows this code
- * to interrupt an active transition, as long as they were either going to LOCKSCREEN or
- * AOD state. One example is when the user presses the power button in the middle of an
- * active transition.
- */
- keyguardInteractor.wakefulnessState
+ keyguardInteractor
+ .dozeTransitionTo(DozeStateModel.DOZE_AOD)
.sample(
keyguardTransitionInteractor.startedKeyguardTransitionStep,
{ a, b -> Pair(a, b) }
)
.collect { pair ->
- val (wakefulnessState, lastStartedStep) = pair
- if (
- isSleepingOrStartingToSleep(wakefulnessState) &&
- lastStartedStep.to == KeyguardState.LOCKSCREEN
- ) {
+ val (dozeToAod, lastStartedStep) = pair
+ if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
@@ -68,10 +64,22 @@
getAnimator(),
)
)
- } else if (
- isWakingOrStartingToWake(wakefulnessState) &&
- lastStartedStep.to == KeyguardState.AOD
- ) {
+ }
+ }
+ }
+ }
+
+ private fun listenForTransitionToLockscreenFromAod() {
+ scope.launch {
+ keyguardInteractor
+ .dozeTransitionTo(DozeStateModel.FINISH)
+ .sample(
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ { a, b -> Pair(a, b) }
+ )
+ .collect { pair ->
+ val (dozeToAod, lastStartedStep) = pair
+ if (lastStartedStep.to == KeyguardState.AOD) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
index 7e01db3..2a220fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -40,7 +40,7 @@
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("AOD->GONE") {
+) : TransitionInteractor(AodToGoneTransitionInteractor::class.simpleName!!) {
private val wakeAndUnlockModes =
setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
index dd29673..056c44d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
@@ -40,7 +40,7 @@
private val shadeRepository: ShadeRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
-) : TransitionInteractor("BOUNCER->GONE") {
+) : TransitionInteractor(BouncerToGoneTransitionInteractor::class.simpleName!!) {
private var transitionId: UUID? = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
index c44cda4..9cbf9ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingLockscreenTransitionInteractor.kt
@@ -21,12 +21,14 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@SysUISingleton
@@ -37,32 +39,43 @@
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor("DREAMING<->LOCKSCREEN") {
+) : TransitionInteractor(DreamingLockscreenTransitionInteractor::class.simpleName!!) {
override fun start() {
scope.launch {
keyguardInteractor.isDreaming
- .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
- .collect { pair ->
- val (isDreaming, keyguardState) = pair
- if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.LOCKSCREEN,
- KeyguardState.DREAMING,
- getAnimator(),
+ .sample(
+ combine(
+ keyguardInteractor.dozeTransitionModel,
+ keyguardTransitionInteractor.finishedKeyguardState
+ ) { a, b -> Pair(a, b) },
+ { a, bc -> Triple(a, bc.first, bc.second) }
+ )
+ .collect { triple ->
+ val (isDreaming, dozeTransitionModel, keyguardState) = triple
+ // Dozing/AOD and dreaming have overlapping events. If the state remains in
+ // FINISH, it means that doze mode is not running and DREAMING is ok to
+ // commence.
+ if (dozeTransitionModel.to == DozeStateModel.FINISH) {
+ if (isDreaming && keyguardState == KeyguardState.LOCKSCREEN) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.DREAMING,
+ getAnimator(),
+ )
)
- )
- } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- name,
- KeyguardState.DREAMING,
- KeyguardState.LOCKSCREEN,
- getAnimator(),
+ } else if (!isDreaming && keyguardState == KeyguardState.DREAMING) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.DREAMING,
+ KeyguardState.LOCKSCREEN,
+ getAnimator(),
+ )
)
- )
+ }
}
}
}
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 5a1c702..7cfd117 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
@@ -20,10 +20,13 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -41,6 +44,8 @@
val dozeAmount: Flow<Float> = repository.dozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
+ /** Doze transition information. */
+ val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
/**
* Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
* but not vice-versa.
@@ -62,6 +67,10 @@
*/
val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
+ fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> {
+ return dozeTransitionModel.filter { it.to == state }
+ }
+
fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 92caa89..748c6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -28,12 +28,14 @@
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardPickerFlag
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
@@ -117,7 +119,7 @@
*
* @return `true` if the affordance was selected successfully; `false` otherwise.
*/
- suspend fun select(slotId: String, affordanceId: String): Boolean {
+ fun select(slotId: String, affordanceId: String): Boolean {
check(isUsingRepository)
val slots = repository.get().getSlotPickerRepresentations()
@@ -152,7 +154,7 @@
* @return `true` if the affordance was successfully removed; `false` otherwise (for example, if
* the affordance was not on the slot to begin with).
*/
- suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
+ fun unselect(slotId: String, affordanceId: String?): Boolean {
check(isUsingRepository)
val slots = repository.get().getSlotPickerRepresentations()
@@ -187,12 +189,16 @@
}
/** Returns affordance IDs indexed by slot ID, for all known slots. */
- suspend fun getSelections(): Map<String, List<String>> {
- check(isUsingRepository)
-
+ suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
+ val slots = repository.get().getSlotPickerRepresentations()
val selections = repository.get().getSelections()
- return repository.get().getSlotPickerRepresentations().associate { slotRepresentation ->
- slotRepresentation.id to (selections[slotRepresentation.id] ?: emptyList())
+ val affordanceById =
+ getAffordancePickerRepresentations().associateBy { affordance -> affordance.id }
+ return slots.associate { slot ->
+ slot.id to
+ (selections[slot.id] ?: emptyList()).mapNotNull { affordanceId ->
+ affordanceById[affordanceId]
+ }
}
}
@@ -302,9 +308,8 @@
return Pair(splitUp[0], splitUp[1])
}
- fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
- check(isUsingRepository)
-
+ suspend fun getAffordancePickerRepresentations():
+ List<KeyguardQuickAffordancePickerRepresentation> {
return repository.get().getAffordancePickerRepresentations()
}
@@ -314,6 +319,15 @@
return repository.get().getSlotPickerRepresentations()
}
+ fun getPickerFlags(): List<KeyguardPickerFlag> {
+ return listOf(
+ KeyguardPickerFlag(
+ name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED,
+ value = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES),
+ )
+ )
+ }
+
companion object {
private const val TAG = "KeyguardQuickAffordanceInteractor"
private const val DELIMITER = "::"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index cca2d56..3bb8241 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -44,7 +44,7 @@
private val shadeRepository: ShadeRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor
-) : TransitionInteractor("LOCKSCREEN<->BOUNCER") {
+) : TransitionInteractor(LockscreenBouncerTransitionInteractor::class.simpleName!!) {
private var transitionId: UUID? = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index fcd653b..2cf5fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -17,10 +17,12 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.res.ColorStateList
+import android.hardware.biometrics.BiometricSourceType
import android.os.Handler
import android.os.Trace
import android.os.UserHandle
import android.os.UserManager
+import android.view.View
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.DejankUtils
@@ -70,7 +72,7 @@
KeyguardUpdateMonitor.getCurrentUser()
) &&
!needsFullscreenBouncer() &&
- !keyguardUpdateMonitor.userNeedsStrongAuth() &&
+ keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
!keyguardBypassController.bypassEnabled
/** Runnable to show the primary bouncer. */
@@ -84,6 +86,7 @@
)
)
repository.setPrimaryShowingSoon(false)
+ primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
}
val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
@@ -117,7 +120,6 @@
@JvmOverloads
fun show(isScrimmed: Boolean) {
// Reset some states as we show the bouncer.
- repository.setShowMessage(null)
repository.setOnScreenTurnedOff(false)
repository.setKeyguardAuthenticated(null)
repository.setPrimaryHide(false)
@@ -183,6 +185,7 @@
repository.setPrimaryVisible(false)
repository.setPrimaryHide(true)
repository.setPrimaryShow(null)
+ primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
Trace.endSection()
}
@@ -272,9 +275,9 @@
repository.setKeyguardAuthenticated(null)
}
- /** Notify that view visibility has changed. */
- fun notifyBouncerVisibilityHasChanged(visibility: Int) {
- primaryBouncerCallbackInteractor.dispatchVisibilityChanged(visibility)
+ /** Notifies that the message was shown. */
+ fun onMessageShown() {
+ repository.setShowMessage(null)
}
/** Notify that the resources have been updated */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
new file mode 100644
index 0000000..7039188
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeStateModel.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Model device doze states. */
+enum class DozeStateModel {
+ /** Default state. Transition to INITIALIZED to get Doze going. */
+ UNINITIALIZED,
+ /** Doze components are set up. Followed by transition to DOZE or DOZE_AOD. */
+ INITIALIZED,
+ /** Regular doze. Device is asleep and listening for pulse triggers. */
+ DOZE,
+ /** Deep doze. Device is asleep and is not listening for pulse triggers. */
+ DOZE_SUSPEND_TRIGGERS,
+ /** Always-on doze. Device is asleep, showing UI and listening for pulse triggers. */
+ DOZE_AOD,
+ /** Pulse has been requested. Device is awake and preparing UI */
+ DOZE_REQUEST_PULSE,
+ /** Pulse is showing. Device is awake and showing UI. */
+ DOZE_PULSING,
+ /** Pulse is showing with bright wallpaper. Device is awake and showing UI. */
+ DOZE_PULSING_BRIGHT,
+ /** Pulse is done showing. Followed by transition to DOZE or DOZE_AOD. */
+ DOZE_PULSE_DONE,
+ /** Doze is done. DozeService is finished. */
+ FINISH,
+ /** AOD, but the display is temporarily off. */
+ DOZE_AOD_PAUSED,
+ /** AOD, prox is near, transitions to DOZE_AOD_PAUSED after a timeout. */
+ DOZE_AOD_PAUSING,
+ /** Always-on doze. Device is awake, showing docking UI and listening for pulse triggers. */
+ DOZE_AOD_DOCKED
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt
new file mode 100644
index 0000000..e96ace2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DozeTransitionModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Doze transition information. */
+data class DozeTransitionModel(
+ val from: DozeStateModel = DozeStateModel.UNINITIALIZED,
+ val to: DozeStateModel = DozeStateModel.UNINITIALIZED,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt
new file mode 100644
index 0000000..a7a5957
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/** Represents a flag that's consumed by the settings or wallpaper picker app. */
+data class KeyguardPickerFlag(
+ val name: String,
+ val value: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
index a56bc90..7d13359 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
@@ -27,4 +27,22 @@
val id: String,
val name: String,
@DrawableRes val iconResourceId: Int,
+
+ /** Whether this quick affordance is enabled. */
+ val isEnabled: Boolean = true,
+
+ /** If not enabled, the list of user-visible steps to re-enable it. */
+ val instructions: List<String>? = null,
+
+ /**
+ * If not enabled, an optional label for a button that takes the user to a destination where
+ * they can re-enable it.
+ */
+ val actionText: String? = null,
+
+ /**
+ * If not enabled, an optional component name (package and action) for a button that takes the
+ * user to a destination where they can re-enable it.
+ */
+ val actionComponentName: String? = null,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 3276b6d..cbe512f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.binder
+import android.graphics.drawable.Animatable2
import android.util.Size
import android.util.TypedValue
import android.view.View
@@ -27,12 +28,11 @@
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.keyguard.LockIconViewController
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.Interpolators
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
@@ -73,7 +73,8 @@
fun onConfigurationChanged()
/**
- * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
+ * Returns whether the keyguard bottom area should be constrained to the top of the lock
+ * icon
*/
fun shouldConstrainToTopOfLockIcon(): Boolean
}
@@ -217,7 +218,7 @@
}
override fun shouldConstrainToTopOfLockIcon(): Boolean =
- viewModel.shouldConstrainToTopOfLockIcon()
+ viewModel.shouldConstrainToTopOfLockIcon()
}
}
@@ -248,6 +249,27 @@
IconViewBinder.bind(viewModel.icon, view)
+ (view.drawable as? Animatable2)?.let { animatable ->
+ (viewModel.icon as? Icon.Resource)?.res?.let { iconResourceId ->
+ // Always start the animation (we do call stop() below, if we need to skip it).
+ animatable.start()
+
+ if (view.tag != iconResourceId) {
+ // Here when we haven't run the animation on a previous update.
+ //
+ // Save the resource ID for next time, so we know not to re-animate the same
+ // animation again.
+ view.tag = iconResourceId
+ } else {
+ // Here when we've already done this animation on a previous update and want to
+ // skip directly to the final frame of the animation to avoid running it.
+ //
+ // By calling stop after start, we go to the final frame of the animation.
+ animatable.stop()
+ }
+ }
+ }
+
view.isActivated = viewModel.isActivated
view.drawable.setTint(
Utils.getColorAttrDefaultColor(
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 7739a45..f772b17 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
@@ -32,7 +32,6 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@@ -89,7 +88,7 @@
}
}
view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
try {
viewModel.setBouncerViewDelegate(delegate)
launch {
@@ -153,7 +152,6 @@
val visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
view.visibility = visibility
hostViewController.onBouncerVisibilityChanged(visibility)
- viewModel.notifyBouncerVisibilityHasChanged(visibility)
}
}
@@ -182,6 +180,7 @@
launch {
viewModel.bouncerShowMessage.collect {
hostViewController.showMessage(it.message, it.colorStateList)
+ viewModel.onMessageShown()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 526ae74..e5d4e49 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -72,10 +72,6 @@
/** Observe whether screen is turned off. */
val screenTurnedOff: Flow<Unit> = interactor.screenTurnedOff
- /** Notify that view visibility has changed. */
- fun notifyBouncerVisibilityHasChanged(visibility: Int) {
- return interactor.notifyBouncerVisibilityHasChanged(visibility)
- }
/** Observe whether we want to update resources. */
fun notifyUpdateResources() {
interactor.notifyUpdatedResources()
@@ -86,6 +82,11 @@
interactor.notifyKeyguardAuthenticatedHandled()
}
+ /** Notifies that the message was shown. */
+ fun onMessageShown() {
+ interactor.onMessageShown()
+ }
+
/** Observe whether back button is enabled. */
fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> {
return interactor.isBackButtonEnabled.map { enabled ->
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
index f9e341c..d6e29e0 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt
@@ -16,9 +16,9 @@
package com.android.systemui.log
-import android.app.ActivityManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogcatEchoTracker
@@ -29,15 +29,6 @@
private val dumpManager: DumpManager,
private val logcatEchoTracker: LogcatEchoTracker
) {
- /* limitiometricMessageDeferralLogger the size of maxPoolSize for low ram (Go) devices */
- private fun adjustMaxSize(requestedMaxSize: Int): Int {
- return if (ActivityManager.isLowRamDeviceStatic()) {
- minOf(requestedMaxSize, 20) /* low ram max log size*/
- } else {
- requestedMaxSize
- }
- }
-
@JvmOverloads
fun create(
name: String,
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt
new file mode 100644
index 0000000..619eac1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBufferHelper.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log
+
+import android.app.ActivityManager
+
+class LogBufferHelper {
+ companion object {
+ /** If necessary, returns a limited maximum size for low ram (Go) devices */
+ fun adjustMaxSize(requestedMaxSize: Int): Int {
+ return if (ActivityManager.isLowRamDeviceStatic()) {
+ minOf(requestedMaxSize, 20) /* low ram max log size*/
+ } else {
+ requestedMaxSize
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
rename to packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
index eeadf40..4b774d3 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricMessagesLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BiometricLog.java
@@ -29,5 +29,5 @@
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
-public @interface BiometricMessagesLog {
+public @interface BiometricLog {
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 9adb855..74d5043 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -300,9 +300,9 @@
*/
@Provides
@SysUISingleton
- @BiometricMessagesLog
- public static LogBuffer provideBiometricMessagesLogBuffer(LogBufferFactory factory) {
- return factory.create("BiometricMessagesLog", 150);
+ @BiometricLog
+ public static LogBuffer provideBiometricLogBuffer(LogBufferFactory factory) {
+ return factory.create("BiometricLog", 200);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
new file mode 100644
index 0000000..bb04b6b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.table
+
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * An interface that enables logging the difference between values in table format.
+ *
+ * Many objects that we want to log are data-y objects with a collection of fields. When logging
+ * these objects, we want to log each field separately. This allows ABT (Android Bug Tool) to easily
+ * highlight changes in individual fields.
+ *
+ * See [TableLogBuffer].
+ */
+interface Diffable<T> {
+ /**
+ * Finds the differences between [prevVal] and this object and logs those diffs to [row].
+ *
+ * Each implementer should determine which individual fields have changed between [prevVal] and
+ * this object, and only log the fields that have actually changed. This helps save buffer
+ * space.
+ *
+ * For example, if:
+ * - prevVal = Object(val1=100, val2=200, val3=300)
+ * - this = Object(val1=100, val2=200, val3=333)
+ *
+ * Then only the val3 change should be logged.
+ */
+ fun logDiffs(prevVal: T, row: TableRowLogger)
+
+ /**
+ * Logs all the relevant fields of this object to [row].
+ *
+ * As opposed to [logDiffs], this method should log *all* fields.
+ *
+ * Implementation is optional. This method will only be used with [logDiffsForTable] in order to
+ * fully log the initial value of the flow.
+ */
+ fun logFull(row: TableRowLogger) {}
+}
+
+/**
+ * Each time the flow is updated with a new value, logs the differences between the previous value
+ * and the new value to the given [tableLogBuffer].
+ *
+ * The new value's [Diffable.logDiffs] method will be used to log the differences to the table.
+ *
+ * @param columnPrefix a prefix that will be applied to every column name that gets logged.
+ */
+fun <T : Diffable<T>> Flow<T>.logDiffsForTable(
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String,
+ initialValue: T,
+): Flow<T> {
+ // Fully log the initial value to the table.
+ val getInitialValue = {
+ tableLogBuffer.logChange(columnPrefix) { row -> initialValue.logFull(row) }
+ initialValue
+ }
+ return this.pairwiseBy(getInitialValue) { prevVal: T, newVal: T ->
+ tableLogBuffer.logDiffs(columnPrefix, prevVal, newVal)
+ newVal
+ }
+}
+
+/**
+ * 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].
+ */
+fun Flow<Boolean>.logDiffsForTable(
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String,
+ columnName: String,
+ initialValue: Boolean,
+): Flow<Boolean> {
+ val initialValueFun = {
+ tableLogBuffer.logChange(columnPrefix, columnName, initialValue)
+ initialValue
+ }
+ return this.pairwiseBy(initialValueFun) { prevVal, newVal: Boolean ->
+ if (prevVal != newVal) {
+ tableLogBuffer.logChange(columnPrefix, columnName, newVal)
+ }
+ 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
new file mode 100644
index 0000000..68c297f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.table
+
+/**
+ * A object used with [TableLogBuffer] to store changes in variables over time. Is recyclable.
+ *
+ * Each message represents a change to exactly 1 type, specified by [DataType].
+ */
+data class TableChange(
+ var timestamp: Long = 0,
+ var columnPrefix: String = "",
+ var columnName: String = "",
+ var type: DataType = DataType.EMPTY,
+ var bool: Boolean = false,
+ var int: Int = 0,
+ var str: String? = null,
+) {
+ /** Resets to default values so that the object can be recycled. */
+ fun reset(timestamp: Long, columnPrefix: String, columnName: String) {
+ this.timestamp = timestamp
+ this.columnPrefix = columnPrefix
+ this.columnName = columnName
+ this.type = DataType.EMPTY
+ this.bool = false
+ this.int = 0
+ this.str = null
+ }
+
+ /** Sets this to store a string change. */
+ fun set(value: String?) {
+ type = DataType.STRING
+ str = value
+ }
+
+ /** Sets this to store a boolean change. */
+ fun set(value: Boolean) {
+ type = DataType.BOOLEAN
+ bool = value
+ }
+
+ /** Sets this to store an int change. */
+ fun set(value: Int) {
+ type = DataType.INT
+ int = value
+ }
+
+ /** Returns true if this object has a change. */
+ fun hasData(): Boolean {
+ return columnName.isNotBlank() && type != DataType.EMPTY
+ }
+
+ fun getName(): String {
+ return if (columnPrefix.isNotBlank()) {
+ "$columnPrefix.$columnName"
+ } else {
+ columnName
+ }
+ }
+
+ fun getVal(): String {
+ return when (type) {
+ DataType.EMPTY -> null
+ DataType.STRING -> str
+ DataType.INT -> int
+ DataType.BOOLEAN -> bool
+ }.toString()
+ }
+
+ enum class DataType {
+ STRING,
+ BOOLEAN,
+ INT,
+ EMPTY,
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
new file mode 100644
index 0000000..9d0b833
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.table
+
+import com.android.systemui.Dumpable
+import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import java.text.SimpleDateFormat
+import java.util.Locale
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * A logger that logs changes in table format.
+ *
+ * Some parts of System UI maintain a lot of pieces of state at once.
+ * [com.android.systemui.plugins.log.LogBuffer] allows us to easily log change events:
+ *
+ * - 10-10 10:10:10.456: state2 updated to newVal2
+ * - 10-10 10:11:00.000: stateN updated to StateN(val1=true, val2=1)
+ * - 10-10 10:11:02.123: stateN updated to StateN(val1=true, val2=2)
+ * - 10-10 10:11:05.123: state1 updated to newVal1
+ * - 10-10 10:11:06.000: stateN updated to StateN(val1=false, val2=3)
+ *
+ * However, it can sometimes be more useful to view the state changes in table format:
+ *
+ * - timestamp--------- | state1- | state2- | ... | stateN.val1 | stateN.val2
+ * - -------------------------------------------------------------------------
+ * - 10-10 10:10:10.123 | val1--- | val2--- | ... | false------ | 0-----------
+ * - 10-10 10:10:10.456 | val1--- | newVal2 | ... | false------ | 0-----------
+ * - 10-10 10:11:00.000 | val1--- | newVal2 | ... | true------- | 1-----------
+ * - 10-10 10:11:02.123 | val1--- | newVal2 | ... | true------- | 2-----------
+ * - 10-10 10:11:05.123 | newVal1 | newVal2 | ... | true------- | 2-----------
+ * - 10-10 10:11:06.000 | newVal1 | newVal2 | ... | false------ | 3-----------
+ *
+ * This class enables easy logging of the state changes in both change event format and table
+ * format.
+ *
+ * This class also enables easy logging of states that are a collection of fields. For example,
+ * stateN in the above example consists of two fields -- val1 and val2. It's useful to put each
+ * field into its own column so that ABT (Android Bug Tool) can easily highlight changes to
+ * individual fields.
+ *
+ * How it works:
+ *
+ * 1) Create an instance of this buffer via [TableLogBufferFactory].
+ *
+ * 2) For any states being logged, implement [Diffable]. Implementing [Diffable] allows the state to
+ * only log the fields that have *changed* since the previous update, instead of always logging all
+ * fields.
+ *
+ * 3) Each time a change in a state happens, call [logDiffs]. If your state is emitted using a
+ * [Flow], you should use the [logDiffsForTable] extension function to automatically log diffs any
+ * time your flow emits a new value.
+ *
+ * When a dump occurs, there will be two dumps:
+ *
+ * 1) The change events under the dumpable name "$name-changes".
+ *
+ * 2) This class will coalesce all the diffs into a table format and log them under the dumpable
+ * name "$name-table".
+ *
+ * @param maxSize the maximum size of the buffer. Must be > 0.
+ */
+class TableLogBuffer(
+ maxSize: Int,
+ private val name: String,
+ private val systemClock: SystemClock,
+) : Dumpable {
+ init {
+ if (maxSize <= 0) {
+ throw IllegalArgumentException("maxSize must be > 0")
+ }
+ }
+
+ private val buffer = RingBuffer(maxSize) { TableChange() }
+
+ // A [TableRowLogger] object, re-used each time [logDiffs] is called.
+ // (Re-used to avoid object allocation.)
+ private val tempRow = TableRowLoggerImpl(0, columnPrefix = "", this)
+
+ /**
+ * Log the differences between [prevVal] and [newVal].
+ *
+ * The [newVal] object's method [Diffable.logDiffs] will be used to fetch the diffs.
+ *
+ * @param columnPrefix a prefix that will be applied to every column name that gets logged. This
+ * ensures that all the columns related to the same state object will be grouped together in the
+ * table.
+ *
+ * @throws IllegalArgumentException if [columnPrefix] or column name contain "|". "|" is used as
+ * the separator token for parsing, so it can't be present in any part of the column name.
+ */
+ @Synchronized
+ fun <T : Diffable<T>> logDiffs(columnPrefix: String, prevVal: T, newVal: T) {
+ val row = tempRow
+ row.timestamp = systemClock.currentTimeMillis()
+ row.columnPrefix = columnPrefix
+ newVal.logDiffs(prevVal, row)
+ }
+
+ /**
+ * Logs change(s) to the buffer using [rowInitializer].
+ *
+ * @param rowInitializer a function that will be called immediately to store relevant data on
+ * the row.
+ */
+ @Synchronized
+ fun logChange(columnPrefix: String, rowInitializer: (TableRowLogger) -> Unit) {
+ val row = tempRow
+ row.timestamp = systemClock.currentTimeMillis()
+ row.columnPrefix = columnPrefix
+ rowInitializer(row)
+ }
+
+ /** Logs a boolean change. */
+ fun logChange(prefix: String, columnName: String, value: Boolean) {
+ logChange(systemClock.currentTimeMillis(), prefix, columnName, value)
+ }
+
+ // Keep these individual [logChange] methods private (don't let clients give us their own
+ // timestamps.)
+
+ private fun logChange(timestamp: Long, prefix: String, columnName: String, value: String?) {
+ val change = obtain(timestamp, prefix, columnName)
+ change.set(value)
+ }
+
+ private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Boolean) {
+ val change = obtain(timestamp, prefix, columnName)
+ change.set(value)
+ }
+
+ private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int) {
+ val change = obtain(timestamp, prefix, columnName)
+ change.set(value)
+ }
+
+ // TODO(b/259454430): Add additional change types here.
+
+ @Synchronized
+ private fun obtain(timestamp: Long, prefix: String, columnName: String): TableChange {
+ verifyValidName(prefix, columnName)
+ val tableChange = buffer.advance()
+ tableChange.reset(timestamp, prefix, columnName)
+ return tableChange
+ }
+
+ private fun verifyValidName(prefix: String, columnName: String) {
+ if (prefix.contains(SEPARATOR)) {
+ throw IllegalArgumentException("columnPrefix cannot contain $SEPARATOR but was $prefix")
+ }
+ if (columnName.contains(SEPARATOR)) {
+ throw IllegalArgumentException(
+ "columnName cannot contain $SEPARATOR but was $columnName"
+ )
+ }
+ }
+
+ @Synchronized
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println(HEADER_PREFIX + name)
+ pw.println("version $VERSION")
+ for (i in 0 until buffer.size) {
+ buffer[i].dump(pw)
+ }
+ pw.println(FOOTER_PREFIX + name)
+ }
+
+ /** Dumps an individual [TableChange]. */
+ private fun TableChange.dump(pw: PrintWriter) {
+ if (!this.hasData()) {
+ return
+ }
+ val formattedTimestamp = TABLE_LOG_DATE_FORMAT.format(timestamp)
+ pw.print(formattedTimestamp)
+ pw.print(SEPARATOR)
+ pw.print(this.getName())
+ pw.print(SEPARATOR)
+ pw.print(this.getVal())
+ pw.println()
+ }
+
+ /**
+ * A private implementation of [TableRowLogger].
+ *
+ * Used so that external clients can't modify [timestamp].
+ */
+ private class TableRowLoggerImpl(
+ var timestamp: Long,
+ var columnPrefix: String,
+ val tableLogBuffer: TableLogBuffer,
+ ) : TableRowLogger {
+ /** Logs a change to a string value. */
+ override fun logChange(columnName: String, value: String?) {
+ tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+ }
+
+ /** Logs a change to a boolean value. */
+ override fun logChange(columnName: String, value: Boolean) {
+ tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+ }
+
+ /** Logs a change to an int value. */
+ override fun logChange(columnName: String, value: Int) {
+ tableLogBuffer.logChange(timestamp, columnPrefix, columnName, value)
+ }
+ }
+}
+
+val TABLE_LOG_DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
+
+private const val HEADER_PREFIX = "SystemUI StateChangeTableSection START: "
+private const val FOOTER_PREFIX = "SystemUI StateChangeTableSection END: "
+private const val SEPARATOR = "|"
+private const val VERSION = "1"
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
new file mode 100644
index 0000000..7a90a74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.table
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferHelper.Companion.adjustMaxSize
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+
+@SysUISingleton
+class TableLogBufferFactory
+@Inject
+constructor(
+ private val dumpManager: DumpManager,
+ private val systemClock: SystemClock,
+) {
+ fun create(
+ name: String,
+ maxSize: Int,
+ ): TableLogBuffer {
+ val tableBuffer = TableLogBuffer(adjustMaxSize(maxSize), name, systemClock)
+ dumpManager.registerNormalDumpable(name, tableBuffer)
+ return tableBuffer
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt
new file mode 100644
index 0000000..a7ba13b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableRowLogger.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.table
+
+/**
+ * A class that logs a row to [TableLogBuffer].
+ *
+ * Objects that implement [Diffable] will receive an instance of this class, and can log any changes
+ * to individual fields using the [logChange] methods. All logged changes will be associated with
+ * the same timestamp.
+ */
+interface TableRowLogger {
+ /** Logs a change to a string value. */
+ fun logChange(columnName: String, value: String?)
+
+ /** Logs a change to a boolean value. */
+ fun logChange(columnName: String, value: Boolean)
+
+ /** Logs a change to an int value. */
+ fun logChange(columnName: String, value: Int)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 22f91f3..bfa67a8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -109,7 +109,6 @@
CharSequence dialogTitle = null;
String appName = null;
if (Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName)) {
- // TODO(b/253438807): handle special app name
dialogText = getString(R.string.media_projection_dialog_service_text);
dialogTitle = getString(R.string.media_projection_dialog_service_title);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
index a7f1b95..a8f39fa9a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
@@ -26,7 +26,8 @@
import androidx.constraintlayout.widget.Barrier
import com.android.systemui.R
import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.TransitionLayout
private const val TAG = "MediaViewHolder"
@@ -38,6 +39,8 @@
// Player information
val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
val multiRippleView = itemView.requireViewById<MultiRippleView>(R.id.touch_ripple_view)
+ val turbulenceNoiseView =
+ itemView.requireViewById<TurbulenceNoiseView>(R.id.turbulence_noise_view)
val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
val titleText = itemView.requireViewById<TextView>(R.id.header_title)
val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 45b319b..cf71d67 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -20,13 +20,12 @@
import android.os.SystemProperties
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.util.time.SystemClock
import java.util.SortedMap
@@ -62,14 +61,13 @@
@Inject
constructor(
private val context: Context,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val broadcastSender: BroadcastSender,
private val lockscreenUserManager: NotificationLockscreenUserManager,
@Main private val executor: Executor,
private val systemClock: SystemClock,
private val logger: MediaUiEventLogger
) : MediaDataManager.Listener {
- private val userTracker: CurrentUserTracker
private val _listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
internal val listeners: Set<MediaDataManager.Listener>
get() = _listeners.toSet()
@@ -81,15 +79,15 @@
private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
private var reactivatedKey: String? = null
- init {
- userTracker =
- object : CurrentUserTracker(broadcastDispatcher) {
- override fun onUserSwitched(newUserId: Int) {
- // Post this so we can be sure lockscreenUserManager already got the broadcast
- executor.execute { handleUserSwitched(newUserId) }
- }
+ private val userTrackerCallback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ handleUserSwitched(newUser)
}
- userTracker.startTracking()
+ }
+
+ init {
+ userTracker.addCallback(userTrackerCallback, executor)
}
override fun onMediaDataLoaded(
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 14dd990..3012bb4 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
@@ -82,7 +82,6 @@
import java.io.IOException
import java.io.PrintWriter
import java.util.concurrent.Executor
-import java.util.concurrent.Executors
import javax.inject.Inject
// URI fields to try loading album art from
@@ -154,6 +153,7 @@
class MediaDataManager(
private val context: Context,
@Background private val backgroundExecutor: Executor,
+ @Main private val uiExecutor: Executor,
@Main private val foregroundExecutor: DelayableExecutor,
private val mediaControllerFactory: MediaControllerFactory,
private val broadcastDispatcher: BroadcastDispatcher,
@@ -171,7 +171,8 @@
private val systemClock: SystemClock,
private val tunerService: TunerService,
private val mediaFlags: MediaFlags,
- private val logger: MediaUiEventLogger
+ private val logger: MediaUiEventLogger,
+ private val smartspaceManager: SmartspaceManager,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -218,6 +219,7 @@
constructor(
context: Context,
@Background backgroundExecutor: Executor,
+ @Main uiExecutor: Executor,
@Main foregroundExecutor: DelayableExecutor,
mediaControllerFactory: MediaControllerFactory,
dumpManager: DumpManager,
@@ -233,10 +235,12 @@
clock: SystemClock,
tunerService: TunerService,
mediaFlags: MediaFlags,
- logger: MediaUiEventLogger
+ logger: MediaUiEventLogger,
+ smartspaceManager: SmartspaceManager,
) : this(
context,
backgroundExecutor,
+ uiExecutor,
foregroundExecutor,
mediaControllerFactory,
broadcastDispatcher,
@@ -254,7 +258,8 @@
clock,
tunerService,
mediaFlags,
- logger
+ logger,
+ smartspaceManager,
)
private val appChangeReceiver =
@@ -314,21 +319,18 @@
// Register for Smartspace data updates.
smartspaceMediaDataProvider.registerListener(this)
- val smartspaceManager: SmartspaceManager =
- context.getSystemService(SmartspaceManager::class.java)
smartspaceSession =
smartspaceManager.createSmartspaceSession(
SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
)
smartspaceSession?.let {
it.addOnTargetsAvailableListener(
- // Use a new thread listening to Smartspace updates instead of using the existing
- // backgroundExecutor. SmartspaceSession has scheduled routine updates which can be
- // unpredictable on test simulators, using the backgroundExecutor makes it's hard to
- // test the threads numbers.
- // Switch to use backgroundExecutor when SmartspaceSession has a good way to be
- // mocked.
- Executors.newCachedThreadPool(),
+ // Use a main uiExecutor thread listening to Smartspace updates instead of using
+ // the existing background executor.
+ // SmartspaceSession has scheduled routine updates which can be unpredictable on
+ // test simulators, using the backgroundExecutor makes it's hard to test the threads
+ // numbers.
+ uiExecutor,
SmartspaceSession.OnTargetsAvailableListener { targets ->
smartspaceMediaDataProvider.onTargetsAvailable(targets)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 4891297..2d10b82 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -32,10 +32,12 @@
import com.android.systemui.broadcast.BroadcastDispatcher
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.media.controls.models.player.MediaData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.settings.UserTracker
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Utils
import com.android.systemui.util.time.SystemClock
@@ -55,6 +57,8 @@
constructor(
private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
+ @Main private val mainExecutor: Executor,
@Background private val backgroundExecutor: Executor,
private val tunerService: TunerService,
private val mediaBrowserFactory: ResumeMediaBrowserFactory,
@@ -77,18 +81,26 @@
private var currentUserId: Int = context.userId
@VisibleForTesting
- val userChangeReceiver =
+ val userUnlockReceiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_USER_UNLOCKED == intent.action) {
- loadMediaResumptionControls()
- } else if (Intent.ACTION_USER_SWITCHED == intent.action) {
- currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
- loadSavedComponents()
+ val userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
+ if (userId == currentUserId) {
+ loadMediaResumptionControls()
+ }
}
}
}
+ private val userTrackerCallback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ currentUserId = newUser
+ loadSavedComponents()
+ }
+ }
+
private val mediaBrowserCallback =
object : ResumeMediaBrowser.Callback() {
override fun addTrack(
@@ -126,13 +138,13 @@
dumpManager.registerDumpable(TAG, this)
val unlockFilter = IntentFilter()
unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
- unlockFilter.addAction(Intent.ACTION_USER_SWITCHED)
broadcastDispatcher.registerReceiver(
- userChangeReceiver,
+ userUnlockReceiver,
unlockFilter,
null,
UserHandle.ALL
)
+ userTracker.addCallback(userTrackerCallback, mainExecutor)
loadSavedComponents()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 918417f..93be6a7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -29,7 +29,8 @@
import com.android.settingslib.Utils
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.monet.ColorScheme
-import com.android.systemui.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
/**
* A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
@@ -102,13 +103,21 @@
private val context: Context,
private val mediaViewHolder: MediaViewHolder,
private val multiRippleController: MultiRippleController,
+ private val turbulenceNoiseController: TurbulenceNoiseController,
animatingColorTransitionFactory: AnimatingColorTransitionFactory
) {
constructor(
context: Context,
mediaViewHolder: MediaViewHolder,
multiRippleController: MultiRippleController,
- ) : this(context, mediaViewHolder, multiRippleController, ::AnimatingColorTransition)
+ turbulenceNoiseController: TurbulenceNoiseController
+ ) : this(
+ context,
+ mediaViewHolder,
+ multiRippleController,
+ turbulenceNoiseController,
+ ::AnimatingColorTransition
+ )
val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
val surfaceColor =
@@ -129,6 +138,7 @@
mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
multiRippleController.updateColor(accentPrimary)
+ turbulenceNoiseController.updateNoiseColor(accentPrimary)
}
val accentSecondary =
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 8aaee81..1fdbc99 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
@@ -184,6 +184,7 @@
private val configListener =
object : ConfigurationController.ConfigurationListener {
+
override fun onDensityOrFontScaleChanged() {
// System font changes should only happen when UMO is offscreen or a flicker may
// occur
@@ -199,6 +200,7 @@
override fun onConfigChanged(newConfig: Configuration?) {
if (newConfig == null) return
isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
+ updatePlayers(recreateMedia = true)
}
override fun onUiModeChanged() {
@@ -635,7 +637,7 @@
val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey()
existingSmartspaceMediaKey?.let {
val removedPlayer =
- MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey, true)
+ removePlayer(existingSmartspaceMediaKey, dismissMediaData = false)
removedPlayer?.run {
debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
}
@@ -685,7 +687,7 @@
key: String,
dismissMediaData: Boolean = true,
dismissRecommendation: Boolean = true
- ) {
+ ): MediaControlPanel? {
if (key == MediaPlayerData.smartspaceMediaKey()) {
MediaPlayerData.smartspaceMediaData?.let {
logger.logRecommendationRemoved(it.packageName, it.instanceId)
@@ -693,7 +695,7 @@
}
val removed =
MediaPlayerData.removeMediaPlayer(key, dismissMediaData || dismissRecommendation)
- removed?.apply {
+ return removed?.apply {
mediaCarouselScrollHandler.onPrePlayerRemoved(removed)
mediaContent.removeView(removed.mediaViewHolder?.player)
mediaContent.removeView(removed.recommendationViewHolder?.recommendations)
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 215fa03..21e64e2 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
@@ -31,6 +31,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.graphics.BlendMode;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
@@ -64,6 +65,7 @@
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.settingslib.widget.AdaptiveIcon;
@@ -97,13 +99,16 @@
import com.android.systemui.monet.Style;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.ripple.MultiRippleController;
-import com.android.systemui.ripple.RippleAnimation;
-import com.android.systemui.ripple.RippleAnimationConfig;
-import com.android.systemui.ripple.RippleShader;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController;
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimation;
+import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig;
+import com.android.systemui.surfaceeffects.ripple.RippleShader;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig;
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController;
import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.time.SystemClock;
@@ -216,7 +221,9 @@
private boolean mShowBroadcastDialogButton = false;
private String mSwitchBroadcastApp;
private MultiRippleController mMultiRippleController;
+ private TurbulenceNoiseController mTurbulenceNoiseController;
private FeatureFlags mFeatureFlags;
+ private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig = null;
/**
* Initialize a new control panel
@@ -394,9 +401,20 @@
AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit,
Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText);
- mMultiRippleController = new MultiRippleController(vh.getMultiRippleView());
+ MultiRippleView multiRippleView = vh.getMultiRippleView();
+ mMultiRippleController = new MultiRippleController(multiRippleView);
+ mTurbulenceNoiseController = new TurbulenceNoiseController(vh.getTurbulenceNoiseView());
+ multiRippleView.addRipplesFinishedListener(
+ () -> {
+ if (mTurbulenceNoiseAnimationConfig == null) {
+ mTurbulenceNoiseAnimationConfig = createLingeringNoiseAnimation();
+ }
+ // Color will be correctly updated in ColorSchemeTransition.
+ mTurbulenceNoiseController.play(mTurbulenceNoiseAnimationConfig);
+ }
+ );
mColorSchemeTransition = new ColorSchemeTransition(
- mContext, mMediaViewHolder, mMultiRippleController);
+ mContext, mMediaViewHolder, mMultiRippleController, mTurbulenceNoiseController);
mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter);
}
@@ -571,7 +589,10 @@
seamlessView.setContentDescription(deviceString);
seamlessView.setOnClickListener(
v -> {
- if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ if (mFalsingManager.isFalseTap(
+ mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+ ? FalsingManager.MODERATE_PENALTY :
+ FalsingManager.LOW_PENALTY)) {
return;
}
@@ -994,7 +1015,10 @@
} else {
button.setEnabled(true);
button.setOnClickListener(v -> {
- if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ if (!mFalsingManager.isFalseTap(
+ mFeatureFlags.isEnabled(Flags.MEDIA_FALSING_PENALTY)
+ ? FalsingManager.MODERATE_PENALTY :
+ FalsingManager.LOW_PENALTY)) {
mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
action.run();
@@ -1027,7 +1051,7 @@
/* maxWidth= */ maxSize,
/* maxHeight= */ maxSize,
/* pixelDensity= */ getContext().getResources().getDisplayMetrics().density,
- mColorSchemeTransition.getAccentPrimary().getTargetColor(),
+ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
/* opacity= */ 100,
/* shouldFillRipple= */ false,
/* sparkleStrength= */ 0f,
@@ -1036,6 +1060,26 @@
);
}
+ private TurbulenceNoiseAnimationConfig createLingeringNoiseAnimation() {
+ return new TurbulenceNoiseAnimationConfig(
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT,
+ TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
+ /* noiseMoveSpeedX= */ 0f,
+ /* noiseMoveSpeedY= */ 0f,
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
+ /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
+ // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art.
+ // Thus, set the background color with alpha 0.
+ /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0),
+ TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
+ /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
+ /* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_DURATION_IN_MILLIS,
+ this.getContext().getResources().getDisplayMetrics().density,
+ BlendMode.PLUS,
+ /* onAnimationEnd= */ null
+ );
+ }
private void clearButton(final ImageButton button) {
button.setImageDrawable(null);
button.setContentDescription(null);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index cbb670e..f7a9bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -799,6 +799,16 @@
}
if (
+ desiredLocation == LOCATION_QS &&
+ previousLocation == LOCATION_LOCKSCREEN &&
+ statusbarState == StatusBarState.SHADE
+ ) {
+ // This is an invalid transition, can happen when tapping on home control and the UMO
+ // while being on landscape orientation in tablet.
+ return false
+ }
+
+ if (
statusbarState == StatusBarState.KEYGUARD &&
(currentLocation == LOCATION_LOCKSCREEN || previousLocation == LOCATION_LOCKSCREEN)
) {
@@ -1043,18 +1053,9 @@
rootOverlay!!.add(mediaFrame)
} else {
val targetHost = getHost(newLocation)!!.hostView
- // When adding back to the host, let's make sure to reset the bounds.
- // Usually adding the view will trigger a layout that does this automatically,
- // but we sometimes suppress this.
+ // This will either do a full layout pass and remeasure, or it will bypass
+ // that and directly set the mediaFrame's bounds within the premeasured host.
targetHost.addView(mediaFrame)
- val left = targetHost.paddingLeft
- val top = targetHost.paddingTop
- mediaFrame.setLeftTopRightBottom(
- left,
- top,
- left + currentBounds.width(),
- top + currentBounds.height()
- )
if (mediaFrame.childCount > 0) {
val child = mediaFrame.getChildAt(0)
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 4bf3031..4feb984 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
@@ -420,7 +420,9 @@
*/
fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? =
traceSection("MediaViewController#getMeasurementsForState") {
- val viewState = obtainViewState(hostState) ?: return null
+ // measurements should never factor in the squish fraction
+ val viewState =
+ obtainViewState(hostState.copy().also { it.squishFraction = 1.0f }) ?: return null
measurement.measuredWidth = viewState.width
measurement.measuredHeight = viewState.height
return measurement
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index a4a96806..647beb9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -61,7 +61,7 @@
@SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE
val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
as StatusBarManager
- val routeInfo = MediaRoute2Info.Builder("id", args[0])
+ val routeInfo = MediaRoute2Info.Builder(if (args.size >= 4) args[3] else "id", args[0])
.addFeature("feature")
val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false")
if (useAppIcon) {
@@ -107,7 +107,7 @@
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND " +
- "<deviceName> <chipState> useAppIcon=[true|false]")
+ "<deviceName> <chipState> useAppIcon=[true|false] <id>")
}
}
@@ -127,8 +127,10 @@
@SuppressLint("WrongConstant") // sysui is allowed to call STATUS_BAR_SERVICE
val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
as StatusBarManager
- val routeInfo = MediaRoute2Info.Builder("id", "Test Name")
- .addFeature("feature")
+ val routeInfo = MediaRoute2Info.Builder(
+ if (args.size >= 3) args[2] else "id",
+ "Test Name"
+ ).addFeature("feature")
val useAppIcon = !(args.size >= 2 && args[1] == "useAppIcon=false")
if (useAppIcon) {
routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
@@ -144,7 +146,7 @@
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar $RECEIVER_COMMAND " +
- "<chipState> useAppIcon=[true|false]")
+ "<chipState> useAppIcon=[true|false] <id>")
}
}
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 8bddffc..cc5e256 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
@@ -56,7 +56,7 @@
* TODO(b/245610654): Re-name this to be MediaTttReceiverCoordinator.
*/
@SysUISingleton
-class MediaTttChipControllerReceiver @Inject constructor(
+open class MediaTttChipControllerReceiver @Inject constructor(
private val commandQueue: CommandQueue,
context: Context,
@MediaTttReceiverLogger logger: MediaTttLogger,
@@ -121,18 +121,32 @@
uiEventLogger.logReceiverStateChange(chipState)
if (chipState == ChipStateReceiver.FAR_FROM_SENDER) {
- removeView(removalReason = ChipStateReceiver.FAR_FROM_SENDER.name)
+ removeView(routeInfo.id, removalReason = ChipStateReceiver.FAR_FROM_SENDER.name)
return
}
if (appIcon == null) {
- displayView(ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appName))
+ displayView(
+ ChipReceiverInfo(
+ routeInfo,
+ appIconDrawableOverride = null,
+ appName,
+ id = routeInfo.id,
+ )
+ )
return
}
appIcon.loadDrawableAsync(
context,
Icon.OnDrawableLoadedListener { drawable ->
- displayView(ChipReceiverInfo(routeInfo, drawable, appName))
+ displayView(
+ ChipReceiverInfo(
+ routeInfo,
+ drawable,
+ appName,
+ id = routeInfo.id,
+ )
+ )
},
// Notify the listener on the main handler since the listener will update
// the UI.
@@ -169,15 +183,28 @@
val appIconView = view.getAppIconView()
appIconView.animate()
.translationYBy(-1 * getTranslationAmount().toFloat())
- .setDuration(30.frames)
+ .setDuration(ICON_TRANSLATION_ANIM_DURATION)
.start()
appIconView.animate()
.alpha(1f)
- .setDuration(5.frames)
+ .setDuration(ICON_ALPHA_ANIM_DURATION)
.start()
// Using withEndAction{} doesn't apply a11y focus when screen is unlocked.
appIconView.postOnAnimation { view.requestAccessibilityFocus() }
- startRipple(view.requireViewById(R.id.ripple))
+ expandRipple(view.requireViewById(R.id.ripple))
+ }
+
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ val appIconView = view.getAppIconView()
+ appIconView.animate()
+ .translationYBy(getTranslationAmount().toFloat())
+ .setDuration(ICON_TRANSLATION_ANIM_DURATION)
+ .start()
+ appIconView.animate()
+ .alpha(0f)
+ .setDuration(ICON_ALPHA_ANIM_DURATION)
+ .start()
+ (view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd)
}
override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -191,11 +218,22 @@
return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
}
- private fun startRipple(rippleView: ReceiverChipRippleView) {
+ private fun expandRipple(rippleView: ReceiverChipRippleView) {
if (rippleView.rippleInProgress()) {
// Skip if ripple is still playing
return
}
+
+ // In case the device orientation changes, we need to reset the layout.
+ rippleView.addOnLayoutChangeListener (
+ View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
+ if (v == null) return@OnLayoutChangeListener
+
+ val layoutChangedRippleView = v as ReceiverChipRippleView
+ layoutRipple(layoutChangedRippleView)
+ layoutChangedRippleView.invalidate()
+ }
+ )
rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(view: View?) {}
@@ -205,7 +243,7 @@
}
val attachedRippleView = view as ReceiverChipRippleView
layoutRipple(attachedRippleView)
- attachedRippleView.startRipple()
+ attachedRippleView.expandRipple()
attachedRippleView.removeOnAttachStateChangeListener(this)
}
})
@@ -228,10 +266,14 @@
}
}
+val ICON_TRANSLATION_ANIM_DURATION = 30.frames
+val ICON_ALPHA_ANIM_DURATION = 5.frames
+
data class ChipReceiverInfo(
val routeInfo: MediaRoute2Info,
val appIconDrawableOverride: Drawable?,
val appNameOverride: CharSequence?,
override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
+ override val id: String,
) : TemporaryViewInfo()
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 e354a03..6e9fc5c 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
@@ -16,19 +16,47 @@
package com.android.systemui.media.taptotransfer.receiver
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.util.AttributeSet
-import com.android.systemui.ripple.RippleShader
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleShader
+import com.android.systemui.surfaceeffects.ripple.RippleView
/**
* An expanding ripple effect for the media tap-to-transfer receiver chip.
*/
class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleView(context, attrs) {
+
+ // Indicates whether the ripple started expanding.
+ private var isStarted: Boolean
+
init {
setupShader(RippleShader.RippleShape.ELLIPSE)
setRippleFill(true)
setSparkleStrength(0f)
duration = 3000L
+ isStarted = false
+ }
+
+ fun expandRipple(onAnimationEnd: Runnable? = null) {
+ isStarted = true
+ super.startRipple(onAnimationEnd)
+ }
+
+ /** Used to animate out the ripple. No-op if the ripple was never started via [startRipple]. */
+ fun collapseRipple(onAnimationEnd: Runnable? = null) {
+ if (!isStarted) {
+ return // Ignore if ripple is not started yet.
+ }
+ // Reset all listeners to animator.
+ animator.removeAllListeners()
+ animator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ onAnimationEnd?.run()
+ isStarted = false
+ }
+ })
+ animator.reverse()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index d1ea2d0..bb7bc6f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -108,7 +108,7 @@
}
displayedState = null
- chipbarCoordinator.removeView(removalReason)
+ chipbarCoordinator.removeView(routeInfo.id, removalReason)
} else {
displayedState = chipState
chipbarCoordinator.displayView(
@@ -162,6 +162,7 @@
windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
timeoutMs = chipStateSender.timeout,
+ id = routeInfo.id,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index e8b49cd..7a77c47 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -16,19 +16,19 @@
package com.android.systemui.mediaprojection.appselector.data
-import android.app.ActivityManager
import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.recents.RecentTasks
import com.android.wm.shell.util.GroupedRecentTaskInfo
import java.util.Optional
+import java.util.concurrent.Executor
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
-import java.util.concurrent.Executor
interface RecentTaskListProvider {
/** Loads recent tasks, the returned task list is from the most-recent to least-recent order */
@@ -40,7 +40,8 @@
constructor(
@Background private val coroutineDispatcher: CoroutineDispatcher,
@Background private val backgroundExecutor: Executor,
- private val recentTasks: Optional<RecentTasks>
+ private val recentTasks: Optional<RecentTasks>,
+ private val userTracker: UserTracker
) : RecentTaskListProvider {
private val recents by lazy { recentTasks.getOrNull() }
@@ -67,10 +68,8 @@
getRecentTasks(
Integer.MAX_VALUE,
RECENT_IGNORE_UNAVAILABLE,
- ActivityManager.getCurrentUser(),
+ userTracker.userId,
backgroundExecutor
- ) { tasks ->
- continuation.resume(tasks)
- }
+ ) { tasks -> continuation.resume(tasks) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 33021e3..2c0745b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -16,6 +16,8 @@
package com.android.systemui.navigationbar;
+import static android.app.StatusBarManager.WINDOW_NAVIGATION_BAR;
+import static android.app.StatusBarManager.WindowVisibleState;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
@@ -58,6 +60,7 @@
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -86,7 +89,7 @@
AccessibilityButtonModeObserver.ModeChangedListener,
AccessibilityButtonTargetsObserver.TargetsChangedListener,
OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
- Dumpable {
+ Dumpable, CommandQueue.Callbacks {
private final AccessibilityManager mAccessibilityManager;
private final Lazy<AssistManager> mAssistManagerLazy;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
@@ -97,13 +100,18 @@
private final AccessibilityButtonTargetsObserver mAccessibilityButtonTargetsObserver;
private final List<NavbarTaskbarStateUpdater> mA11yEventListeners = new ArrayList<>();
private final Context mContext;
- private ContentResolver mContentResolver;
+ private final CommandQueue mCommandQueue;
+ private final ContentResolver mContentResolver;
private boolean mAssistantAvailable;
private boolean mLongPressHomeEnabled;
private boolean mAssistantTouchGestureEnabled;
private int mNavBarMode;
private int mA11yButtonState;
+ // Attributes used in NavBarHelper.CurrentSysuiState
+ private int mWindowStateDisplayId;
+ private @WindowVisibleState int mWindowState;
+
private final ContentObserver mAssistContentObserver = new ContentObserver(
new Handler(Looper.getMainLooper())) {
@Override
@@ -128,8 +136,10 @@
KeyguardStateController keyguardStateController,
NavigationModeController navigationModeController,
UserTracker userTracker,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ CommandQueue commandQueue) {
mContext = context;
+ mCommandQueue = commandQueue;
mContentResolver = mContext.getContentResolver();
mAccessibilityManager = accessibilityManager;
mAssistManagerLazy = assistManagerLazy;
@@ -160,10 +170,13 @@
false, mAssistContentObserver, UserHandle.USER_ALL);
updateAssistantAvailability();
updateA11yState();
+ mCommandQueue.addCallback(this);
+
}
public void destroy() {
mContentResolver.unregisterContentObserver(mAssistContentObserver);
+ mCommandQueue.removeCallback(this);
}
/**
@@ -333,6 +346,20 @@
|| (!isKeyguardShowing && (vis & InputMethodService.IME_VISIBLE) != 0);
}
+ @Override
+ public void setWindowState(int displayId, int window, int state) {
+ CommandQueue.Callbacks.super.setWindowState(displayId, window, state);
+ if (window != WINDOW_NAVIGATION_BAR) {
+ return;
+ }
+ mWindowStateDisplayId = displayId;
+ mWindowState = state;
+ }
+
+ public CurrentSysuiState getCurrentSysuiState() {
+ return new CurrentSysuiState();
+ }
+
/**
* Callbacks will get fired once immediately after registering via
* {@link #registerNavTaskStateUpdater(NavbarTaskbarStateUpdater)}
@@ -342,6 +369,17 @@
void updateAssistantAvailable(boolean available);
}
+ /** Data class to help Taskbar/Navbar initiate state correctly when switching between the two.*/
+ public class CurrentSysuiState {
+ public final int mWindowStateDisplayId;
+ public final @WindowVisibleState int mWindowState;
+
+ public CurrentSysuiState() {
+ mWindowStateDisplayId = NavBarHelper.this.mWindowStateDisplayId;
+ mWindowState = NavBarHelper.this.mWindowState;
+ }
+ }
+
static @TransitionMode int transitionMode(boolean isTransient, int appearance) {
final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_NAVIGATION_BARS;
if (isTransient) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 85d15dc..d762b39 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -52,13 +52,12 @@
import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
import android.annotation.IdRes;
+import android.annotation.NonNull;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.PixelFormat;
@@ -114,7 +113,6 @@
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
@@ -132,6 +130,7 @@
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.recents.utilities.Utilities;
@@ -202,7 +201,7 @@
private final NotificationRemoteInputManager mNotificationRemoteInputManager;
private final OverviewProxyService mOverviewProxyService;
private final NavigationModeController mNavigationModeController;
- private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
private final CommandQueue mCommandQueue;
private final Optional<Pip> mPipOptional;
private final Optional<Recents> mRecentsOptional;
@@ -504,7 +503,7 @@
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
SysUiState sysUiFlagsContainer,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
CommandQueue commandQueue,
Optional<Pip> pipOptional,
Optional<Recents> recentsOptional,
@@ -547,7 +546,7 @@
mNotificationRemoteInputManager = notificationRemoteInputManager;
mOverviewProxyService = overviewProxyService;
mNavigationModeController = navigationModeController;
- mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
mCommandQueue = commandQueue;
mPipOptional = pipOptional;
mRecentsOptional = recentsOptional;
@@ -650,6 +649,9 @@
mDisplayId = mContext.getDisplayId();
mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
+ // Ensure we try to get currentSysuiState from navBarHelper before command queue callbacks
+ // start firing, since the latter is source of truth
+ parseCurrentSysuiState();
mCommandQueue.addCallback(this);
mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
mNavBarHelper.init();
@@ -726,9 +728,7 @@
prepareNavigationBarView();
checkNavBarModes();
- IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
- Handler.getMain(), UserHandle.ALL);
+ mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
notifyNavigationBarScreenOn();
@@ -779,7 +779,7 @@
mView.setUpdateActiveTouchRegionsCallback(null);
getBarTransitions().destroy();
mOverviewProxyService.removeCallback(mOverviewProxyListener);
- mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
+ mUserTracker.removeCallback(mUserChangedCallback);
mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
if (mOrientationHandle != null) {
resetSecondaryHandle();
@@ -937,6 +937,13 @@
setOrientedHandleSamplingRegion(null);
}
+ private void parseCurrentSysuiState() {
+ NavBarHelper.CurrentSysuiState state = mNavBarHelper.getCurrentSysuiState();
+ if (state.mWindowStateDisplayId == mDisplayId) {
+ mNavigationBarWindowState = state.mWindowState;
+ }
+ }
+
private void reconfigureHomeLongClick() {
if (mView.getHomeButton().getCurrentView() == null) {
return;
@@ -1664,21 +1671,14 @@
}
};
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- // TODO(193941146): Currently unregistering a receiver through BroadcastDispatcher is
- // async, but we've already cleared the fields. Just return early in this case.
- if (mView == null) {
- return;
- }
- String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- // The accessibility settings may be different for the new user
- updateAccessibilityStateFlags();
- }
- }
- };
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ // The accessibility settings may be different for the new user
+ updateAccessibilityStateFlags();
+ }
+ };
@VisibleForTesting
int getNavigationIconHints() {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 73fc21e..0d9af31 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -214,6 +214,7 @@
return;
}
mDisplayId = displayId;
+ parseCurrentSysuiState();
mCommandQueue.addCallback(this);
mOverviewProxyService.addCallback(this);
mEdgeBackGestureHandler.onNavigationModeChanged(
@@ -271,6 +272,13 @@
return mInitialized;
}
+ private void parseCurrentSysuiState() {
+ NavBarHelper.CurrentSysuiState state = mNavBarHelper.getCurrentSysuiState();
+ if (state.mWindowStateDisplayId == mDisplayId) {
+ mTaskBarWindowState = state.mWindowState;
+ }
+ }
+
private void updateSysuiFlags() {
int a11yFlags = mNavBarHelper.getA11yButtonState();
boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index c319a82..cb0f3e2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -59,7 +59,6 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -70,9 +69,9 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.settings.CurrentUserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.QuickStepContract;
@@ -102,8 +101,8 @@
/**
* Utility class to handle edge swipes for back gesture
*/
-public class EdgeBackGestureHandler extends CurrentUserTracker
- implements PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
+public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBackPlugin>,
+ ProtoTraceable<SystemUiTraceProto> {
private static final String TAG = "EdgeBackGestureHandler";
private static final int MAX_LONG_PRESS_TIMEOUT = SystemProperties.getInt(
@@ -172,6 +171,7 @@
private final Context mContext;
+ private final UserTracker mUserTracker;
private final OverviewProxyService mOverviewProxyService;
private final SysUiState mSysUiState;
private Runnable mStateChangeCallback;
@@ -319,6 +319,15 @@
private final Consumer<Boolean> mOnIsInPipStateChangedListener =
(isInPip) -> mIsInPip = isInPip;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ updateIsEnabled();
+ updateCurrentUserResources();
+ }
+ };
+
EdgeBackGestureHandler(
Context context,
OverviewProxyService overviewProxyService,
@@ -326,7 +335,7 @@
PluginManager pluginManager,
@Main Executor executor,
@Background Executor backgroundExecutor,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
BackPanelController.Factory backPanelControllerFactory,
@@ -338,11 +347,11 @@
Provider<NavigationBarEdgePanel> navigationBarEdgePanelProvider,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
FeatureFlags featureFlags) {
- super(broadcastDispatcher);
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = executor;
mBackgroundExecutor = backgroundExecutor;
+ mUserTracker = userTracker;
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
@@ -444,12 +453,6 @@
}
}
- @Override
- public void onUserSwitched(int newUserId) {
- updateIsEnabled();
- updateCurrentUserResources();
- }
-
/**
* @see NavigationBarView#onAttachedToWindow()
*/
@@ -459,7 +462,7 @@
mOverviewProxyService.addCallback(mQuickSwitchListener);
mSysUiState.addCallback(mSysUiStateCallback);
updateIsEnabled();
- startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
}
/**
@@ -471,7 +474,7 @@
mOverviewProxyService.removeCallback(mQuickSwitchListener);
mSysUiState.removeCallback(mSysUiStateCallback);
updateIsEnabled();
- stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
}
/**
@@ -1074,7 +1077,7 @@
private final PluginManager mPluginManager;
private final Executor mExecutor;
private final Executor mBackgroundExecutor;
- private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
private final ProtoTracer mProtoTracer;
private final NavigationModeController mNavigationModeController;
private final BackPanelController.Factory mBackPanelControllerFactory;
@@ -1094,7 +1097,7 @@
PluginManager pluginManager,
@Main Executor executor,
@Background Executor backgroundExecutor,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ProtoTracer protoTracer,
NavigationModeController navigationModeController,
BackPanelController.Factory backPanelControllerFactory,
@@ -1112,7 +1115,7 @@
mPluginManager = pluginManager;
mExecutor = executor;
mBackgroundExecutor = backgroundExecutor;
- mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
mProtoTracer = protoTracer;
mNavigationModeController = navigationModeController;
mBackPanelControllerFactory = backPanelControllerFactory;
@@ -1135,7 +1138,7 @@
mPluginManager,
mExecutor,
mBackgroundExecutor,
- mBroadcastDispatcher,
+ mUserTracker,
mProtoTracer,
mNavigationModeController,
mBackPanelControllerFactory,
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
index 0b565ea..e6575d5a 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginDependencyProvider.java
@@ -18,7 +18,6 @@
import com.android.systemui.Dependency;
import com.android.systemui.plugins.PluginDependency.DependencyProvider;
-import com.android.systemui.shared.plugins.PluginManager;
import javax.inject.Inject;
import javax.inject.Singleton;
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
index 638f81b..146633d 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginsModule.java
@@ -27,7 +27,6 @@
import com.android.systemui.shared.plugins.PluginActionManager;
import com.android.systemui.shared.plugins.PluginEnabler;
import com.android.systemui.shared.plugins.PluginInstance;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
import com.android.systemui.shared.plugins.PluginPrefs;
import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 1da866e..5a1ad96 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -39,6 +39,8 @@
import android.util.Log;
import android.util.Slog;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.ThreadUtils;
@@ -47,6 +49,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -80,6 +83,7 @@
private final PowerManager mPowerManager;
private final WarningsUI mWarnings;
private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final UserTracker mUserTracker;
private InattentiveSleepWarningView mOverlayView;
private final Configuration mLastConfiguration = new Configuration();
private int mPlugType = 0;
@@ -122,12 +126,21 @@
}
};
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mWarnings.userSwitched();
+ }
+ };
+
@Inject
public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher,
CommandQueue commandQueue, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
WarningsUI warningsUI, EnhancedEstimates enhancedEstimates,
WakefulnessLifecycle wakefulnessLifecycle,
- PowerManager powerManager) {
+ PowerManager powerManager,
+ UserTracker userTracker) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mCommandQueue = commandQueue;
@@ -136,6 +149,7 @@
mEnhancedEstimates = enhancedEstimates;
mPowerManager = powerManager;
mWakefulnessLifecycle = wakefulnessLifecycle;
+ mUserTracker = userTracker;
}
public void start() {
@@ -154,6 +168,7 @@
false, obs, UserHandle.USER_ALL);
updateBatteryWarningLevels();
mReceiver.init();
+ mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
// Check to see if we need to let the user know that the phone previously shut down due
@@ -250,7 +265,6 @@
IntentFilter filter = new IntentFilter();
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler);
// Force get initial values. Relying on Sticky behavior until API for getting info.
if (!mHasReceivedBattery) {
@@ -332,8 +346,6 @@
plugged, bucket);
});
- } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- mWarnings.userSwitched();
} else {
Slog.w(TAG, "unknown intent: " + intent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index 2c20feb..fa3f878f 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -158,14 +158,18 @@
* Returns true if lock screen entry point for QR Code Scanner is to be enabled.
*/
public boolean isEnabledForLockScreenButton() {
- return mQRCodeScannerEnabled && mIntent != null && mConfigEnableLockScreenButton
- && isActivityCallable(mIntent);
+ return mQRCodeScannerEnabled && isAbleToOpenCameraApp() && isAvailableOnDevice();
+ }
+
+ /** Returns whether the feature is available on the device. */
+ public boolean isAvailableOnDevice() {
+ return mConfigEnableLockScreenButton;
}
/**
- * Returns true if quick settings entry point for QR Code Scanner is to be enabled.
+ * Returns true if the feature can open a camera app on the device.
*/
- public boolean isEnabledForQuickSettings() {
+ public boolean isAbleToOpenCameraApp() {
return mIntent != null && isActivityCallable(mIntent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 0697133..f92bbf7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -364,13 +364,18 @@
private void distributeTiles() {
emptyAndInflateOrRemovePages();
- final int tileCount = mPages.get(0).maxTiles();
- if (DEBUG) Log.d(TAG, "Distributing tiles");
+ final int tilesPerPageCount = mPages.get(0).maxTiles();
int index = 0;
- final int NT = mTiles.size();
- for (int i = 0; i < NT; i++) {
+ final int totalTilesCount = mTiles.size();
+ if (DEBUG) {
+ Log.d(TAG, "Distributing tiles: "
+ + "[tilesPerPageCount=" + tilesPerPageCount + "]"
+ + "[totalTilesCount=" + totalTilesCount + "]"
+ );
+ }
+ for (int i = 0; i < totalTilesCount; i++) {
TileRecord tile = mTiles.get(i);
- if (mPages.get(index).mRecords.size() == tileCount) index++;
+ if (mPages.get(index).mRecords.size() == tilesPerPageCount) index++;
if (DEBUG) {
Log.d(TAG, "Adding " + tile.tile.getClass().getSimpleName() + " to "
+ index);
@@ -577,8 +582,8 @@
});
setOffscreenPageLimit(lastPageNumber); // Ensure the page to reveal has been inflated.
int dx = getWidth() * lastPageNumber;
- mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
- REVEAL_SCROLL_DURATION_MILLIS);
+ mScroller.startScroll(getScrollX(), getScrollY(), isLayoutRtl() ? -dx : dx, 0,
+ REVEAL_SCROLL_DURATION_MILLIS);
postInvalidateOnAnimation();
}
@@ -738,6 +743,7 @@
public interface PageListener {
int INVALID_PAGE = -1;
+
void onPageChanged(boolean isFirst, int pageNumber);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 20f1a8e..71ab457 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -515,7 +515,13 @@
public void setExpanded(boolean expanded) {
if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
mQsExpanded = expanded;
- updateQsPanelControllerListening();
+ if (mInSplitShade && mQsExpanded) {
+ // in split shade QS is expanded immediately when shade expansion starts and then we
+ // also need to listen to changes - otherwise QS is updated only once its fully expanded
+ setListening(true);
+ } else {
+ updateQsPanelControllerListening();
+ }
updateQsState();
}
@@ -691,10 +697,12 @@
if (mQSAnimator != null) {
mQSAnimator.setPosition(expansion);
}
- if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD
+ if (!mInSplitShade
+ || mStatusBarStateController.getState() == StatusBarState.KEYGUARD
|| mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
// At beginning, state is 0 and will apply wrong squishiness to MediaHost in lockscreen
- // and media player expect no change by squishiness in lock screen shade
+ // and media player expect no change by squishiness in lock screen shade. Don't bother
+ // squishing mQsMediaHost when not in split shade to prevent problems with stale state.
mQsMediaHost.setSquishFraction(1.0F);
} else {
mQsMediaHost.setSquishFraction(mSquishinessFraction);
@@ -751,7 +759,8 @@
return ShadeInterpolation.getContentAlpha(progress);
}
- private void updateQsBounds() {
+ @VisibleForTesting
+ void updateQsBounds() {
if (mLastQSExpansion == 1.0f) {
// Fully expanded, let's set the layout bounds as clip bounds. This is necessary because
// it's a scrollview and otherwise wouldn't be clipped. However, we set the horizontal
@@ -767,9 +776,10 @@
mQSPanelScrollView.getLocationOnScreen(mLocationTemp);
int left = mLocationTemp[0];
int top = mLocationTemp[1];
- mQsMediaHost.getCurrentClipping().set(left, top, left + getView().getMeasuredWidth(),
+ mQsMediaHost.getCurrentClipping().set(left, top,
+ left + getView().getMeasuredWidth(),
top + mQSPanelScrollView.getMeasuredHeight()
- - mQSPanelScrollView.getPaddingBottom());
+ - mQSPanelController.getPaddingBottom());
}
private boolean headerWillBeAnimating() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 64962b4..1827eaf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -239,5 +239,9 @@
public boolean isBouncerInTransit() {
return mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit();
}
+
+ public int getPaddingBottom() {
+ return mView.getPaddingBottom();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 2a80de0..dd88c83 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -25,6 +25,7 @@
import android.content.res.Configuration;
import android.content.res.Configuration.Orientation;
import android.metrics.LogMaker;
+import android.util.Log;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
@@ -38,6 +39,7 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileViewImpl;
import com.android.systemui.util.LargeScreenUtils;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -237,6 +239,16 @@
private void addTile(final QSTile tile, boolean collapsedView) {
final TileRecord r =
new TileRecord(tile, mHost.createTileView(getContext(), tile, collapsedView));
+ // TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of
+ // b/250618218.
+ try {
+ QSTileViewImpl qsTileView = (QSTileViewImpl) (r.tileView);
+ if (qsTileView != null) {
+ qsTileView.setQsLogger(mQSLogger);
+ }
+ } catch (ClassCastException e) {
+ Log.e(TAG, "Failed to cast QSTileView to QSTileViewImpl", e);
+ }
mView.addTile(r);
mRecords.add(r);
mCachedSpecs = getTilesSpecs();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index f37d668..6240c10 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -41,6 +41,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
@@ -53,7 +54,6 @@
import com.android.systemui.qs.nano.QsTileState;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
index 6b0abd4..7794fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java
@@ -16,7 +16,6 @@
package com.android.systemui.qs;
-import android.app.ActivityManager;
import android.database.ContentObserver;
import android.os.Handler;
@@ -47,10 +46,6 @@
this(settingsProxy, handler, settingName, userId, 0);
}
- public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName) {
- this(settingsProxy, handler, settingName, ActivityManager.getCurrentUser());
- }
-
public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName,
int userId, int defaultValue) {
super(handler);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 3d00dd4..7ee4047 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -123,7 +123,6 @@
public boolean updateResources() {
final Resources res = mContext.getResources();
mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
- updateColumns();
mMaxCellHeight = mContext.getResources().getDimensionPixelSize(mCellHeightResId);
mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
mSidePadding = useSidePadding() ? mCellMarginHorizontal / 2 : 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index cf10c79..79fcc7d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -82,12 +82,12 @@
DefaultItemAnimator animator = new DefaultItemAnimator();
animator.setMoveDuration(TileAdapter.MOVE_DURATION);
mRecyclerView.setItemAnimator(animator);
+
+ updateTransparentViewHeight();
}
void updateResources() {
- LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
- lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
- mTransparentView.setLayoutParams(lp);
+ updateTransparentViewHeight();
mRecyclerView.getAdapter().notifyItemChanged(0);
}
@@ -236,4 +236,10 @@
public boolean isOpening() {
return mOpening;
}
+
+ private void updateTransparentViewHeight() {
+ LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
+ lp.height = QSUtils.getQsHeaderSystemIconsAreaHeight(mContext);
+ mTransparentView.setLayoutParams(lp);
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index 9ba3501..03bb7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -32,8 +32,6 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.FgsManagerController
@@ -42,10 +40,9 @@
import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.security.data.repository.SecurityRepository
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.domain.interactor.UserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -100,13 +97,12 @@
@Inject
constructor(
private val activityStarter: ActivityStarter,
- private val featureFlags: FeatureFlags,
private val metricsLogger: MetricsLogger,
private val uiEventLogger: UiEventLogger,
private val deviceProvisionedController: DeviceProvisionedController,
private val qsSecurityFooterUtils: QSSecurityFooterUtils,
private val fgsManagerController: FgsManagerController,
- private val userSwitchDialogController: UserSwitchDialogController,
+ private val userInteractor: UserInteractor,
securityRepository: SecurityRepository,
foregroundServicesRepository: ForegroundServicesRepository,
userSwitcherRepository: UserSwitcherRepository,
@@ -182,22 +178,6 @@
}
override fun showUserSwitcher(context: Context, expandable: Expandable) {
- if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- userSwitchDialogController.showDialog(context, expandable)
- return
- }
-
- val intent =
- Intent(context, UserSwitcherActivity::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
-
- activityStarter.startActivity(
- intent,
- true /* dismissShade */,
- expandable.activityLaunchController(),
- true /* showOverlockscreenwhenlocked */,
- UserHandle.SYSTEM,
- )
+ userInteractor.showUserSwitcher(context, expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 931dc8d..9f6317f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -129,12 +129,36 @@
})
}
- fun logInternetTileUpdate(lastType: Int, callback: String) {
+ fun logInternetTileUpdate(tileSpec: String, lastType: Int, callback: String) {
log(VERBOSE, {
+ str1 = tileSpec
int1 = lastType
- str1 = callback
+ str2 = callback
}, {
- "mLastTileState=$int1, Callback=$str1."
+ "[$str1] mLastTileState=$int1, Callback=$str2."
+ })
+ }
+
+ // TODO(b/250618218): Remove this method once we know the root cause of b/250618218.
+ fun logTileBackgroundColorUpdateIfInternetTile(
+ tileSpec: String,
+ state: Int,
+ disabledByPolicy: Boolean,
+ color: Int
+ ) {
+ // This method is added to further debug b/250618218 which has only been observed from the
+ // InternetTile, so we are only logging the background color change for the InternetTile
+ // to avoid spamming the QSLogger.
+ if (tileSpec != "internet") {
+ return
+ }
+ log(VERBOSE, {
+ str1 = tileSpec
+ int1 = state
+ bool1 = disabledByPolicy
+ int2 = color
+ }, {
+ "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2."
})
}
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 972b243..b355d4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -50,6 +50,7 @@
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.qs.QSTile.BooleanState
import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
import java.util.Objects
@@ -116,7 +117,7 @@
protected lateinit var sideView: ViewGroup
private lateinit var customDrawableView: ImageView
private lateinit var chevronView: ImageView
-
+ private var mQsLogger: QSLogger? = null
protected var showRippleEffect = true
private lateinit var ripple: RippleDrawable
@@ -188,6 +189,10 @@
updateHeight()
}
+ fun setQsLogger(qsLogger: QSLogger) {
+ mQsLogger = qsLogger
+ }
+
fun updateResources() {
FontSizeUtils.updateFontSize(label, R.dimen.qs_tile_text_size)
FontSizeUtils.updateFontSize(secondaryLabel, R.dimen.qs_tile_text_size)
@@ -493,6 +498,11 @@
// Colors
if (state.state != lastState || state.disabledByPolicy || lastDisabledByPolicy) {
singleAnimator.cancel()
+ mQsLogger?.logTileBackgroundColorUpdateIfInternetTile(
+ state.spec,
+ state.state,
+ state.disabledByPolicy,
+ getBackgroundColorForState(state.state, state.disabledByPolicy))
if (allowAnimations) {
singleAnimator.setValues(
colorValuesHolder(
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 86d4fa3..033dbe0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -48,6 +48,7 @@
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.settings.GlobalSettings;
import javax.inject.Inject;
@@ -74,14 +75,16 @@
QSLogger qsLogger,
BroadcastDispatcher broadcastDispatcher,
Lazy<ConnectivityManager> lazyConnectivityManager,
- GlobalSettings globalSettings
+ GlobalSettings globalSettings,
+ UserTracker userTracker
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mBroadcastDispatcher = broadcastDispatcher;
mLazyConnectivityManager = lazyConnectivityManager;
- mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON) {
+ mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON,
+ userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
// mHandler is the background handler so calling this is OK
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 c65bd9b..41d8549 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -32,6 +32,7 @@
import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
@@ -125,14 +126,15 @@
state.icon = icon
if (controlsComponent.isEnabled() && hasControlsApps.get()) {
if (controlsComponent.getVisibility() == AVAILABLE) {
- val structureInfo = controlsComponent
- .getControlsController().get().getPreferredStructure()
- state.state = if (structureInfo.controls.isEmpty()) {
+ val selection = controlsComponent
+ .getControlsController().get().getPreferredSelection()
+ state.state = if (selection is SelectedItem.StructureItem &&
+ selection.structure.controls.isEmpty()) {
Tile.STATE_INACTIVE
} else {
Tile.STATE_ACTIVE
}
- val label = structureInfo.structure
+ val label = selection.name
state.secondaryLabel = if (label == tileLabel) null else label
} else {
state.state = Tile.STATE_INACTIVE
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 bebd580..5bc209a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -70,7 +70,7 @@
private final SettingObserver mDreamSettingObserver;
private final UserTracker mUserTracker;
private final boolean mDreamSupported;
- private final boolean mDreamOnlyEnabledForSystemUser;
+ private final boolean mDreamOnlyEnabledForDockUser;
private boolean mIsDocked = false;
@@ -100,22 +100,22 @@
BroadcastDispatcher broadcastDispatcher,
UserTracker userTracker,
@Named(DreamModule.DREAM_SUPPORTED) boolean dreamSupported,
- @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
- boolean dreamOnlyEnabledForSystemUser
+ @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER)
+ boolean dreamOnlyEnabledForDockUser
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mDreamManager = dreamManager;
mBroadcastDispatcher = broadcastDispatcher;
mEnabledSettingObserver = new SettingObserver(secureSettings, mHandler,
- Settings.Secure.SCREENSAVER_ENABLED) {
+ Settings.Secure.SCREENSAVER_ENABLED, userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
refreshState();
}
};
mDreamSettingObserver = new SettingObserver(secureSettings, mHandler,
- Settings.Secure.SCREENSAVER_COMPONENTS) {
+ Settings.Secure.SCREENSAVER_COMPONENTS, userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
refreshState();
@@ -123,7 +123,7 @@
};
mUserTracker = userTracker;
mDreamSupported = dreamSupported;
- mDreamOnlyEnabledForSystemUser = dreamOnlyEnabledForSystemUser;
+ mDreamOnlyEnabledForDockUser = dreamOnlyEnabledForDockUser;
}
@Override
@@ -203,7 +203,8 @@
// For now, restrict to debug users.
return Build.isDebuggable()
&& mDreamSupported
- && (!mDreamOnlyEnabledForSystemUser || mUserTracker.getUserHandle().isSystem());
+ // TODO(b/257333623): Allow the Dock User to be non-SystemUser user in HSUM.
+ && (!mDreamOnlyEnabledForDockUser || mUserTracker.getUserHandle().isSystem());
}
@VisibleForTesting
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 d304024..5670b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -383,7 +383,8 @@
@Override
protected void handleUpdateState(SignalState state, Object arg) {
- mQSLogger.logInternetTileUpdate(mLastTileState, arg == null ? "null" : arg.toString());
+ mQSLogger.logInternetTileUpdate(
+ getTileSpec(), mLastTileState, arg == null ? "null" : arg.toString());
if (arg instanceof CellularCallbackInfo) {
mLastTileState = 0;
handleUpdateCellularState(state, arg);
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 376d3d8..6d50b56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -115,8 +115,12 @@
state.label = mContext.getString(R.string.qr_code_scanner_title);
state.contentDescription = state.label;
state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
- state.state = mQRCodeScannerController.isEnabledForQuickSettings() ? Tile.STATE_INACTIVE
+ state.state = mQRCodeScannerController.isAbleToOpenCameraApp() ? Tile.STATE_INACTIVE
: Tile.STATE_UNAVAILABLE;
+ // The assumption is that if the OEM has the QR code scanner module enabled then the scanner
+ // would go to "Unavailable" state only when GMS core is updating.
+ state.secondaryLabel = state.state == Tile.STATE_UNAVAILABLE
+ ? mContext.getString(R.string.qr_code_scanner_updating_secondary_label) : null;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 27ad86f..ee3b130 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -249,15 +249,7 @@
mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
mInternetDialogTitle.setText(getDialogTitleText());
mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
-
- TypedArray typedArray = mContext.obtainStyledAttributes(
- new int[]{android.R.attr.selectableItemBackground});
- try {
- mBackgroundOff = typedArray.getDrawable(0 /* index */);
- } finally {
- typedArray.recycle();
- }
-
+ mBackgroundOff = mContext.getDrawable(R.drawable.internet_dialog_selected_effect);
setOnClickListener();
mTurnWifiOnLayout.setBackground(null);
mAirplaneModeButton.setVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 4c7f10e..2e6ea0e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -142,21 +142,28 @@
private static final int SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE =
R.string.all_network_unavailable;
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final TelephonyDisplayInfo DEFAULT_TELEPHONY_DISPLAY_INFO =
+ new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
static final int MAX_WIFI_ENTRY_COUNT = 3;
private final FeatureFlags mFeatureFlags;
+ @VisibleForTesting
+ /** Should be accessible only to the main thread. */
+ final Map<Integer, TelephonyDisplayInfo> mSubIdTelephonyDisplayInfoMap = new HashMap<>();
+
private WifiManager mWifiManager;
private Context mContext;
private SubscriptionManager mSubscriptionManager;
+ /** Should be accessible only to the main thread. */
private Map<Integer, TelephonyManager> mSubIdTelephonyManagerMap = new HashMap<>();
+ /** Should be accessible only to the main thread. */
+ private Map<Integer, TelephonyCallback> mSubIdTelephonyCallbackMap = new HashMap<>();
private TelephonyManager mTelephonyManager;
private ConnectivityManager mConnectivityManager;
private CarrierConfigTracker mCarrierConfigTracker;
- private TelephonyDisplayInfo mTelephonyDisplayInfo =
- new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
private Handler mHandler;
private Handler mWorkerHandler;
private MobileMappings.Config mConfig = null;
@@ -190,8 +197,6 @@
@VisibleForTesting
protected SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
@VisibleForTesting
- protected InternetTelephonyCallback mInternetTelephonyCallback;
- @VisibleForTesting
protected WifiUtils.InternetIconInjector mWifiIconInjector;
@VisibleForTesting
protected boolean mCanConfigWifi;
@@ -290,8 +295,10 @@
mConfig = MobileMappings.Config.readConfig(mContext);
mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
mSubIdTelephonyManagerMap.put(mDefaultDataSubId, mTelephonyManager);
- mInternetTelephonyCallback = new InternetTelephonyCallback();
- mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
+ InternetTelephonyCallback telephonyCallback =
+ new InternetTelephonyCallback(mDefaultDataSubId);
+ mSubIdTelephonyCallbackMap.put(mDefaultDataSubId, telephonyCallback);
+ mTelephonyManager.registerTelephonyCallback(mExecutor, telephonyCallback);
// Listen the connectivity changes
mConnectivityManager.registerDefaultNetworkCallback(mConnectivityManagerNetworkCallback);
mCanConfigWifi = canConfigWifi;
@@ -304,7 +311,12 @@
}
mBroadcastDispatcher.unregisterReceiver(mConnectionStateReceiver);
for (TelephonyManager tm : mSubIdTelephonyManagerMap.values()) {
- tm.unregisterTelephonyCallback(mInternetTelephonyCallback);
+ TelephonyCallback callback = mSubIdTelephonyCallbackMap.get(tm.getSubscriptionId());
+ if (callback != null) {
+ tm.unregisterTelephonyCallback(callback);
+ } else if (DEBUG) {
+ Log.e(TAG, "Unexpected null telephony call back for Sub " + tm.getSubscriptionId());
+ }
}
mSubscriptionManager.removeOnSubscriptionsChangedListener(
mOnSubscriptionsChangedListener);
@@ -623,7 +635,9 @@
int subId = subInfo.getSubscriptionId();
if (mSubIdTelephonyManagerMap.get(subId) == null) {
TelephonyManager secondaryTm = mTelephonyManager.createForSubscriptionId(subId);
- secondaryTm.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
+ InternetTelephonyCallback telephonyCallback = new InternetTelephonyCallback(subId);
+ secondaryTm.registerTelephonyCallback(mExecutor, telephonyCallback);
+ mSubIdTelephonyCallbackMap.put(subId, telephonyCallback);
mSubIdTelephonyManagerMap.put(subId, secondaryTm);
}
return subId;
@@ -637,8 +651,7 @@
}
String getMobileNetworkSummary(int subId) {
- String description = getNetworkTypeDescription(mContext, mConfig,
- mTelephonyDisplayInfo, subId);
+ String description = getNetworkTypeDescription(mContext, mConfig, subId);
return getMobileSummary(mContext, description, subId);
}
@@ -646,7 +659,9 @@
* Get currently description of mobile network type.
*/
private String getNetworkTypeDescription(Context context, MobileMappings.Config config,
- TelephonyDisplayInfo telephonyDisplayInfo, int subId) {
+ int subId) {
+ TelephonyDisplayInfo telephonyDisplayInfo =
+ mSubIdTelephonyDisplayInfoMap.getOrDefault(subId, DEFAULT_TELEPHONY_DISPLAY_INFO);
String iconKey = getIconKey(telephonyDisplayInfo);
if (mapIconSets(config) == null || mapIconSets(config).get(iconKey) == null) {
@@ -725,11 +740,10 @@
Intent getSubSettingIntent(int subId) {
final Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
-
final Bundle fragmentArgs = new Bundle();
// Special contract for Settings to highlight permission row
fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID);
- fragmentArgs.putInt(Settings.EXTRA_SUB_ID, subId);
+ intent.putExtra(Settings.EXTRA_SUB_ID, subId);
intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
return intent;
}
@@ -1054,6 +1068,11 @@
TelephonyCallback.SignalStrengthsListener,
TelephonyCallback.UserMobileDataStateListener {
+ private final int mSubId;
+ private InternetTelephonyCallback(int subId) {
+ mSubId = subId;
+ }
+
@Override
public void onServiceStateChanged(@NonNull ServiceState serviceState) {
mCallback.onServiceStateChanged(serviceState);
@@ -1071,7 +1090,7 @@
@Override
public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
- mTelephonyDisplayInfo = telephonyDisplayInfo;
+ mSubIdTelephonyDisplayInfoMap.put(mSubId, telephonyDisplayInfo);
mCallback.onDisplayInfoChanged(telephonyDisplayInfo);
}
@@ -1196,19 +1215,30 @@
}
return;
}
-
- mDefaultDataSubId = defaultDataSubId;
if (DEBUG) {
- Log.d(TAG, "DDS: defaultDataSubId:" + mDefaultDataSubId);
+ Log.d(TAG, "DDS: defaultDataSubId:" + defaultDataSubId);
}
- if (SubscriptionManager.isUsableSubscriptionId(mDefaultDataSubId)) {
- mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
- mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
- mSubIdTelephonyManagerMap.put(mDefaultDataSubId, mTelephonyManager);
- mTelephonyManager.registerTelephonyCallback(mHandler::post,
- mInternetTelephonyCallback);
- mCallback.onSubscriptionsChanged(mDefaultDataSubId);
+ if (SubscriptionManager.isUsableSubscriptionId(defaultDataSubId)) {
+ // clean up old defaultDataSubId
+ TelephonyCallback oldCallback = mSubIdTelephonyCallbackMap.get(mDefaultDataSubId);
+ if (oldCallback != null) {
+ mTelephonyManager.unregisterTelephonyCallback(oldCallback);
+ } else if (DEBUG) {
+ Log.e(TAG, "Unexpected null telephony call back for Sub " + mDefaultDataSubId);
+ }
+ mSubIdTelephonyCallbackMap.remove(mDefaultDataSubId);
+ mSubIdTelephonyDisplayInfoMap.remove(mDefaultDataSubId);
+ mSubIdTelephonyManagerMap.remove(mDefaultDataSubId);
+
+ // create for new defaultDataSubId
+ mTelephonyManager = mTelephonyManager.createForSubscriptionId(defaultDataSubId);
+ mSubIdTelephonyManagerMap.put(defaultDataSubId, mTelephonyManager);
+ InternetTelephonyCallback newCallback = new InternetTelephonyCallback(defaultDataSubId);
+ mSubIdTelephonyCallbackMap.put(defaultDataSubId, newCallback);
+ mTelephonyManager.registerTelephonyCallback(mHandler::post, newCallback);
+ mCallback.onSubscriptionsChanged(defaultDataSubId);
}
+ mDefaultDataSubId = defaultDataSubId;
}
public WifiUtils.InternetIconInjector getWifiIconInjector() {
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
new file mode 100644
index 0000000..802db7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.reardisplay;
+
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManagerGlobal;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+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;
+
+import com.airbnb.lottie.LottieAnimationView;
+import com.airbnb.lottie.LottieDrawable;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * Provides an educational dialog to the user alerting them to what
+ * they may need to do to enter rear display mode. This may be to open the
+ * device if it is currently folded, or to confirm that they would like
+ * the content to move to the screen on their device that is aligned with
+ * the rear camera. This includes a device animation to provide more context
+ * to the user.
+ *
+ * We are suppressing lint for the VisibleForTests check because the use of
+ * DeviceStateManagerGlobal as in this file should not be encouraged for other use-cases.
+ * The lint check will notify any other use-cases that they are possibly doing something
+ * incorrectly.
+ */
+@SuppressLint("VisibleForTests") // TODO(b/260264542) Migrate away from DeviceStateManagerGlobal
+@SysUISingleton
+public class RearDisplayDialogController implements CoreStartable, CommandQueue.Callbacks {
+
+ private int[] mFoldedStates;
+ private boolean mStartedFolded;
+ private boolean mServiceNotified = false;
+ private int mAnimationRepeatCount = LottieDrawable.INFINITE;
+
+ private DeviceStateManagerGlobal mDeviceStateManagerGlobal;
+ private DeviceStateManager.DeviceStateCallback mDeviceStateManagerCallback =
+ new DeviceStateManagerCallback();
+
+ private final Context mContext;
+ private final CommandQueue mCommandQueue;
+ private final Executor mExecutor;
+
+ @VisibleForTesting
+ SystemUIDialog mRearDisplayEducationDialog;
+
+ @Inject
+ public RearDisplayDialogController(Context context, CommandQueue commandQueue,
+ @Main Executor executor) {
+ mContext = context;
+ mCommandQueue = commandQueue;
+ mExecutor = executor;
+ }
+
+ @Override
+ public void start() {
+ mCommandQueue.addCallback(this);
+ }
+
+ @Override
+ public void showRearDisplayDialog(int currentBaseState) {
+ initializeValues(currentBaseState);
+ createAndShowDialog();
+ }
+
+ private void createAndShowDialog() {
+ mServiceNotified = false;
+ Context dialogContext = mRearDisplayEducationDialog.getContext();
+
+ View dialogView;
+ if (mStartedFolded) {
+ dialogView = View.inflate(dialogContext,
+ R.layout.activity_rear_display_education, null);
+ } else {
+ dialogView = View.inflate(dialogContext,
+ R.layout.activity_rear_display_education_opened, null);
+ }
+ LottieAnimationView animationView = dialogView.findViewById(
+ R.id.rear_display_folded_animation);
+ animationView.setRepeatCount(mAnimationRepeatCount);
+ mRearDisplayEducationDialog.setView(dialogView);
+
+ configureDialogButtons();
+
+ mRearDisplayEducationDialog.show();
+ }
+
+ /**
+ * Configures the buttons on the dialog depending on the starting device posture
+ */
+ private void configureDialogButtons() {
+ // If we are open, we need to provide a confirm option
+ if (!mStartedFolded) {
+ mRearDisplayEducationDialog.setPositiveButton(
+ R.string.rear_display_bottom_sheet_confirm,
+ (dialog, which) -> closeOverlayAndNotifyService(false), true);
+ }
+ mRearDisplayEducationDialog.setNegativeButton(R.string.rear_display_bottom_sheet_cancel,
+ (dialog, which) -> closeOverlayAndNotifyService(true), true);
+ mRearDisplayEducationDialog.setOnDismissListener(dialog -> {
+ // Dialog is being dismissed before we've notified the system server
+ if (!mServiceNotified) {
+ closeOverlayAndNotifyService(true);
+ }
+ });
+ }
+
+ /**
+ * Initializes properties and values we need when getting ready to show the dialog.
+ *
+ * Ensures we're not using old values from when the dialog may have been shown previously.
+ */
+ private void initializeValues(int startingBaseState) {
+ mRearDisplayEducationDialog = new SystemUIDialog(mContext);
+ if (mFoldedStates == null) {
+ mFoldedStates = mContext.getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates);
+ }
+ mStartedFolded = isFoldedState(startingBaseState);
+ mDeviceStateManagerGlobal = DeviceStateManagerGlobal.getInstance();
+ mDeviceStateManagerGlobal.registerDeviceStateCallback(mDeviceStateManagerCallback,
+ mExecutor);
+ }
+
+ private boolean isFoldedState(int state) {
+ for (int i = 0; i < mFoldedStates.length; i++) {
+ if (mFoldedStates[i] == state) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Closes the educational overlay, and notifies the system service if rear display mode
+ * should be cancelled or enabled.
+ */
+ private void closeOverlayAndNotifyService(boolean shouldCancelRequest) {
+ mServiceNotified = true;
+ mDeviceStateManagerGlobal.unregisterDeviceStateCallback(mDeviceStateManagerCallback);
+ mDeviceStateManagerGlobal.onStateRequestOverlayDismissed(shouldCancelRequest);
+ }
+
+ /**
+ * TestAPI to allow us to set the folded states array, instead of reading from resources.
+ */
+ @TestApi
+ void setFoldedStates(int[] foldedStates) {
+ mFoldedStates = foldedStates;
+ }
+
+ @TestApi
+ void setDeviceStateManagerCallback(
+ DeviceStateManager.DeviceStateCallback deviceStateManagerCallback) {
+ mDeviceStateManagerCallback = deviceStateManagerCallback;
+ }
+
+ @TestApi
+ void setAnimationRepeatCount(int repeatCount) {
+ mAnimationRepeatCount = repeatCount;
+ }
+
+ private class DeviceStateManagerCallback implements DeviceStateManager.DeviceStateCallback {
+ @Override
+ public void onBaseStateChanged(int state) {
+ if (mStartedFolded && !isFoldedState(state)) {
+ // We've opened the device, we can close the overlay
+ mRearDisplayEducationDialog.dismiss();
+ closeOverlayAndNotifyService(false);
+ } else if (!mStartedFolded && isFoldedState(state)) {
+ // We've closed the device, finish activity
+ mRearDisplayEducationDialog.dismiss();
+ closeOverlayAndNotifyService(true);
+ }
+ }
+
+ // We only care about physical device changes in this scenario
+ @Override
+ public void onStateChanged(int state) {}
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ba97297..547b496 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -78,8 +78,8 @@
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.Dumpable;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.ScreenLifecycle;
@@ -90,12 +90,11 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -108,20 +107,19 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
import javax.inject.Inject;
import dagger.Lazy;
-
/**
* Class to send information from overview to launcher with a binder.
*/
@SysUISingleton
-public class OverviewProxyService extends CurrentUserTracker implements
- CallbackController<OverviewProxyListener>, NavigationModeController.ModeChangedListener,
- Dumpable {
+public class OverviewProxyService implements CallbackController<OverviewProxyListener>,
+ NavigationModeController.ModeChangedListener, Dumpable {
private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
@@ -133,6 +131,7 @@
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
+ private final Executor mMainExecutor;
private final ShellInterface mShellInterface;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private SysUiState mSysUiState;
@@ -145,6 +144,7 @@
private final Intent mQuickStepIntent;
private final ScreenshotHelper mScreenshotHelper;
private final CommandQueue mCommandQueue;
+ private final UserTracker mUserTracker;
private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
private final UiEventLogger mUiEventLogger;
@@ -417,7 +417,7 @@
return;
}
- mCurrentBoundedUserId = getCurrentUserId();
+ mCurrentBoundedUserId = mUserTracker.getUserId();
mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
Bundle params = new Bundle();
@@ -498,34 +498,44 @@
}
};
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mConnectionBackoffAttempts = 0;
+ internalConnectToCurrentUser();
+ }
+ };
+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public OverviewProxyService(Context context,
+ @Main Executor mainExecutor,
CommandQueue commandQueue,
ShellInterface shellInterface,
Lazy<NavigationBarController> navBarControllerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
NavigationModeController navModeController,
NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ScreenLifecycle screenLifecycle,
UiEventLogger uiEventLogger,
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
AssistUtils assistUtils,
DumpManager dumpManager) {
- super(broadcastDispatcher);
-
// 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());
}
mContext = context;
+ mMainExecutor = mainExecutor;
mShellInterface = shellInterface;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
mStatusBarWinController = statusBarWinController;
+ mUserTracker = userTracker;
mConnectionBackoffAttempts = 0;
mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
com.android.internal.R.string.config_recentsComponentName));
@@ -566,7 +576,7 @@
mCommandQueue = commandQueue;
// Listen for user setup
- startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
screenLifecycle.addObserver(mLifecycleObserver);
@@ -579,12 +589,6 @@
assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener);
}
- @Override
- public void onUserSwitched(int newUserId) {
- mConnectionBackoffAttempts = 0;
- internalConnectToCurrentUser();
- }
-
public void onVoiceSessionWindowVisibilityChanged(boolean visible) {
mSysUiState.setFlag(SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, visible)
.commitUpdate(mContext.getDisplayId());
@@ -712,7 +716,7 @@
mBound = mContext.bindServiceAsUser(launcherServiceIntent,
mOverviewServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
- UserHandle.of(getCurrentUserId()));
+ UserHandle.of(mUserTracker.getUserId()));
} catch (SecurityException e) {
Log.e(TAG_OPS, "Unable to bind because of security error", e);
}
@@ -941,7 +945,7 @@
}
private void updateEnabledState() {
- final int currentUser = ActivityManagerWrapper.getInstance().getCurrentUserId();
+ final int currentUser = mUserTracker.getUserId();
mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
MATCH_SYSTEM_ONLY, currentUser) != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 2ee5f05..645b125 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -51,10 +51,13 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.util.leak.RotationUtils;
@@ -76,6 +79,7 @@
private final AccessibilityManager mAccessibilityService;
private final WindowManager mWindowManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
private RequestWindowView mRequestWindow;
private int mNavBarMode;
@@ -83,12 +87,21 @@
/** ID of task to be pinned or locked. */
private int taskId;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ clearPrompt();
+ }
+ };
+
@Inject
public ScreenPinningRequest(
Context context,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
NavigationModeController navigationModeController,
- BroadcastDispatcher broadcastDispatcher) {
+ BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker) {
mContext = context;
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mAccessibilityService = (AccessibilityManager)
@@ -97,6 +110,7 @@
mContext.getSystemService(Context.WINDOW_SERVICE);
mNavBarMode = navigationModeController.addListener(this);
mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
}
public void clearPrompt() {
@@ -228,9 +242,9 @@
}
IntentFilter filter = new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_SCREEN_OFF);
mBroadcastDispatcher.registerReceiver(mReceiver, filter);
+ mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
}
private void inflateView(int rotation) {
@@ -358,6 +372,7 @@
@Override
public void onDetachedFromWindow() {
mBroadcastDispatcher.unregisterReceiver(mReceiver);
+ mUserTracker.removeCallback(mUserChangedCallback);
}
protected void onConfigurationChanged() {
@@ -388,8 +403,7 @@
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
post(mUpdateLayoutRunnable);
- } else if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)
- || intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
clearPrompt();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
deleted file mode 100644
index 6de4648..0000000
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.ripple
-
-/** A common utility functions that are used for computing [RippleShader]. */
-class RippleShaderUtilLibrary {
- //language=AGSL
- companion object {
- const val SHADER_LIB = """
- float triangleNoise(vec2 n) {
- n = fract(n * vec2(5.3987, 5.4421));
- n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
- float xy = n.x * n.y;
- return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
- }
- const float PI = 3.1415926535897932384626;
-
- float sparkles(vec2 uv, float t) {
- float n = triangleNoise(uv);
- float s = 0.0;
- for (float i = 0; i < 4; i += 1) {
- float l = i * 0.01;
- float h = l + 0.1;
- float o = smoothstep(n - l, h, n);
- o *= abs(sin(PI * o * (t + 0.55 * i)));
- s += o;
- }
- return s;
- }
-
- vec2 distort(vec2 p, float time, float distort_amount_radial,
- float distort_amount_xy) {
- float angle = atan(p.y, p.x);
- return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
- cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
- + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
- cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
- }"""
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
index f4d59a8..db0052a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
@@ -23,8 +23,11 @@
import android.view.WindowManager
import android.widget.AdapterView
import android.widget.ArrayAdapter
+import android.widget.ImageView
import android.widget.Spinner
import android.widget.TextView
+import androidx.annotation.ColorRes
+import androidx.annotation.DrawableRes
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
import com.android.systemui.R
@@ -34,7 +37,9 @@
open class BaseScreenSharePermissionDialog(
context: Context?,
private val screenShareOptions: List<ScreenShareOption>,
- private val appName: String?
+ private val appName: String?,
+ @DrawableRes private val dialogIconDrawable: Int? = null,
+ @ColorRes private val dialogIconTint: Int? = null
) : SystemUIDialog(context), AdapterView.OnItemSelectedListener {
private lateinit var dialogTitle: TextView
private lateinit var startButton: TextView
@@ -53,10 +58,21 @@
warning = findViewById(R.id.text_warning)
startButton = findViewById(R.id.button_start)
findViewById<TextView>(R.id.button_cancel).setOnClickListener { dismiss() }
+ updateIcon()
initScreenShareOptions()
createOptionsView(getOptionsViewLayoutId())
}
+ private fun updateIcon() {
+ val icon = findViewById<ImageView>(R.id.screen_share_dialog_icon)
+ if (dialogIconTint != null) {
+ icon.setColorFilter(context.getColor(dialogIconTint))
+ }
+ if (dialogIconDrawable != null) {
+ icon.setImageDrawable(context.getDrawable(dialogIconDrawable))
+ }
+ }
+
protected fun initScreenShareOptions() {
selectedScreenShareOption = screenShareOptions.first()
warning.text = warningText
@@ -69,8 +85,12 @@
private fun initScreenShareSpinner() {
val options = screenShareOptions.map { context.getString(it.spinnerText) }.toTypedArray()
val adapter =
- ArrayAdapter(context.applicationContext, android.R.layout.simple_spinner_item, options)
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ ArrayAdapter(
+ context.applicationContext,
+ R.layout.screen_share_dialog_spinner_text,
+ options
+ )
+ adapter.setDropDownViewResource(R.layout.screen_share_dialog_spinner_item_text)
screenShareModeSpinner = findViewById(R.id.screen_share_mode_spinner)
screenShareModeSpinner.adapter = adapter
screenShareModeSpinner.onItemSelectedListener = this
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
index 15b0bc4..c5a82ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/MediaProjectionPermissionDialog.kt
@@ -23,11 +23,15 @@
class MediaProjectionPermissionDialog(
context: Context?,
private val onStartRecordingClicked: Runnable,
- appName: String?
-) : BaseScreenSharePermissionDialog(context, createOptionList(), appName) {
+ private val appName: String?
+) : BaseScreenSharePermissionDialog(context, createOptionList(appName), appName) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setDialogTitle(R.string.media_projection_permission_dialog_title)
+ if (appName == null) {
+ setDialogTitle(R.string.media_projection_permission_dialog_system_service_title)
+ } else {
+ setDialogTitle(R.string.media_projection_permission_dialog_title)
+ }
setStartButtonText(R.string.media_projection_permission_dialog_continue)
setStartButtonOnClickListener {
// Note that it is important to run this callback before dismissing, so that the
@@ -38,17 +42,30 @@
}
companion object {
- private fun createOptionList(): List<ScreenShareOption> {
+ private fun createOptionList(appName: String?): List<ScreenShareOption> {
+ val singleAppWarningText =
+ if (appName == null) {
+ R.string.media_projection_permission_dialog_system_service_warning_single_app
+ } else {
+ R.string.media_projection_permission_dialog_warning_single_app
+ }
+ val entireScreenWarningText =
+ if (appName == null) {
+ R.string.media_projection_permission_dialog_system_service_warning_entire_screen
+ } else {
+ R.string.media_projection_permission_dialog_warning_entire_screen
+ }
+
return listOf(
ScreenShareOption(
- SINGLE_APP,
- R.string.media_projection_permission_dialog_option_single_app,
- R.string.media_projection_permission_dialog_warning_single_app
+ mode = ENTIRE_SCREEN,
+ spinnerText = R.string.media_projection_permission_dialog_option_entire_screen,
+ warningText = entireScreenWarningText
),
ScreenShareOption(
- ENTIRE_SCREEN,
- R.string.media_projection_permission_dialog_option_entire_screen,
- R.string.media_projection_permission_dialog_warning_entire_screen
+ mode = SINGLE_APP,
+ spinnerText = R.string.media_projection_permission_dialog_option_single_app,
+ warningText = singleAppWarningText
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index ce4e0ec..b8684ee 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -33,13 +33,16 @@
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.CallbackController;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -55,8 +58,10 @@
private boolean mIsRecording;
private PendingIntent mStopIntent;
private CountDownTimer mCountDownTimer = null;
- private BroadcastDispatcher mBroadcastDispatcher;
- private UserContextProvider mUserContextProvider;
+ private final Executor mMainExecutor;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserContextProvider mUserContextProvider;
+ private final UserTracker mUserTracker;
protected static final String INTENT_UPDATE_STATE =
"com.android.systemui.screenrecord.UPDATE_STATE";
@@ -66,12 +71,13 @@
new CopyOnWriteArrayList<>();
@VisibleForTesting
- protected final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- stopRecording();
- }
- };
+ final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ stopRecording();
+ }
+ };
@VisibleForTesting
protected final BroadcastReceiver mStateChangeReceiver = new BroadcastReceiver() {
@@ -92,10 +98,14 @@
* Create a new RecordingController
*/
@Inject
- public RecordingController(BroadcastDispatcher broadcastDispatcher,
- UserContextProvider userContextProvider) {
+ public RecordingController(@Main Executor mainExecutor,
+ BroadcastDispatcher broadcastDispatcher,
+ UserContextProvider userContextProvider,
+ UserTracker userTracker) {
+ mMainExecutor = mainExecutor;
mBroadcastDispatcher = broadcastDispatcher;
mUserContextProvider = userContextProvider;
+ mUserTracker = userTracker;
}
/** Create a dialog to show screen recording options to the user. */
@@ -139,9 +149,7 @@
}
try {
startIntent.send();
- IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, userFilter, null,
- UserHandle.ALL);
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
IntentFilter stateFilter = new IntentFilter(INTENT_UPDATE_STATE);
mBroadcastDispatcher.registerReceiver(mStateChangeReceiver, stateFilter, null,
@@ -211,7 +219,7 @@
public synchronized void updateState(boolean isRecording) {
if (!isRecording && mIsRecording) {
// Unregister receivers if we have stopped recording
- mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
+ mUserTracker.removeCallback(mUserChangedCallback);
mBroadcastDispatcher.unregisterReceiver(mStateChangeReceiver);
}
mIsRecording = isRecording;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 19bb15a..44b18ec 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -46,7 +46,14 @@
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val userContextProvider: UserContextProvider,
private val onStartRecordingClicked: Runnable?
-) : BaseScreenSharePermissionDialog(context, createOptionList(), null) {
+) :
+ BaseScreenSharePermissionDialog(
+ context,
+ createOptionList(),
+ null,
+ R.drawable.ic_screenrecord,
+ R.color.screenrecord_icon_color
+ ) {
private lateinit var tapsSwitch: Switch
private lateinit var tapsView: View
private lateinit var audioSwitch: Switch
@@ -169,14 +176,14 @@
private fun createOptionList(): List<ScreenShareOption> {
return listOf(
ScreenShareOption(
- SINGLE_APP,
- R.string.screenrecord_option_single_app,
- R.string.screenrecord_warning_single_app
- ),
- ScreenShareOption(
ENTIRE_SCREEN,
R.string.screenrecord_option_entire_screen,
R.string.screenrecord_warning_entire_screen
+ ),
+ ScreenShareOption(
+ SINGLE_APP,
+ R.string.screenrecord_option_single_app,
+ R.string.screenrecord_warning_single_app
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
index 914d29a..3d39fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenShareOption.kt
@@ -20,11 +20,11 @@
import kotlin.annotation.Retention
@Retention(AnnotationRetention.SOURCE)
-@IntDef(SINGLE_APP, ENTIRE_SCREEN)
+@IntDef(ENTIRE_SCREEN, SINGLE_APP)
annotation class ScreenShareMode
-const val SINGLE_APP = 0
-const val ENTIRE_SCREEN = 1
+const val ENTIRE_SCREEN = 0
+const val SINGLE_APP = 1
class ScreenShareOption(
@ScreenShareMode val mode: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 5961635..01e32b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -32,7 +32,7 @@
import com.android.internal.infra.ServiceConnector
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 javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
@@ -45,7 +45,7 @@
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- @Background private val bgDispatcher: CoroutineDispatcher,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val context: Context,
) {
/**
@@ -70,23 +70,21 @@
userId: Int,
overrideTransition: Boolean,
) {
- withContext(bgDispatcher) {
- dismissKeyguard()
+ dismissKeyguard()
- if (userId == UserHandle.myUserId()) {
- context.startActivity(intent, bundle)
- } else {
- launchCrossProfileIntent(userId, intent, bundle)
- }
+ if (userId == UserHandle.myUserId()) {
+ withContext(mainDispatcher) { context.startActivity(intent, bundle) }
+ } else {
+ launchCrossProfileIntent(userId, intent, bundle)
+ }
- if (overrideTransition) {
- val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
- try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
- } catch (e: Exception) {
- Log.e(TAG, "Error overriding screenshot app transition", e)
- }
+ if (overrideTransition) {
+ val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error overriding screenshot app transition", e)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 8bf956b..5450db9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -46,6 +46,8 @@
import com.android.systemui.R;
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;
import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot;
import com.google.common.util.concurrent.ListenableFuture;
@@ -67,6 +69,7 @@
private static final String TAG = LogConfig.logTag(LongScreenshotActivity.class);
public static final String EXTRA_CAPTURE_RESPONSE = "capture-response";
+ public static final String EXTRA_SCREENSHOT_USER_HANDLE = "screenshot-userhandle";
private static final String KEY_SAVED_IMAGE_PATH = "saved-image-path";
private final UiEventLogger mUiEventLogger;
@@ -74,6 +77,8 @@
private final Executor mBackgroundExecutor;
private final ImageExporter mImageExporter;
private final LongScreenshotData mLongScreenshotHolder;
+ private final ActionIntentExecutor mActionExecutor;
+ private final FeatureFlags mFeatureFlags;
private ImageView mPreview;
private ImageView mTransitionView;
@@ -85,6 +90,7 @@
private CropView mCropView;
private MagnifierView mMagnifierView;
private ScrollCaptureResponse mScrollCaptureResponse;
+ private UserHandle mScreenshotUserHandle;
private File mSavedImagePath;
private ListenableFuture<File> mCacheSaveFuture;
@@ -103,12 +109,15 @@
@Inject
public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter,
@Main Executor mainExecutor, @Background Executor bgExecutor,
- LongScreenshotData longScreenshotHolder) {
+ LongScreenshotData longScreenshotHolder, ActionIntentExecutor actionExecutor,
+ FeatureFlags featureFlags) {
mUiEventLogger = uiEventLogger;
mUiExecutor = mainExecutor;
mBackgroundExecutor = bgExecutor;
mImageExporter = imageExporter;
mLongScreenshotHolder = longScreenshotHolder;
+ mActionExecutor = actionExecutor;
+ mFeatureFlags = featureFlags;
}
@@ -139,6 +148,11 @@
Intent intent = getIntent();
mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE);
+ mScreenshotUserHandle = intent.getParcelableExtra(EXTRA_SCREENSHOT_USER_HANDLE,
+ UserHandle.class);
+ if (mScreenshotUserHandle == null) {
+ mScreenshotUserHandle = Process.myUserHandle();
+ }
if (savedInstanceState != null) {
String savedImagePath = savedInstanceState.getString(KEY_SAVED_IMAGE_PATH);
@@ -318,36 +332,51 @@
}
private void doEdit(Uri uri) {
- String editorPackage = getString(R.string.config_screenshotEditor);
- Intent intent = new Intent(Intent.ACTION_EDIT);
- if (!TextUtils.isEmpty(editorPackage)) {
- intent.setComponent(ComponentName.unflattenFromString(editorPackage));
- }
- intent.setDataAndType(uri, "image/png");
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
- | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY) && mScreenshotUserHandle
+ != Process.myUserHandle()) {
+ // TODO: Fix transition for work profile. Omitting it in the meantime.
+ mActionExecutor.launchIntentAsync(
+ ActionIntentCreator.INSTANCE.createEditIntent(uri, this),
+ null,
+ mScreenshotUserHandle.getIdentifier(), false);
+ } else {
+ String editorPackage = getString(R.string.config_screenshotEditor);
+ Intent intent = new Intent(Intent.ACTION_EDIT);
+ if (!TextUtils.isEmpty(editorPackage)) {
+ intent.setComponent(ComponentName.unflattenFromString(editorPackage));
+ }
+ intent.setDataAndType(uri, "image/png");
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- mTransitionView.setImageBitmap(mOutputBitmap);
- mTransitionView.setVisibility(View.VISIBLE);
- mTransitionView.setTransitionName(
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
- // TODO: listen for transition completing instead of finishing onStop
- mTransitionStarted = true;
- startActivity(intent,
- ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle());
+ mTransitionView.setImageBitmap(mOutputBitmap);
+ mTransitionView.setVisibility(View.VISIBLE);
+ mTransitionView.setTransitionName(
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
+ // TODO: listen for transition completing instead of finishing onStop
+ mTransitionStarted = true;
+ startActivity(intent,
+ ActivityOptions.makeSceneTransitionAnimation(this, mTransitionView,
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME).toBundle());
+ }
}
private void doShare(Uri uri) {
- Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setType("image/png");
- intent.putExtra(Intent.EXTRA_STREAM, uri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
- | Intent.FLAG_GRANT_READ_URI_PERMISSION);
- Intent sharingChooserIntent = Intent.createChooser(intent, null)
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ if (mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+ Intent shareIntent = ActionIntentCreator.INSTANCE.createShareIntent(uri, null);
+ mActionExecutor.launchIntentAsync(shareIntent, null,
+ mScreenshotUserHandle.getIdentifier(), false);
+ } else {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("image/png");
+ intent.putExtra(Intent.EXTRA_STREAM, uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ Intent sharingChooserIntent = Intent.createChooser(intent, null)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+ startActivityAsUser(sharingChooserIntent, UserHandle.CURRENT);
+ }
}
private void onClicked(View v) {
@@ -389,8 +418,8 @@
mOutputBitmap = renderBitmap(drawable, bounds);
ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
mBackgroundExecutor, UUID.randomUUID(), mOutputBitmap, ZonedDateTime.now(),
- // TODO: Owner must match the owner of the captured window.
- Process.myUserHandle());
+ mFeatureFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+ ? mScreenshotUserHandle : Process.myUserHandle());
exportFuture.addListener(() -> onExportCompleted(action, exportFuture), mUiExecutor);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 7143ba2..b4934cf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -38,6 +38,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -81,7 +82,6 @@
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
private String mScreenshotId;
- private final boolean mSmartActionsEnabled;
private final Random mRandom = new Random();
private final Supplier<ActionTransition> mSharedElementTransition;
private final ImageExporter mImageExporter;
@@ -109,8 +109,6 @@
mParams = data;
// Initialize screenshot notification smart actions provider.
- mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true);
mSmartActionsProvider = screenshotNotificationSmartActionsProvider;
}
@@ -131,8 +129,16 @@
Bitmap image = mParams.image;
mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, requestId);
+
+ boolean savingToOtherUser = mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+ && (user != Process.myUserHandle());
+ // Smart actions don't yet work for cross-user saves.
+ boolean smartActionsEnabled = !savingToOtherUser
+ && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
+ true);
try {
- if (mSmartActionsEnabled && mParams.mQuickShareActionsReadyListener != null) {
+ if (smartActionsEnabled && mParams.mQuickShareActionsReadyListener != null) {
// Since Quick Share target recommendation does not rely on image URL, it is
// queried and surfaced before image compress/export. Action intent would not be
// used, because it does not contain image URL.
@@ -150,10 +156,9 @@
CompletableFuture<List<Notification.Action>> smartActionsFuture =
mScreenshotSmartActions.getSmartActionsFuture(
mScreenshotId, uri, image, mSmartActionsProvider, REGULAR_SMART_ACTIONS,
- mSmartActionsEnabled, user);
-
+ smartActionsEnabled, user);
List<Notification.Action> smartActions = new ArrayList<>();
- if (mSmartActionsEnabled) {
+ if (smartActionsEnabled) {
int timeoutMs = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
@@ -168,9 +173,12 @@
mImageData.uri = uri;
mImageData.owner = user;
mImageData.smartActions = smartActions;
- mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
- mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
- mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
+ mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri,
+ smartActionsEnabled);
+ mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri,
+ smartActionsEnabled);
+ mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
+ smartActionsEnabled);
mImageData.quickShareAction = createQuickShareAction(mContext,
mQuickShareData.quickShareAction, uri);
mImageData.subject = getSubjectString();
@@ -228,7 +236,8 @@
* Assumes that the action intent is sent immediately after being supplied.
*/
@VisibleForTesting
- Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri) {
+ Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri,
+ boolean smartActionsEnabled) {
return () -> {
ActionTransition transition = mSharedElementTransition.get();
@@ -274,7 +283,7 @@
.putExtra(ScreenshotController.EXTRA_DISALLOW_ENTER_PIP, true)
.putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
+ smartActionsEnabled)
.setAction(Intent.ACTION_SEND)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
@@ -290,7 +299,8 @@
}
@VisibleForTesting
- Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri) {
+ Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri,
+ boolean smartActionsEnabled) {
return () -> {
ActionTransition transition = mSharedElementTransition.get();
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
@@ -323,7 +333,7 @@
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
.putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
+ smartActionsEnabled)
.putExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, true)
.setAction(Intent.ACTION_EDIT)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
@@ -339,7 +349,8 @@
}
@VisibleForTesting
- Notification.Action createDeleteAction(Context context, Resources r, Uri uri) {
+ Notification.Action createDeleteAction(Context context, Resources r, Uri uri,
+ boolean smartActionsEnabled) {
// Make sure pending intents for the system user are still unique across users
// by setting the (otherwise unused) request code to the current user id.
int requestCode = mContext.getUserId();
@@ -350,7 +361,7 @@
.putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString())
.putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
+ smartActionsEnabled)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT
| PendingIntent.FLAG_ONE_SHOT
@@ -391,7 +402,7 @@
Intent intent = new Intent(context, SmartActionsReceiver.class)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, action.actionIntent)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+ addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
mRandom.nextInt(),
intent,
@@ -445,7 +456,9 @@
Intent intent = new Intent(context, SmartActionsReceiver.class)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, updatedPendingIntent)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+ // We only query for quick share actions when smart actions are enabled, so we can assert
+ // that it's true here.
+ addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
mRandom.nextInt(),
intent,
@@ -464,7 +477,7 @@
mScreenshotSmartActions.getSmartActionsFuture(
mScreenshotId, null, image, mSmartActionsProvider,
QUICK_SHARE_ACTION,
- mSmartActionsEnabled, user);
+ true /* smartActionsEnabled */, user);
int timeoutMs = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_QUICK_SHARE_ACTIONS_TIMEOUT_MS,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index d395bd3..a6447a5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -84,6 +84,8 @@
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
import android.window.WindowContext;
import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -279,6 +281,13 @@
private final ActionIntentExecutor mActionExecutor;
private final UserManager mUserManager;
+ private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "Predictive Back callback dispatched");
+ }
+ respondToBack();
+ };
+
private ScreenshotView mScreenshotView;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
@@ -465,6 +474,10 @@
}
}
+ private void respondToBack() {
+ dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+ }
+
/**
* Update resources on configuration change. Reinflate for theme/color changes.
*/
@@ -476,6 +489,26 @@
// Inflate the screenshot layout
mScreenshotView = (ScreenshotView)
LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
+ mScreenshotView.addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(@NonNull View v) {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "Registering Predictive Back callback");
+ }
+ mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(@NonNull View v) {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "Unregistering Predictive Back callback");
+ }
+ mScreenshotView.findOnBackInvokedDispatcher()
+ .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+ }
+ });
mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
@Override
public void onUserInteraction() {
@@ -503,7 +536,7 @@
if (DEBUG_INPUT) {
Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
}
- dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+ respondToBack();
return true;
}
return false;
@@ -546,9 +579,16 @@
private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
Insets screenInsets, ComponentName topComponent, boolean showFlash, UserHandle owner) {
- withWindowAttached(() ->
+ withWindowAttached(() -> {
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+ && mUserManager.isManagedProfile(owner.getIdentifier())) {
+ mScreenshotView.announceForAccessibility(mContext.getResources().getString(
+ R.string.screenshot_saving_work_profile_title));
+ } else {
mScreenshotView.announceForAccessibility(
- mContext.getResources().getString(R.string.screenshot_saving_title)));
+ mContext.getResources().getString(R.string.screenshot_saving_title));
+ }
+ });
mScreenshotView.reset();
@@ -591,7 +631,7 @@
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
withWindowAttached(() -> {
- requestScrollCapture();
+ requestScrollCapture(owner);
mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
new ViewRootImpl.ActivityConfigCallback() {
@Override
@@ -603,11 +643,11 @@
mScreenshotView.hideScrollChip();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
- mScreenshotHandler.postDelayed(
- ScreenshotController.this::requestScrollCapture, 150);
+ mScreenshotHandler.postDelayed(() -> {
+ requestScrollCapture(owner);
+ }, 150);
mScreenshotView.updateInsets(
- mWindowManager.getCurrentWindowMetrics()
- .getWindowInsets());
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
// so just end
if (mScreenshotAnimation != null
@@ -655,7 +695,7 @@
mScreenshotHandler.cancelTimeout(); // restarted after animation
}
- private void requestScrollCapture() {
+ private void requestScrollCapture(UserHandle owner) {
if (!allowLongScreenshots()) {
Log.d(TAG, "Long screenshots not supported on this device");
return;
@@ -668,10 +708,11 @@
mScrollCaptureClient.request(DEFAULT_DISPLAY);
mLastScrollCaptureRequest = future;
mLastScrollCaptureRequest.addListener(() ->
- onScrollCaptureResponseReady(future), mMainExecutor);
+ onScrollCaptureResponseReady(future, owner), mMainExecutor);
}
- private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture) {
+ private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture,
+ UserHandle owner) {
try {
if (mLastScrollCaptureResponse != null) {
mLastScrollCaptureResponse.close();
@@ -701,7 +742,7 @@
mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
mScreenshotTakenInPortrait);
// delay starting scroll capture to make sure the scrim is up before the app moves
- mScreenshotView.post(() -> runBatchScrollCapture(response));
+ mScreenshotView.post(() -> runBatchScrollCapture(response, owner));
});
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "requestScrollCapture failed", e);
@@ -710,7 +751,7 @@
ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
- private void runBatchScrollCapture(ScrollCaptureResponse response) {
+ private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
// Clear the reference to prevent close() in dismissScreenshot
mLastScrollCaptureResponse = null;
@@ -738,12 +779,18 @@
mLongScreenshotHolder.setLongScreenshot(longScreenshot);
mLongScreenshotHolder.setTransitionDestinationCallback(
- (transitionDestination, onTransitionEnd) ->
+ (transitionDestination, onTransitionEnd) -> {
mScreenshotView.startLongScreenshotTransition(
transitionDestination, onTransitionEnd,
- longScreenshot));
+ longScreenshot);
+ // TODO: Do this via ActionIntentExecutor instead.
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ }
+ );
final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
+ intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE,
+ owner);
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -965,13 +1012,8 @@
if (imageData.uri != null) {
if (!imageData.owner.equals(Process.myUserHandle())) {
- // TODO: Handle non-primary user ownership (e.g. Work Profile)
- // This image is owned by another user. Special treatment will be
- // required in the UI (badging) as well as sending intents which can
- // correctly forward those URIs on to be read (actions).
-
- Log.d(TAG, "*** Screenshot saved to a non-primary user ("
- + imageData.owner + ") as " + imageData.uri);
+ Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as "
+ + imageData.uri);
}
mScreenshotHandler.post(() -> {
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
@@ -1052,6 +1094,11 @@
R.string.screenshot_failed_to_save_text);
} else {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+ && mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0,
+ mPackageName);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 8b5a24c..c891686 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -89,7 +89,9 @@
@UiEvent(doc = "User has saved a long screenshot to a file")
SCREENSHOT_LONG_SCREENSHOT_SAVED(910),
@UiEvent(doc = "User has discarded the result of a long screenshot")
- SCREENSHOT_LONG_SCREENSHOT_EXIT(911);
+ SCREENSHOT_LONG_SCREENSHOT_EXIT(911),
+ @UiEvent(doc = "A screenshot has been taken and saved to work profile")
+ SCREENSHOT_SAVED_TO_WORK_PROFILE(1240);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 0a4b550..fae938d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -825,12 +825,23 @@
}
});
if (mQuickShareChip != null) {
- mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
- () -> {
- mUiEventLogger.log(
- ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0, mPackageName);
- animateDismissal();
- });
+ if (imageData.quickShareAction != null) {
+ mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
+ () -> {
+ mUiEventLogger.log(
+ ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED, 0,
+ mPackageName);
+ animateDismissal();
+ });
+ } else {
+ // hide chip and unset pending interaction if necessary, since we don't actually
+ // have a useable quick share intent
+ Log.wtf(TAG, "Showed quick share chip, but quick share intent was null");
+ if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
+ mPendingInteraction = null;
+ }
+ mQuickShareChip.setVisibility(GONE);
+ }
}
if (mPendingInteraction != null) {
@@ -898,6 +909,7 @@
void startLongScreenshotTransition(Rect destination, Runnable onTransitionEnd,
ScrollCaptureController.LongScreenshot longScreenshot) {
+ mPendingSharedTransition = true;
AnimatorSet animSet = new AnimatorSet();
ValueAnimator scrimAnim = ValueAnimator.ofFloat(0, 1);
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index 2d1d8b7..d33d113 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -29,6 +29,8 @@
import android.hardware.SensorPrivacyManager.Sources.DIALOG
import android.os.Bundle
import android.os.Handler
+import android.window.OnBackInvokedDispatcher
+import androidx.annotation.OpenForTesting
import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
@@ -45,7 +47,8 @@
*
* <p>The dialog is started for the user the app is running for which might be a secondary users.
*/
-class SensorUseStartedActivity @Inject constructor(
+@OpenForTesting
+open class SensorUseStartedActivity @Inject constructor(
private val sensorPrivacyController: IndividualSensorPrivacyController,
private val keyguardStateController: KeyguardStateController,
private val keyguardDismissUtil: KeyguardDismissUtil,
@@ -67,9 +70,10 @@
private lateinit var sensorUsePackageName: String
private var unsuppressImmediately = false
- private lateinit var sensorPrivacyListener: IndividualSensorPrivacyController.Callback
+ private var sensorPrivacyListener: IndividualSensorPrivacyController.Callback? = null
private var mDialog: AlertDialog? = null
+ private val mBackCallback = this::onBackInvoked
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -84,15 +88,14 @@
if (intent.getBooleanExtra(EXTRA_ALL_SENSORS, false)) {
sensor = ALL_SENSORS
- sensorPrivacyListener =
- IndividualSensorPrivacyController.Callback { _, _ ->
- if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
- !sensorPrivacyController.isSensorBlocked(CAMERA)) {
- finish()
- }
- }
-
- sensorPrivacyController.addCallback(sensorPrivacyListener)
+ val callback = IndividualSensorPrivacyController.Callback { _, _ ->
+ if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
+ !sensorPrivacyController.isSensorBlocked(CAMERA)) {
+ finish()
+ }
+ }
+ sensorPrivacyListener = callback
+ sensorPrivacyController.addCallback(callback)
if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
!sensorPrivacyController.isSensorBlocked(CAMERA)) {
finish()
@@ -105,14 +108,14 @@
return
}
}
- sensorPrivacyListener =
- IndividualSensorPrivacyController.Callback { whichSensor: Int,
- isBlocked: Boolean ->
- if (whichSensor == sensor && !isBlocked) {
- finish()
- }
- }
- sensorPrivacyController.addCallback(sensorPrivacyListener)
+ val callback = IndividualSensorPrivacyController.Callback {
+ whichSensor: Int, isBlocked: Boolean ->
+ if (whichSensor == sensor && !isBlocked) {
+ finish()
+ }
+ }
+ sensorPrivacyListener = callback
+ sensorPrivacyController.addCallback(callback)
if (!sensorPrivacyController.isSensorBlocked(sensor)) {
finish()
@@ -122,6 +125,10 @@
mDialog = SensorUseDialog(this, sensor, this, this)
mDialog!!.show()
+
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ mBackCallback)
}
override fun onStart() {
@@ -180,10 +187,15 @@
override fun onDestroy() {
super.onDestroy()
mDialog?.dismiss()
- sensorPrivacyController.removeCallback(sensorPrivacyListener)
+ sensorPrivacyListener?.also { sensorPrivacyController.removeCallback(it) }
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mBackCallback)
}
override fun onBackPressed() {
+ onBackInvoked()
+ }
+
+ fun onBackInvoked() {
// do not allow backing out
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java
deleted file mode 100644
index dea8c32..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserObservable.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2019 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.settings;
-
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-/**
- * A class that has an observable for the current user.
- */
-public class CurrentUserObservable {
-
- private final CurrentUserTracker mTracker;
-
- private final MutableLiveData<Integer> mCurrentUser = new MutableLiveData<Integer>() {
- @Override
- protected void onActive() {
- super.onActive();
- mTracker.startTracking();
- }
-
- @Override
- protected void onInactive() {
- super.onInactive();
- mTracker.stopTracking();
- }
- };
-
- public CurrentUserObservable(BroadcastDispatcher broadcastDispatcher) {
- mTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- mCurrentUser.setValue(newUserId);
- }
- };
- }
-
- /**
- * Returns the current user that can be observed.
- */
- public LiveData<Integer> getCurrentUser() {
- if (mCurrentUser.getValue() == null) {
- mCurrentUser.setValue(mTracker.getCurrentUserId());
- }
- return mCurrentUser;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
deleted file mode 100644
index 9599d77..0000000
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2013 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.settings;
-
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-public abstract class CurrentUserTracker {
- private final UserReceiver mUserReceiver;
-
- private Consumer<Integer> mCallback = this::onUserSwitched;
-
- public CurrentUserTracker(BroadcastDispatcher broadcastDispatcher) {
- this(UserReceiver.getInstance(broadcastDispatcher));
- }
-
- @VisibleForTesting
- CurrentUserTracker(UserReceiver receiver) {
- mUserReceiver = receiver;
- }
-
- public int getCurrentUserId() {
- return mUserReceiver.getCurrentUserId();
- }
-
- public void startTracking() {
- mUserReceiver.addTracker(mCallback);
- }
-
- public void stopTracking() {
- mUserReceiver.removeTracker(mCallback);
- }
-
- public abstract void onUserSwitched(int newUserId);
-
- @VisibleForTesting
- static class UserReceiver extends BroadcastReceiver {
- private static UserReceiver sInstance;
-
- private boolean mReceiverRegistered;
- private int mCurrentUserId;
- private final BroadcastDispatcher mBroadcastDispatcher;
-
- private List<Consumer<Integer>> mCallbacks = new ArrayList<>();
-
- @VisibleForTesting
- UserReceiver(BroadcastDispatcher broadcastDispatcher) {
- mBroadcastDispatcher = broadcastDispatcher;
- }
-
- static UserReceiver getInstance(BroadcastDispatcher broadcastDispatcher) {
- if (sInstance == null) {
- sInstance = new UserReceiver(broadcastDispatcher);
- }
- return sInstance;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
- notifyUserSwitched(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- }
- }
-
- public int getCurrentUserId() {
- return mCurrentUserId;
- }
-
- private void addTracker(Consumer<Integer> callback) {
- if (!mCallbacks.contains(callback)) {
- mCallbacks.add(callback);
- }
- if (!mReceiverRegistered) {
- mCurrentUserId = ActivityManager.getCurrentUser();
- IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(this, filter, null,
- UserHandle.ALL);
- mReceiverRegistered = true;
- }
- }
-
- private void removeTracker(Consumer<Integer> callback) {
- if (mCallbacks.contains(callback)) {
- mCallbacks.remove(callback);
- if (mCallbacks.size() == 0 && mReceiverRegistered) {
- mBroadcastDispatcher.unregisterReceiver(this);
- mReceiverRegistered = false;
- }
- }
- }
-
- private void notifyUserSwitched(int newUserId) {
- if (mCurrentUserId != newUserId) {
- mCurrentUserId = newUserId;
- List<Consumer<Integer>> callbacks = new ArrayList<>(mCallbacks);
- for (Consumer<Integer> consumer : callbacks) {
- // Accepting may modify this list
- if (mCallbacks.contains(consumer)) {
- consumer.accept(newUserId);
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
index d450afa..bfba6df 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
@@ -35,12 +35,14 @@
import javax.inject.Inject
/**
- * Implementation for retrieving file paths for file storage of system and secondary users.
- * Files lie in {File Directory}/UserFileManager/{User Id} for secondary user.
- * For system user, we use the conventional {File Directory}
+ * Implementation for retrieving file paths for file storage of system and secondary users. Files
+ * lie in {File Directory}/UserFileManager/{User Id} for secondary user. For system user, we use the
+ * conventional {File Directory}
*/
@SysUISingleton
-class UserFileManagerImpl @Inject constructor(
+class UserFileManagerImpl
+@Inject
+constructor(
// Context of system process and system user.
private val context: Context,
val userManager: UserManager,
@@ -49,80 +51,114 @@
) : UserFileManager, CoreStartable {
companion object {
private const val FILES = "files"
- @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
+ const val SHARED_PREFS = "shared_prefs"
@VisibleForTesting internal const val ID = "UserFileManager"
- }
- private val broadcastReceiver = object : BroadcastReceiver() {
+ /** Returns `true` if the given user ID is that for the primary/system user. */
+ fun isPrimaryUser(userId: Int): Boolean {
+ return UserHandle(userId).isSystem
+ }
+
/**
- * Listen to Intent.ACTION_USER_REMOVED to clear user data.
+ * Returns a [File] pointing to the correct path for a secondary user ID.
+ *
+ * Note that there is no check for the type of user. This should only be called for
+ * secondary users, never for the system user. For that, make sure to call [isPrimaryUser].
+ *
+ * Note also that there is no guarantee that the parent directory structure for the file
+ * exists on disk. For that, call [ensureParentDirExists].
+ *
+ * @param context The context
+ * @param fileName The name of the file
+ * @param directoryName The name of the directory that would contain the file
+ * @param userId The ID of the user to build a file path for
*/
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action == Intent.ACTION_USER_REMOVED) {
- clearDeletedUserData()
+ fun secondaryUserFile(
+ context: Context,
+ fileName: String,
+ directoryName: String,
+ userId: Int,
+ ): File {
+ return Environment.buildPath(
+ context.filesDir,
+ ID,
+ userId.toString(),
+ directoryName,
+ fileName,
+ )
+ }
+
+ /**
+ * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
+ * recursively.
+ */
+ fun ensureParentDirExists(file: File) {
+ val parent = file.parentFile
+ if (!parent.exists()) {
+ if (!parent.mkdirs()) {
+ Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
+ }
}
}
}
- /**
- * Poll for user-specific directories to delete upon start up.
- */
+ private val broadcastReceiver =
+ object : BroadcastReceiver() {
+ /** Listen to Intent.ACTION_USER_REMOVED to clear user data. */
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == Intent.ACTION_USER_REMOVED) {
+ clearDeletedUserData()
+ }
+ }
+ }
+
+ /** Poll for user-specific directories to delete upon start up. */
override fun start() {
clearDeletedUserData()
- val filter = IntentFilter().apply {
- addAction(Intent.ACTION_USER_REMOVED)
- }
+ val filter = IntentFilter().apply { addAction(Intent.ACTION_USER_REMOVED) }
broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor)
}
- /**
- * Return the file based on current user.
- */
+ /** Return the file based on current user. */
override fun getFile(fileName: String, userId: Int): File {
- return if (UserHandle(userId).isSystem) {
- Environment.buildPath(
- context.filesDir,
- fileName
- )
+ return if (isPrimaryUser(userId)) {
+ Environment.buildPath(context.filesDir, fileName)
} else {
- val secondaryFile = Environment.buildPath(
- context.filesDir,
- ID,
- userId.toString(),
- FILES,
- fileName
- )
+ val secondaryFile =
+ secondaryUserFile(
+ context = context,
+ userId = userId,
+ directoryName = FILES,
+ fileName = fileName,
+ )
ensureParentDirExists(secondaryFile)
secondaryFile
}
}
- /**
- * Get shared preferences from user.
- */
+ /** Get shared preferences from user. */
override fun getSharedPreferences(
fileName: String,
@Context.PreferencesMode mode: Int,
userId: Int
): SharedPreferences {
- if (UserHandle(userId).isSystem) {
+ if (isPrimaryUser(userId)) {
return context.getSharedPreferences(fileName, mode)
}
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- ID,
- userId.toString(),
- SHARED_PREFS,
- fileName
- )
+
+ val secondaryUserDir =
+ secondaryUserFile(
+ context = context,
+ fileName = fileName,
+ directoryName = SHARED_PREFS,
+ userId = userId,
+ )
ensureParentDirExists(secondaryUserDir)
return context.getSharedPreferences(secondaryUserDir, mode)
}
- /**
- * Remove dirs for deleted users.
- */
+ /** Remove dirs for deleted users. */
@VisibleForTesting
internal fun clearDeletedUserData() {
backgroundExecutor.execute {
@@ -133,10 +169,11 @@
dirsToDelete.forEach { dir ->
try {
- val dirToDelete = Environment.buildPath(
- file,
- dir,
- )
+ val dirToDelete =
+ Environment.buildPath(
+ file,
+ dir,
+ )
dirToDelete.deleteRecursively()
} catch (e: Exception) {
Log.e(ID, "Deletion failed.", e)
@@ -144,18 +181,4 @@
}
}
}
-
- /**
- * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
- * recursively.
- */
- @VisibleForTesting
- internal fun ensureParentDirExists(file: File) {
- val parent = file.parentFile
- if (!parent.exists()) {
- if (!parent.mkdirs()) {
- Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 7801c68..5880003 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -21,6 +21,7 @@
import static com.android.settingslib.display.BrightnessUtils.convertLinearToGammaFloat;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -46,11 +47,13 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtilsInternal;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import java.util.concurrent.Executor;
+
import javax.inject.Inject;
public class BrightnessController implements ToggleSlider.Listener, MirroredBrightnessController {
@@ -74,9 +77,10 @@
private final Context mContext;
private final ToggleSlider mControl;
private final DisplayManager mDisplayManager;
- private final CurrentUserTracker mUserTracker;
+ private final UserTracker mUserTracker;
private final IVrManager mVrManager;
+ private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
private final BrightnessObserver mBrightnessObserver;
@@ -169,7 +173,7 @@
}
mBrightnessObserver.startObserving();
- mUserTracker.startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
// Update the slider and mode before attaching the listener so we don't
// receive the onChanged notifications for the initial values.
@@ -197,7 +201,7 @@
}
mBrightnessObserver.stopObserving();
- mUserTracker.stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
mHandler.sendEmptyMessage(MSG_DETACH_LISTENER);
}
@@ -275,22 +279,27 @@
}
};
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mBackgroundHandler.post(mUpdateModeRunnable);
+ mBackgroundHandler.post(mUpdateSliderRunnable);
+ }
+ };
+
public BrightnessController(
Context context,
ToggleSlider control,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
+ @Main Executor mainExecutor,
@Background Handler bgHandler) {
mContext = context;
mControl = control;
mControl.setMax(GAMMA_SPACE_MAX);
+ mMainExecutor = mainExecutor;
mBackgroundHandler = bgHandler;
- mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- mBackgroundHandler.post(mUpdateModeRunnable);
- mBackgroundHandler.post(mUpdateSliderRunnable);
- }
- };
+ mUserTracker = userTracker;
mBrightnessObserver = new BrightnessObserver(mHandler);
mDisplayId = mContext.getDisplayId();
@@ -364,7 +373,7 @@
mControl.setEnforcedAdmin(
RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
UserManager.DISALLOW_CONFIG_BRIGHTNESS,
- mUserTracker.getCurrentUserId()));
+ mUserTracker.getUserId()));
}
});
}
@@ -440,16 +449,19 @@
/** Factory for creating a {@link BrightnessController}. */
public static class Factory {
private final Context mContext;
- private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
+ private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
@Inject
public Factory(
Context context,
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
+ @Main Executor mainExecutor,
@Background Handler bgHandler) {
mContext = context;
- mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
+ mMainExecutor = mainExecutor;
mBackgroundHandler = bgHandler;
}
@@ -458,7 +470,8 @@
return new BrightnessController(
mContext,
toggleSlider,
- mBroadcastDispatcher,
+ mUserTracker,
+ mMainExecutor,
mBackgroundHandler);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index d5a3954..e208be9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -34,10 +34,12 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -46,16 +48,19 @@
private BrightnessController mBrightnessController;
private final BrightnessSliderController.Factory mToggleSliderFactory;
- private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
+ private final Executor mMainExecutor;
private final Handler mBackgroundHandler;
@Inject
public BrightnessDialog(
- BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
BrightnessSliderController.Factory factory,
+ @Main Executor mainExecutor,
@Background Handler bgHandler) {
- mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
mToggleSliderFactory = factory;
+ mMainExecutor = mainExecutor;
mBackgroundHandler = bgHandler;
}
@@ -101,7 +106,7 @@
frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
mBrightnessController = new BrightnessController(
- this, controller, mBroadcastDispatcher, mBackgroundHandler);
+ this, controller, mUserTracker, mMainExecutor, mBackgroundHandler);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 4063af3..5011227 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -51,6 +51,8 @@
connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
+ constrainedWidth(R.id.date, true)
+ constrainedWidth(R.id.statusIcons, true)
}
)
}
@@ -92,7 +94,8 @@
centerEnd,
ConstraintSet.END
)
- constrainWidth(R.id.statusIcons, 0)
+ constrainedWidth(R.id.date, true)
+ constrainedWidth(R.id.statusIcons, true)
},
qsConstraintsChanges = {
setGuidelineBegin(centerStart, offsetFromEdge)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 63d0d16..5e47d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -19,6 +19,7 @@
import android.annotation.IdRes
import android.app.StatusBarManager
import android.content.res.Configuration
+import android.os.Bundle
import android.os.Trace
import android.os.Trace.TRACE_TAG_APP
import android.util.Pair
@@ -34,6 +35,8 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -53,6 +56,7 @@
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
+import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.VariableDateView
import com.android.systemui.statusbar.policy.VariableDateViewController
@@ -89,7 +93,8 @@
private val dumpManager: DumpManager,
private val featureFlags: FeatureFlags,
private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
- private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager
+ private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
+ private val demoModeController: DemoModeController
) : ViewController<View>(header), Dumpable {
companion object {
@@ -126,7 +131,7 @@
private lateinit var qsCarrierGroupController: QSCarrierGroupController
private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
- private val clock: TextView = header.findViewById(R.id.clock)
+ private val clock: Clock = header.findViewById(R.id.clock)
private val date: TextView = header.findViewById(R.id.date)
private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
@@ -212,6 +217,14 @@
view.onApplyWindowInsets(insets)
}
+ private val demoModeReceiver = object : DemoMode {
+ override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK)
+ override fun dispatchDemoCommand(command: String, args: Bundle) =
+ clock.dispatchDemoCommand(command, args)
+ override fun onDemoModeStarted() = clock.onDemoModeStarted()
+ override fun onDemoModeFinished() = clock.onDemoModeFinished()
+ }
+
private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
override fun onChipVisibilityRefreshed(visible: Boolean) {
if (header is MotionLayout) {
@@ -300,6 +313,7 @@
dumpManager.registerDumpable(this)
configurationController.addCallback(configurationControllerListener)
+ demoModeController.addCallback(demoModeReceiver)
updateVisibility()
updateTransition()
@@ -309,6 +323,7 @@
privacyIconsController.chipVisibilityListener = null
dumpManager.unregisterDumpable(this::class.java.simpleName)
configurationController.removeCallback(configurationControllerListener)
+ demoModeController.removeCallback(demoModeReceiver)
}
fun disable(state1: Int, state2: Int, animate: Boolean) {
@@ -331,13 +346,8 @@
// Use resources.getXml instead of passing the resource id due to bug b/205018300
header.getConstraintSet(QQS_HEADER_CONSTRAINT)
.load(context, resources.getXml(R.xml.qqs_header))
- val qsConstraints = if (featureFlags.isEnabled(Flags.NEW_HEADER)) {
- R.xml.qs_header_new
- } else {
- R.xml.qs_header
- }
header.getConstraintSet(QS_HEADER_CONSTRAINT)
- .load(context, resources.getXml(qsConstraints))
+ .load(context, resources.getXml(R.xml.qs_header))
header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
.load(context, resources.getXml(R.xml.large_screen_shade_header))
}
@@ -526,4 +536,7 @@
updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges)
}
}
+
+ @VisibleForTesting
+ internal fun simulateViewDetached() = this.onViewDetached()
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 32c8f3b..f52ace7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -177,6 +177,7 @@
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -253,6 +254,8 @@
private static final int FLING_COLLAPSE = 1;
/** Fling until QS is completely hidden. */
private static final int FLING_HIDE = 2;
+ /** The delay to reset the hint text when the hint animation is finished running. */
+ private static final int HINT_RESET_DELAY_MS = 1200;
private static final long ANIMATION_DELAY_ICON_FADE_IN =
ActivityLaunchAnimator.TIMINGS.getTotalDuration()
- CollapsedStatusBarFragment.FADE_IN_DURATION
@@ -276,6 +279,11 @@
private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
private static final Rect EMPTY_RECT = new Rect();
+ /**
+ * Duration to use for the animator when the keyguard status view alignment changes, and a
+ * custom clock animation is in use.
+ */
+ private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
private final Resources mResources;
@@ -343,6 +351,7 @@
private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired;
private final FragmentListener mQsFragmentListener = new QsFragmentListener();
private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
+ private final NotificationGutsManager mGutsManager;
private long mDownTime;
private boolean mTouchSlopExceededBeforeDown;
@@ -384,6 +393,9 @@
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private int mQsTrackingPointer;
private VelocityTracker mQsVelocityTracker;
+ private TrackingStartedListener mTrackingStartedListener;
+ private OpenCloseListener mOpenCloseListener;
+ private GestureRecorder mGestureRecorder;
private boolean mQsTracking;
/** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */
private boolean mConflictingQsExpansionGesture;
@@ -625,7 +637,6 @@
private float mLastGesturedOverExpansion = -1;
/** Whether the current animator is the spring back animation. */
private boolean mIsSpringBackAnimation;
- private boolean mInSplitShade;
private float mHintDistance;
private float mInitialOffsetOnTouch;
private boolean mCollapsedAndHeadsUpOnDown;
@@ -672,7 +683,7 @@
};
private final Runnable mMaybeHideExpandedRunnable = () -> {
if (getExpansionFraction() == 0.0f) {
- getView().post(mHideExpandedRunnable);
+ postToView(mHideExpandedRunnable);
}
};
@@ -702,6 +713,7 @@
ConversationNotificationManager conversationNotificationManager,
MediaHierarchyManager mediaHierarchyManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ NotificationGutsManager gutsManager,
NotificationsQSContainerController notificationsQSContainerController,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
@@ -755,6 +767,7 @@
mLockscreenGestureLogger = lockscreenGestureLogger;
mShadeExpansionStateManager = shadeExpansionStateManager;
mShadeLog = shadeLogger;
+ mGutsManager = gutsManager;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -1061,7 +1074,6 @@
mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
- mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get()
.setMaxLengthSeconds(0.4f).build();
mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
@@ -1353,6 +1365,14 @@
mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
}
+ void setOpenCloseListener(OpenCloseListener openCloseListener) {
+ mOpenCloseListener = openCloseListener;
+ }
+
+ void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) {
+ mTrackingStartedListener = trackingStartedListener;
+ }
+
private void updateGestureExclusionRect() {
Rect exclusionRect = calculateGestureExclusionRect();
mView.setSystemGestureExclusionRects(exclusionRect.isEmpty() ? Collections.emptyList()
@@ -1569,23 +1589,31 @@
// Find the clock, so we can exclude it from this transition.
FrameLayout clockContainerView =
mView.findViewById(R.id.lockscreen_clock_view_large);
- View clockView = clockContainerView.getChildAt(0);
- transition.excludeTarget(clockView, /* exclude= */ true);
+ // The clock container can sometimes be null. If it is, just fall back to the
+ // old animation rather than setting up the custom animations.
+ if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
+ TransitionManager.beginDelayedTransition(
+ mNotificationContainerParent, transition);
+ } else {
+ View clockView = clockContainerView.getChildAt(0);
- TransitionSet set = new TransitionSet();
- set.addTransition(transition);
+ transition.excludeTarget(clockView, /* exclude= */ true);
- SplitShadeTransitionAdapter adapter =
- new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
+ TransitionSet set = new TransitionSet();
+ set.addTransition(transition);
- // Use linear here, so the actual clock can pick its own interpolator.
- adapter.setInterpolator(Interpolators.LINEAR);
- adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- adapter.addTarget(clockView);
- set.addTransition(adapter);
+ SplitShadeTransitionAdapter adapter =
+ new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
- TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+ // Use linear here, so the actual clock can pick its own interpolator.
+ adapter.setInterpolator(Interpolators.LINEAR);
+ adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
+ adapter.addTarget(clockView);
+ set.addTransition(adapter);
+
+ TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+ }
} else {
TransitionManager.beginDelayedTransition(
mNotificationContainerParent, transition);
@@ -1760,7 +1788,7 @@
}
public void resetViews(boolean animate) {
- mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
+ mGutsManager.closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
animateCloseQs(true /* animateAway */);
@@ -1836,6 +1864,10 @@
public void closeQs() {
cancelQsAnimation();
setQsExpansionHeight(mQsMinExpansionHeight);
+ // qsExpandImmediate is a safety latch in case we're calling closeQS while we're in the
+ // middle of animation - we need to make sure that value is always false when shade if
+ // fully collapsed or expanded
+ setQsExpandImmediate(false);
}
@VisibleForTesting
@@ -1915,9 +1947,9 @@
}
private void fling(float vel) {
- GestureRecorder gr = mCentralSurfaces.getGestureRecorder();
- if (gr != null) {
- gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
+ if (mGestureRecorder != null) {
+ mGestureRecorder.tag("fling " + ((vel > 0) ? "open" : "closed"),
+ "notifications,v=" + vel);
}
fling(vel, true, 1.0f /* collapseSpeedUpFactor */, false);
}
@@ -1938,7 +1970,7 @@
// we want to perform an overshoot animation when flinging open
final boolean addOverscroll =
expand
- && !mInSplitShade // Split shade has its own overscroll logic
+ && !mSplitShadeEnabled // Split shade has its own overscroll logic
&& mStatusBarStateController.getState() != KEYGUARD
&& mOverExpansion == 0.0f
&& vel >= 0;
@@ -2051,6 +2083,14 @@
mInitialTouchX = x;
initVelocityTracker();
trackMovement(event);
+ float qsExpansionFraction = computeQsExpansionFraction();
+ // Intercept the touch if QS is between fully collapsed and fully expanded state
+ if (!mSplitShadeEnabled
+ && qsExpansionFraction > 0.0 && qsExpansionFraction < 1.0) {
+ mShadeLog.logMotionEvent(event,
+ "onQsIntercept: down action, QS partially expanded/collapsed");
+ return true;
+ }
if (mKeyguardShowing
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
// Dragging down on the lockscreen statusbar should prohibit other interactions
@@ -2303,6 +2343,14 @@
if (!isFullyCollapsed()) {
handleQsDown(event);
}
+ // defer touches on QQS to shade while shade is collapsing. Added margin for error
+ // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS.
+ if (!mSplitShadeEnabled
+ && computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) {
+ mShadeLog.logMotionEvent(event,
+ "handleQsTouch: QQS touched while shade collapsing");
+ mQsTracking = false;
+ }
if (!mQsExpandImmediate && mQsTracking) {
onQsTouch(event);
if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) {
@@ -2543,7 +2591,6 @@
// Reset scroll position and apply that position to the expanded height.
float height = mQsExpansionHeight;
setQsExpansionHeight(height);
- updateExpandedHeightToMaxHeight();
mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
// When expanding QS, let's authenticate the user if possible,
@@ -2727,8 +2774,10 @@
* as well based on the bounds of the shade and QS state.
*/
private void setQSClippingBounds() {
- final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction());
- final boolean qsVisible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0);
+ float qsExpansionFraction = computeQsExpansionFraction();
+ final int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
+ final boolean qsVisible = (qsExpansionFraction > 0 || qsPanelBottomY > 0);
+ checkCorrectScrimVisibility(qsExpansionFraction);
int top = calculateTopQsClippingBound(qsPanelBottomY);
int bottom = calculateBottomQsClippingBound(top);
@@ -2739,6 +2788,19 @@
applyQSClippingBounds(left, top, right, bottom, qsVisible);
}
+ private void checkCorrectScrimVisibility(float expansionFraction) {
+ // issues with scrims visible on keyguard occur only in split shade
+ if (mSplitShadeEnabled) {
+ boolean keyguardViewsVisible = mBarState == KEYGUARD && mKeyguardOnlyContentAlpha == 1;
+ // expansionFraction == 1 means scrims are fully visible as their size/visibility depend
+ // on QS expansion
+ if (expansionFraction == 1 && keyguardViewsVisible) {
+ Log.wtf(TAG,
+ "Incorrect state, scrim is visible at the same time when clock is visible");
+ }
+ }
+ }
+
private int calculateTopQsClippingBound(int qsPanelBottomY) {
int top;
if (mSplitShadeEnabled) {
@@ -2779,7 +2841,7 @@
return top + mNotificationStackScrollLayoutController.getHeight()
+ mSplitShadeNotificationsScrimMarginBottom;
} else {
- return getView().getBottom();
+ return mView.getBottom();
}
}
@@ -2794,7 +2856,7 @@
private int calculateRightQsClippingBound() {
if (mIsFullWidth) {
- return getView().getRight() + mDisplayRightInset;
+ return mView.getRight() + mDisplayRightInset;
} else {
return mNotificationStackScrollLayoutController.getRight();
}
@@ -3478,13 +3540,17 @@
}
private float getHeaderTranslation() {
+ if (mSplitShadeEnabled) {
+ // in split shade QS don't translate, just (un)squish and overshoot
+ return 0;
+ }
if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
return -mQs.getQsMinExpansionHeight();
}
float appearAmount = mNotificationStackScrollLayoutController
.calculateAppearFraction(mExpandedHeight);
float startHeight = -mQsExpansionHeight;
- if (!mSplitShadeEnabled && mBarState == StatusBarState.SHADE) {
+ if (mBarState == StatusBarState.SHADE) {
// Small parallax as we pull down and clip QS
startHeight = -mQsExpansionHeight * QS_PARALLAX_AMOUNT;
}
@@ -3671,7 +3737,7 @@
mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
endClosing();
mTracking = true;
- mCentralSurfaces.onTrackingStarted();
+ mTrackingStartedListener.onTrackingStarted();
notifyExpandingStarted();
updatePanelExpansionAndVisibility();
mScrimController.onTrackingStarted();
@@ -3686,7 +3752,6 @@
private void onTrackingStopped(boolean expand) {
mFalsingCollector.onTrackingStopped();
mTracking = false;
- mCentralSurfaces.onTrackingStopped(expand);
updatePanelExpansionAndVisibility();
if (expand) {
mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
@@ -3729,14 +3794,16 @@
@VisibleForTesting
void onUnlockHintFinished() {
- mCentralSurfaces.onHintFinished();
+ // Delay the reset a bit so the user can read the text.
+ mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
mScrimController.setExpansionAffectsAlpha(true);
mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
}
@VisibleForTesting
void onUnlockHintStarted() {
- mCentralSurfaces.onUnlockHintStarted();
+ mFalsingCollector.onUnlockHintStarted();
+ mKeyguardIndicationController.showActionToUnlock();
mScrimController.setExpansionAffectsAlpha(false);
mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
}
@@ -3904,7 +3971,7 @@
}
private void onClosingFinished() {
- mCentralSurfaces.onClosingFinished();
+ mOpenCloseListener.onClosingFinished();
setClosingWithAlphaFadeout(false);
mMediaHierarchyManager.closeGuts();
}
@@ -4393,7 +4460,7 @@
ipw.print("mPanelFlingOvershootAmount="); ipw.println(mPanelFlingOvershootAmount);
ipw.print("mLastGesturedOverExpansion="); ipw.println(mLastGesturedOverExpansion);
ipw.print("mIsSpringBackAnimation="); ipw.println(mIsSpringBackAnimation);
- ipw.print("mInSplitShade="); ipw.println(mInSplitShade);
+ ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled);
ipw.print("mHintDistance="); ipw.println(mHintDistance);
ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch);
ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown);
@@ -4463,11 +4530,13 @@
*/
public void initDependencies(
CentralSurfaces centralSurfaces,
+ GestureRecorder recorder,
Runnable hideExpandedRunnable,
NotificationShelfController notificationShelfController) {
// TODO(b/254859580): this can be injected.
mCentralSurfaces = centralSurfaces;
+ mGestureRecorder = recorder;
mHideExpandedRunnable = hideExpandedRunnable;
mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
mNotificationShelfController = notificationShelfController;
@@ -4549,20 +4618,20 @@
return false;
}
- // If the view that would receive the touch is disabled, just have status bar
- // eat the gesture.
- if (event.getAction() == MotionEvent.ACTION_DOWN && !mView.isEnabled()) {
- Log.v(TAG,
- String.format(
- "onTouchForwardedFromStatusBar: "
- + "panel view disabled, eating touch at (%d,%d)",
- (int) event.getX(),
- (int) event.getY()
- )
- );
- return true;
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ // If the view that would receive the touch is disabled, just have status
+ // bar eat the gesture.
+ if (!mView.isEnabled()) {
+ mShadeLog.logMotionEvent(event,
+ "onTouchForwardedFromStatusBar: panel view disabled");
+ return true;
+ }
+ if (isFullyCollapsed() && event.getY() < 1f) {
+ // b/235889526 Eat events on the top edge of the phone when collapsed
+ mShadeLog.logMotionEvent(event, "top edge touch ignored");
+ return true;
+ }
}
-
return mView.dispatchTouchEvent(event);
}
};
@@ -4762,7 +4831,6 @@
}
mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
- mCentralSurfaces.isFalsingThresholdNeeded(),
mCentralSurfaces.isWakeUpComingFromTouch());
// Log collapse gesture if on lock screen.
if (!expand && onKeyguard) {
@@ -4811,9 +4879,6 @@
*/
private boolean isFalseTouch(float x, float y,
@Classifier.InteractionType int interactionType) {
- if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
- return false;
- }
if (mFalsingManager.isClassifierEnabled()) {
return mFalsingManager.isFalseTouch(interactionType);
}
@@ -4911,7 +4976,7 @@
float maxPanelHeight = getMaxPanelTransitionDistance();
if (mHeightAnimator == null) {
// Split shade has its own overscroll logic
- if (mTracking && !mInSplitShade) {
+ if (mTracking && !mSplitShadeEnabled) {
float overExpansionPixels = Math.max(0, h - maxPanelHeight);
setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
}
@@ -5162,6 +5227,26 @@
return mView;
}
+ /** */
+ public boolean postToView(Runnable action) {
+ return mView.post(action);
+ }
+
+ /** */
+ public boolean sendInterceptTouchEventToView(MotionEvent event) {
+ return mView.onInterceptTouchEvent(event);
+ }
+
+ /** */
+ public void requestLayoutOnView() {
+ mView.requestLayout();
+ }
+
+ /** */
+ public void resetViewAlphas() {
+ ViewGroupFadeHelper.reset(mView);
+ }
+
private void beginJankMonitoring() {
if (mInteractionJankMonitor == null) {
return;
@@ -5459,14 +5544,23 @@
// - from SHADE to KEYGUARD
// - from SHADE_LOCKED to SHADE
// - getting notified again about the current SHADE or KEYGUARD state
+ if (mSplitShadeEnabled && oldState == SHADE && statusBarState == KEYGUARD) {
+ // user can go to keyguard from different shade states and closing animation
+ // may not fully run - we always want to make sure we close QS when that happens
+ // as we never need QS open in fresh keyguard state
+ closeQs();
+ }
final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
&& statusBarState == KEYGUARD
&& mScreenOffAnimationController.isKeyguardShowDelayed();
if (!animatingUnlockedShadeToKeyguard) {
// Only make the status bar visible if we're not animating the screen off, since
// we only want to be showing the clock/notifications during the animation.
- mShadeLog.v("Updating keyguard status bar state to "
- + (keyguardShowing ? "visible" : "invisible"));
+ if (keyguardShowing) {
+ mShadeLog.v("Updating keyguard status bar state to visible");
+ } else {
+ mShadeLog.v("Updating keyguard status bar state to invisible");
+ }
mKeyguardStatusBarViewController.updateViewState(
/* alpha= */ 1f,
keyguardShowing ? View.VISIBLE : View.INVISIBLE);
@@ -5691,7 +5785,7 @@
if (mSplitShadeEnabled && !isOnKeyguard()) {
setQsExpandImmediate(true);
}
- mCentralSurfaces.makeExpandedVisible(false);
+ mOpenCloseListener.onOpenStarted();
}
if (state == STATE_CLOSED) {
setQsExpandImmediate(false);
@@ -6174,4 +6268,17 @@
return super.performAccessibilityAction(host, action, args);
}
}
+
+ /** Listens for when touch tracking begins. */
+ interface TrackingStartedListener {
+ void onTrackingStarted();
+ }
+
+ /** Listens for when shade begins opening of finishes closing. */
+ interface OpenCloseListener {
+ /** Called when the shade finishes closing. */
+ void onClosingFinished();
+ /** Called when the shade starts opening. */
+ void onOpenStarted();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index b719177..8698c04 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -246,7 +246,6 @@
mLp.token = new Binder();
mLp.gravity = Gravity.TOP;
mLp.setFitInsetsTypes(0 /* types */);
- mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
mLp.setTitle("NotificationShade");
mLp.packageName = mContext.getPackageName();
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -384,8 +383,6 @@
mLpChanged.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
}
-
- mLpChanged.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
}
private void applyForceShowNavigationFlag(State state) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index e52170e..6acf417 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -16,6 +16,7 @@
package com.android.systemui.shade;
+import static android.os.Trace.TRACE_TAG_ALWAYS;
import static android.view.WindowInsets.Type.systemBars;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
@@ -23,6 +24,7 @@
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.LayoutRes;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -33,7 +35,9 @@
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Trace;
import android.util.AttributeSet;
+import android.util.Pair;
import android.view.ActionMode;
import android.view.DisplayCutout;
import android.view.InputQueue;
@@ -72,6 +76,7 @@
private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
private InteractionEventHandler mInteractionEventHandler;
+ private LayoutInsetsController mLayoutInsetProvider;
public NotificationShadeWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -106,12 +111,10 @@
mLeftInset = 0;
mRightInset = 0;
DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
- if (displayCutout != null) {
- mLeftInset = displayCutout.getSafeInsetLeft();
- mRightInset = displayCutout.getSafeInsetRight();
- }
- mLeftInset = Math.max(insets.left, mLeftInset);
- mRightInset = Math.max(insets.right, mRightInset);
+ Pair<Integer, Integer> pairInsets = mLayoutInsetProvider
+ .getinsets(windowInsets, displayCutout);
+ mLeftInset = pairInsets.first;
+ mRightInset = pairInsets.second;
applyMargins();
return windowInsets;
}
@@ -170,6 +173,10 @@
mInteractionEventHandler = listener;
}
+ protected void setLayoutInsetsController(LayoutInsetsController provider) {
+ mLayoutInsetProvider = provider;
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
@@ -299,6 +306,19 @@
return mode;
}
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("NotificationShadeWindowView#onMeasure");
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ Trace.endSection();
+ }
+
+ @Override
+ public void requestLayout() {
+ Trace.instant(TRACE_TAG_ALWAYS, "NotificationShadeWindowView#requestLayout");
+ super.requestLayout();
+ }
+
private class ActionModeCallback2Wrapper extends ActionMode.Callback2 {
private final ActionMode.Callback mWrapped;
@@ -338,6 +358,18 @@
}
}
+ /**
+ * Controller responsible for calculating insets for the shade window.
+ */
+ public interface LayoutInsetsController {
+
+ /**
+ * Update the insets and calculate them accordingly.
+ */
+ Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+ @Nullable DisplayCutout displayCutout);
+ }
+
interface InteractionEventHandler {
/**
* Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index bb67280c..d773c01 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -42,6 +42,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -76,6 +77,7 @@
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final AmbientState mAmbientState;
private final PulsingGestureListener mPulsingGestureListener;
+ private final NotificationInsetsController mNotificationInsetsController;
private GestureDetector mPulsingWakeupGestureHandler;
private View mBrightnessMirror;
@@ -111,6 +113,7 @@
CentralSurfaces centralSurfaces,
NotificationShadeWindowController controller,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
PulsingGestureListener pulsingGestureListener,
FeatureFlags featureFlags,
@@ -134,6 +137,7 @@
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mAmbientState = ambientState;
mPulsingGestureListener = pulsingGestureListener;
+ mNotificationInsetsController = notificationInsetsController;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -165,6 +169,7 @@
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
+ mView.setLayoutInsetsController(mNotificationInsetsController);
mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
@Override
public Boolean handleDispatchTouchEvent(MotionEvent ev) {
@@ -311,7 +316,7 @@
MotionEvent cancellation = MotionEvent.obtain(ev);
cancellation.setAction(MotionEvent.ACTION_CANCEL);
mStackScrollLayout.onInterceptTouchEvent(cancellation);
- mNotificationPanelViewController.getView().onInterceptTouchEvent(cancellation);
+ mNotificationPanelViewController.sendInterceptTouchEventToView(cancellation);
cancellation.recycle();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index aa610bd..a41a15d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -16,6 +16,9 @@
package com.android.systemui.shade;
+import android.view.MotionEvent;
+
+import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -29,31 +32,32 @@
*/
public interface ShadeController {
- /**
- * Make our window larger and the panel expanded
- */
- void instantExpandNotificationsPanel();
+ /** Make our window larger and the shade expanded */
+ void instantExpandShade();
- /** See {@link #animateCollapsePanels(int, boolean)}. */
- void animateCollapsePanels();
+ /** Collapse the shade instantly with no animation. */
+ void instantCollapseShade();
- /** See {@link #animateCollapsePanels(int, boolean)}. */
- void animateCollapsePanels(int flags);
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShade();
+
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShade(int flags);
+
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShadeForced();
+
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShadeDelayed();
/**
* Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
- * dismissing {@link CentralSurfaces} when on {@link StatusBarState#SHADE}.
+ * dismissing status bar when on {@link StatusBarState#SHADE}.
*/
- void animateCollapsePanels(int flags, boolean force);
-
- /** See {@link #animateCollapsePanels(int, boolean)}. */
- void animateCollapsePanels(int flags, boolean force, boolean delayed);
-
- /** See {@link #animateCollapsePanels(int, boolean)}. */
void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
/**
- * If the notifications panel is not fully expanded, collapse it animated.
+ * If the shade is not fully expanded, collapse it animated.
*
* @return Seems to always return false
*/
@@ -77,9 +81,7 @@
*/
void addPostCollapseAction(Runnable action);
- /**
- * Run all of the runnables added by {@link #addPostCollapseAction}.
- */
+ /** Run all of the runnables added by {@link #addPostCollapseAction}. */
void runPostCollapseRunnables();
/**
@@ -87,13 +89,51 @@
*
* @return true if the shade was open, else false
*/
- boolean collapsePanel();
+ boolean collapseShade();
/**
- * If animate is true, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
- * the panel. Post collapse runnables will be executed
+ * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
+ * the shade. Post collapse runnables will be executed
*
* @param animate true to animate the collapse, false for instantaneous collapse
*/
- void collapsePanel(boolean animate);
+ void collapseShade(boolean animate);
+
+ /** Makes shade expanded but not visible. */
+ void makeExpandedInvisible();
+
+ /** Makes shade expanded and visible. */
+ void makeExpandedVisible(boolean force);
+
+ /** Returns whether the shade is expanded and visible. */
+ boolean isExpandedVisible();
+
+ /** Handle status bar touch event. */
+ void onStatusBarTouch(MotionEvent event);
+
+ /** Called when the shade finishes collapsing. */
+ void onClosingFinished();
+
+ /** Sets the listener for when the visibility of the shade changes. */
+ void setVisibilityListener(ShadeVisibilityListener listener);
+
+ /** */
+ void setNotificationPresenter(NotificationPresenter presenter);
+
+ /** */
+ void setNotificationShadeWindowViewController(
+ NotificationShadeWindowViewController notificationShadeWindowViewController);
+
+ /** */
+ void setNotificationPanelViewController(
+ NotificationPanelViewController notificationPanelViewController);
+
+ /** Listens for shade visibility changes. */
+ interface ShadeVisibilityListener {
+ /** Called when the visibility of the shade changes. */
+ void visibilityChanged(boolean visible);
+
+ /** Called when shade expanded and visible state changed. */
+ void expandedVisibleChanged(boolean expandedVisible);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index eaf7fae..638e748 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -16,9 +16,12 @@
package com.android.systemui.shade;
+import android.content.ComponentCallbacks2;
import android.util.Log;
+import android.view.MotionEvent;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
@@ -27,11 +30,12 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowController;
import java.util.ArrayList;
-import java.util.Optional;
import javax.inject.Inject;
@@ -39,68 +43,81 @@
/** An implementation of {@link ShadeController}. */
@SysUISingleton
-public class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl implements ShadeController {
private static final String TAG = "ShadeControllerImpl";
private static final boolean SPEW = false;
- private final CommandQueue mCommandQueue;
- private final StatusBarStateController mStatusBarStateController;
- protected final NotificationShadeWindowController mNotificationShadeWindowController;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final int mDisplayId;
- protected final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+
+ private final CommandQueue mCommandQueue;
+ private final KeyguardStateController mKeyguardStateController;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final StatusBarStateController mStatusBarStateController;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final StatusBarWindowController mStatusBarWindowController;
+
private final Lazy<AssistManager> mAssistManagerLazy;
+ private final Lazy<NotificationGutsManager> mGutsManager;
private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+ private boolean mExpandedVisible;
+
+ private NotificationPanelViewController mNotificationPanelViewController;
+ private NotificationPresenter mPresenter;
+ private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ private ShadeVisibilityListener mShadeVisibilityListener;
+
@Inject
public ShadeControllerImpl(
CommandQueue commandQueue,
+ KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
- NotificationShadeWindowController notificationShadeWindowController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ StatusBarWindowController statusBarWindowController,
+ NotificationShadeWindowController notificationShadeWindowController,
WindowManager windowManager,
- Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
- Lazy<AssistManager> assistManagerLazy
+ Lazy<AssistManager> assistManagerLazy,
+ Lazy<NotificationGutsManager> gutsManager
) {
mCommandQueue = commandQueue;
mStatusBarStateController = statusBarStateController;
+ mStatusBarWindowController = statusBarWindowController;
+ mGutsManager = gutsManager;
mNotificationShadeWindowController = notificationShadeWindowController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
- // TODO: Remove circular reference to CentralSurfaces when possible.
- mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+ mKeyguardStateController = keyguardStateController;
mAssistManagerLazy = assistManagerLazy;
}
@Override
- public void instantExpandNotificationsPanel() {
+ public void instantExpandShade() {
// Make our window larger and the panel expanded.
- getCentralSurfaces().makeExpandedVisible(true /* force */);
- getNotificationPanelViewController().expand(false /* animate */);
+ makeExpandedVisible(true /* force */);
+ mNotificationPanelViewController.expand(false /* animate */);
mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
}
@Override
- public void animateCollapsePanels() {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ public void animateCollapseShade() {
+ animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
}
@Override
- public void animateCollapsePanels(int flags) {
- animateCollapsePanels(flags, false /* force */, false /* delayed */,
- 1.0f /* speedUpFactor */);
+ public void animateCollapseShade(int flags) {
+ animateCollapsePanels(flags, false, false, 1.0f);
}
@Override
- public void animateCollapsePanels(int flags, boolean force) {
- animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
+ public void animateCollapseShadeForced() {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
}
@Override
- public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
- animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
+ public void animateCollapseShadeDelayed() {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
}
@Override
@@ -111,34 +128,26 @@
return;
}
if (SPEW) {
- Log.d(TAG, "animateCollapse():"
- + " mExpandedVisible=" + getCentralSurfaces().isExpandedVisible()
- + " flags=" + flags);
+ Log.d(TAG,
+ "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
}
-
- // TODO(b/62444020): remove when this bug is fixed
- Log.v(TAG, "NotificationShadeWindow: " + getNotificationShadeWindowView()
- + " canPanelBeCollapsed(): "
- + getNotificationPanelViewController().canPanelBeCollapsed());
if (getNotificationShadeWindowView() != null
- && getNotificationPanelViewController().canPanelBeCollapsed()
+ && mNotificationPanelViewController.canPanelBeCollapsed()
&& (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
// release focus immediately to kick off focus change transition
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
- getCentralSurfaces().getNotificationShadeWindowViewController().cancelExpandHelper();
- getNotificationPanelViewController()
- .collapsePanel(true /* animate */, delayed, speedUpFactor);
+ mNotificationShadeWindowViewController.cancelExpandHelper();
+ mNotificationPanelViewController.collapsePanel(true, delayed, speedUpFactor);
}
}
-
@Override
public boolean closeShadeIfOpen() {
- if (!getNotificationPanelViewController().isFullyCollapsed()) {
+ if (!mNotificationPanelViewController.isFullyCollapsed()) {
mCommandQueue.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
- getCentralSurfaces().visibilityChanged(false);
+ notifyVisibilityChanged(false);
mAssistManagerLazy.get().hideAssist();
}
return false;
@@ -146,21 +155,19 @@
@Override
public boolean isShadeOpen() {
- NotificationPanelViewController controller =
- getNotificationPanelViewController();
- return controller.isExpanding() || controller.isFullyExpanded();
+ return mNotificationPanelViewController.isExpanding()
+ || mNotificationPanelViewController.isFullyExpanded();
}
@Override
public void postOnShadeExpanded(Runnable executable) {
- getNotificationPanelViewController().addOnGlobalLayoutListener(
+ mNotificationPanelViewController.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
- if (getCentralSurfaces().getNotificationShadeWindowView()
- .isVisibleToUser()) {
- getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
- getNotificationPanelViewController().getView().post(executable);
+ if (getNotificationShadeWindowView().isVisibleToUser()) {
+ mNotificationPanelViewController.removeOnGlobalLayoutListener(this);
+ mNotificationPanelViewController.postToView(executable);
}
}
});
@@ -183,12 +190,11 @@
}
@Override
- public boolean collapsePanel() {
- if (!getNotificationPanelViewController().isFullyCollapsed()) {
+ public boolean collapseShade() {
+ if (!mNotificationPanelViewController.isFullyCollapsed()) {
// close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed */);
- getCentralSurfaces().visibilityChanged(false);
+ animateCollapseShadeDelayed();
+ notifyVisibilityChanged(false);
return true;
} else {
@@ -197,33 +203,154 @@
}
@Override
- public void collapsePanel(boolean animate) {
+ public void collapseShade(boolean animate) {
if (animate) {
- boolean willCollapse = collapsePanel();
+ boolean willCollapse = collapseShade();
if (!willCollapse) {
runPostCollapseRunnables();
}
- } else if (!getPresenter().isPresenterFullyCollapsed()) {
- getCentralSurfaces().instantCollapseNotificationPanel();
- getCentralSurfaces().visibilityChanged(false);
+ } else if (!mPresenter.isPresenterFullyCollapsed()) {
+ instantCollapseShade();
+ notifyVisibilityChanged(false);
} else {
runPostCollapseRunnables();
}
}
- private CentralSurfaces getCentralSurfaces() {
- return mCentralSurfacesOptionalLazy.get().get();
+ @Override
+ public void onStatusBarTouch(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (mExpandedVisible) {
+ animateCollapseShade();
+ }
+ }
}
- private NotificationPresenter getPresenter() {
- return getCentralSurfaces().getPresenter();
+ @Override
+ public void onClosingFinished() {
+ runPostCollapseRunnables();
+ if (!mPresenter.isPresenterFullyCollapsed()) {
+ // if we set it not to be focusable when collapsing, we have to undo it when we aborted
+ // the closing
+ mNotificationShadeWindowController.setNotificationShadeFocusable(true);
+ }
}
- protected NotificationShadeWindowView getNotificationShadeWindowView() {
- return getCentralSurfaces().getNotificationShadeWindowView();
+ @Override
+ public void instantCollapseShade() {
+ mNotificationPanelViewController.instantCollapse();
+ runPostCollapseRunnables();
}
- private NotificationPanelViewController getNotificationPanelViewController() {
- return getCentralSurfaces().getNotificationPanelViewController();
+ @Override
+ public void makeExpandedVisible(boolean force) {
+ if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
+ if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+ return;
+ }
+
+ mExpandedVisible = true;
+
+ // Expand the window to encompass the full screen in anticipation of the drag.
+ // It's only possible to do atomically because the status bar is at the top of the screen!
+ mNotificationShadeWindowController.setPanelVisible(true);
+
+ notifyVisibilityChanged(true);
+ mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+ notifyExpandedVisibleChanged(true);
+ }
+
+ @Override
+ public void makeExpandedInvisible() {
+ if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
+
+ if (!mExpandedVisible || getNotificationShadeWindowView() == null) {
+ return;
+ }
+
+ // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
+ mNotificationPanelViewController.collapsePanel(false, false, 1.0f);
+
+ mNotificationPanelViewController.closeQs();
+
+ mExpandedVisible = false;
+ notifyVisibilityChanged(false);
+
+ // Update the visibility of notification shade and status bar window.
+ mNotificationShadeWindowController.setPanelVisible(false);
+ mStatusBarWindowController.setForceStatusBarVisible(false);
+
+ // Close any guts that might be visible
+ mGutsManager.get().closeAndSaveGuts(
+ true /* removeLeavebehind */,
+ true /* force */,
+ true /* removeControls */,
+ -1 /* x */,
+ -1 /* y */,
+ true /* resetMenu */);
+
+ runPostCollapseRunnables();
+ notifyExpandedVisibleChanged(false);
+ mCommandQueue.recomputeDisableFlags(
+ mDisplayId,
+ mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
+
+ // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
+ // the bouncer appear animation.
+ if (!mKeyguardStateController.isShowing()) {
+ WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+ }
+ }
+
+ @Override
+ public boolean isExpandedVisible() {
+ return mExpandedVisible;
+ }
+
+ @Override
+ public void setVisibilityListener(ShadeVisibilityListener listener) {
+ mShadeVisibilityListener = listener;
+ }
+
+ private void notifyVisibilityChanged(boolean visible) {
+ mShadeVisibilityListener.visibilityChanged(visible);
+ }
+
+ private void notifyExpandedVisibleChanged(boolean expandedVisible) {
+ mShadeVisibilityListener.expandedVisibleChanged(expandedVisible);
+ }
+
+ @Override
+ public void setNotificationPresenter(NotificationPresenter presenter) {
+ mPresenter = presenter;
+ }
+
+ @Override
+ public void setNotificationShadeWindowViewController(
+ NotificationShadeWindowViewController controller) {
+ mNotificationShadeWindowViewController = controller;
+ }
+
+ private NotificationShadeWindowView getNotificationShadeWindowView() {
+ return mNotificationShadeWindowViewController.getView();
+ }
+
+ @Override
+ public void setNotificationPanelViewController(
+ NotificationPanelViewController notificationPanelViewController) {
+ mNotificationPanelViewController = notificationPanelViewController;
+ mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables);
+ mNotificationPanelViewController.setOpenCloseListener(
+ new NotificationPanelViewController.OpenCloseListener() {
+ @Override
+ public void onClosingFinished() {
+ ShadeControllerImpl.this.onClosingFinished();
+ }
+
+ @Override
+ public void onOpenStarted() {
+ makeExpandedVisible(false);
+ }
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 1054aa5..07820ec 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -78,7 +78,7 @@
})
shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
- dumpManager.registerDumpable("ShadeTransitionController") { printWriter, _ ->
+ dumpManager.registerCriticalDumpable("ShadeTransitionController") { printWriter, _ ->
dump(printWriter)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
index fde08ee..f95125f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
@@ -70,7 +70,7 @@
updateResources()
}
})
- dumpManager.registerDumpable("SplitShadeOverScroller") { printWriter, _ ->
+ dumpManager.registerCriticalDumpable("SplitShadeOverScroller") { printWriter, _ ->
dump(printWriter)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index e6d7e41..426d4fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -165,6 +165,7 @@
private static final int MSG_REGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 66 << MSG_SHIFT;
private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT;
private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT;
+ private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -474,6 +475,11 @@
*/
default void unregisterNearbyMediaDevicesProvider(
@NonNull INearbyMediaDevicesProvider provider) {}
+
+ /**
+ * @see IStatusBar#showRearDisplayDialog
+ */
+ default void showRearDisplayDialog(int currentBaseState) {}
}
public CommandQueue(Context context) {
@@ -1229,6 +1235,13 @@
}
@Override
+ public void showRearDisplayDialog(int currentBaseState) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_SHOW_REAR_DISPLAY_DIALOG, currentBaseState).sendToTarget();
+ }
+ }
+
+ @Override
public void requestAddTile(
@NonNull ComponentName componentName,
@NonNull CharSequence appName,
@@ -1724,6 +1737,10 @@
mCallbacks.get(i).requestTileServiceListeningState(component);
}
break;
+ case MSG_SHOW_REAR_DISPLAY_DIALOG:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).showRearDisplayDialog((Integer) msg.obj);
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 0deb47d..0f27420 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -36,7 +36,7 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
@@ -55,7 +55,6 @@
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.BatteryManager;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -64,11 +63,11 @@
import android.os.UserManager;
import android.text.TextUtils;
import android.text.format.Formatter;
-import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -76,6 +75,8 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TrustGrantFlags;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
@@ -123,7 +124,6 @@
private static final String TAG = "KeyguardIndication";
private static final boolean DEBUG_CHARGING_SPEED = false;
- private static final boolean DEBUG = Build.IS_DEBUGGABLE;
private static final int MSG_HIDE_TRANSIENT = 1;
private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
@@ -139,6 +139,7 @@
protected final StatusBarStateController mStatusBarStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final AuthController mAuthController;
+ private final KeyguardLogger mKeyguardLogger;
private ViewGroup mIndicationArea;
private KeyguardIndicationTextView mTopIndicationView;
private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -155,11 +156,12 @@
private final AccessibilityManager mAccessibilityManager;
private final Handler mHandler;
- protected KeyguardIndicationRotateTextViewController mRotateTextViewController;
+ @VisibleForTesting
+ public KeyguardIndicationRotateTextViewController mRotateTextViewController;
private BroadcastReceiver mBroadcastReceiver;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private String mRestingIndication;
+ private String mPersistentUnlockMessage;
private String mAlignmentIndication;
private CharSequence mTrustGrantedIndication;
private CharSequence mTransientIndication;
@@ -196,7 +198,9 @@
public void onScreenTurnedOn() {
mHandler.removeMessages(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON);
if (mBiometricErrorMessageToShowOnScreenOn != null) {
- showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn);
+ String followUpMessage = mFaceLockedOutThisAuthSession
+ ? faceLockedOutFollowupMessage() : null;
+ showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn, followUpMessage);
// We want to keep this message around in case the screen was off
hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS);
mBiometricErrorMessageToShowOnScreenOn = null;
@@ -229,7 +233,8 @@
ScreenLifecycle screenLifecycle,
KeyguardBypassController keyguardBypassController,
AccessibilityManager accessibilityManager,
- FaceHelpMessageDeferral faceHelpMessageDeferral) {
+ FaceHelpMessageDeferral faceHelpMessageDeferral,
+ KeyguardLogger keyguardLogger) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mDevicePolicyManager = devicePolicyManager;
@@ -249,6 +254,7 @@
mKeyguardBypassController = keyguardBypassController;
mAccessibilityManager = accessibilityManager;
mScreenLifecycle = screenLifecycle;
+ mKeyguardLogger = keyguardLogger;
mScreenLifecycle.addObserver(mScreenObserver);
mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
@@ -373,7 +379,7 @@
updateLockScreenTrustMsg(userId, getTrustGrantedIndication(), getTrustManagedIndication());
updateLockScreenAlignmentMsg();
updateLockScreenLogoutView();
- updateLockScreenRestingMsg();
+ updateLockScreenPersistentUnlockMsg();
}
private void updateOrganizedOwnedDevice() {
@@ -479,7 +485,8 @@
}
private void updateLockScreenUserLockedMsg(int userId) {
- if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
+ if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)
+ || mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_USER_LOCKED,
new KeyguardIndication.Builder()
@@ -584,18 +591,17 @@
}
}
- private void updateLockScreenRestingMsg() {
- if (!TextUtils.isEmpty(mRestingIndication)
- && !mRotateTextViewController.hasIndications()) {
+ private void updateLockScreenPersistentUnlockMsg() {
+ if (!TextUtils.isEmpty(mPersistentUnlockMessage)) {
mRotateTextViewController.updateIndication(
- INDICATION_TYPE_RESTING,
+ INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
new KeyguardIndication.Builder()
- .setMessage(mRestingIndication)
+ .setMessage(mPersistentUnlockMessage)
.setTextColor(mInitialTextColorState)
.build(),
- false);
+ true);
} else {
- mRotateTextViewController.hideIndication(INDICATION_TYPE_RESTING);
+ mRotateTextViewController.hideIndication(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE);
}
}
@@ -678,11 +684,8 @@
}
}
- /**
- * Sets the indication that is shown if nothing else is showing.
- */
- public void setRestingIndication(String restingIndication) {
- mRestingIndication = restingIndication;
+ private void setPersistentUnlockMessage(String persistentUnlockMessage) {
+ mPersistentUnlockMessage = persistentUnlockMessage;
updateDeviceEntryIndication(false);
}
@@ -1024,7 +1027,7 @@
mChargingTimeRemaining = mPowerPluggedIn
? mBatteryInfo.computeChargeTimeRemaining() : -1;
} catch (RemoteException e) {
- Log.e(TAG, "Error calling IBatteryStats: ", e);
+ mKeyguardLogger.logException(e, "Error calling IBatteryStats");
mChargingTimeRemaining = -1;
}
updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
@@ -1072,8 +1075,10 @@
final boolean isCoExFaceAcquisitionMessage =
faceAuthSoftError && isUnlockWithFingerprintPossible;
if (isCoExFaceAcquisitionMessage && !mCoExFaceAcquisitionMsgIdsToShow.contains(msgId)) {
- debugLog("skip showing msgId=" + msgId + " helpString=" + helpString
- + ", due to co-ex logic");
+ mKeyguardLogger.logBiometricMessage(
+ "skipped showing help message due to co-ex logic",
+ msgId,
+ helpString);
} else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.setKeyguardMessage(helpString,
mInitialTextColorState);
@@ -1114,6 +1119,9 @@
public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) {
if (biometricSourceType == FACE && !mKeyguardUpdateMonitor.isFaceLockedOut()) {
mFaceLockedOutThisAuthSession = false;
+ } else if (biometricSourceType == FINGERPRINT) {
+ setPersistentUnlockMessage(mKeyguardUpdateMonitor.isFingerprintLockedOut()
+ ? mContext.getString(R.string.keyguard_unlock) : "");
}
}
@@ -1131,7 +1139,7 @@
CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
mFaceAcquiredMessageDeferral.reset();
if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) {
- debugLog("suppressingFaceError msgId=" + msgId + " errString= " + errString);
+ mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
return;
}
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -1145,8 +1153,9 @@
private void onFingerprintAuthError(int msgId, String errString) {
if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) {
- debugLog("suppressingFingerprintError msgId=" + msgId
- + " errString= " + errString);
+ mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
+ msgId,
+ errString);
} else {
showErrorMessageNowOrLater(errString, null);
}
@@ -1179,16 +1188,14 @@
@Override
public void onTrustChanged(int userId) {
- if (getCurrentUser() != userId) {
- return;
- }
+ if (!isCurrentUser(userId)) return;
updateDeviceEntryIndication(false);
}
@Override
- public void showTrustGrantedMessage(CharSequence message) {
- mTrustGrantedIndication = message;
- updateDeviceEntryIndication(false);
+ public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+ @NonNull TrustGrantFlags flags, @Nullable String message) {
+ showTrustGrantedMessage(dismissKeyguard, message);
}
@Override
@@ -1248,10 +1255,17 @@
}
}
+ private boolean isCurrentUser(int userId) {
+ return getCurrentUser() == userId;
+ }
+
+ protected void showTrustGrantedMessage(boolean dismissKeyguard, @Nullable String message) {
+ mTrustGrantedIndication = message;
+ updateDeviceEntryIndication(false);
+ }
+
private void handleFaceLockoutError(String errString) {
- int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
- : R.string.keyguard_unlock;
- String followupMessage = mContext.getString(followupMsgId);
+ String followupMessage = faceLockedOutFollowupMessage();
// Lockout error can happen multiple times in a session because we trigger face auth
// even when it is locked out so that the user is aware that face unlock would have
// triggered but didn't because it is locked out.
@@ -1269,13 +1283,20 @@
}
}
+ private String faceLockedOutFollowupMessage() {
+ int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
+ : R.string.keyguard_unlock;
+ return mContext.getString(followupMsgId);
+ }
+
private static boolean isLockoutError(int msgId) {
return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
|| msgId == FaceManager.FACE_ERROR_LOCKOUT;
}
private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
- debugLog("showDeferredFaceMessage msgId=" + deferredFaceMessage);
+ mKeyguardLogger.logBiometricMessage("deferred message after face auth timeout",
+ null, String.valueOf(deferredFaceMessage));
if (canUnlockWithFingerprint()) {
// Co-ex: show deferred message OR nothing
// if we're on the lock screen (bouncer isn't showing), show the deferred msg
@@ -1287,7 +1308,8 @@
);
} else {
// otherwise, don't show any message
- debugLog("skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
+ mKeyguardLogger.logBiometricMessage(
+ "skip showing FACE_ERROR_TIMEOUT due to co-ex logic");
}
} else if (deferredFaceMessage != null) {
// Face-only: The face timeout message is not very actionable, let's ask the
@@ -1308,12 +1330,6 @@
KeyguardUpdateMonitor.getCurrentUser());
}
- private void debugLog(String logMsg) {
- if (DEBUG) {
- Log.d(TAG, logMsg);
- }
- }
-
private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.setKeyguardMessage(errString, mInitialTextColorState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 9d2750f..2334a4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -15,9 +15,12 @@
import android.util.AttributeSet
import android.util.MathUtils.lerp
import android.view.View
+import android.view.animation.PathInterpolator
import com.android.systemui.animation.Interpolators
import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
import com.android.systemui.util.getColorWithAlpha
+import com.android.systemui.util.leak.RotationUtils
+import com.android.systemui.util.leak.RotationUtils.Rotation
import java.util.function.Consumer
/**
@@ -67,42 +70,46 @@
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
val interpolatedAmount = INTERPOLATOR.getInterpolation(amount)
val ovalWidthIncreaseAmount =
- getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD)
+ getPercentPastThreshold(interpolatedAmount, WIDEN_OVAL_THRESHOLD)
val initialWidthMultiplier = (1f - OVAL_INITIAL_WIDTH_PERCENT) / 2f
with(scrim) {
- revealGradientEndColorAlpha = 1f - getPercentPastThreshold(
- amount, FADE_END_COLOR_OUT_THRESHOLD)
+ revealGradientEndColorAlpha =
+ 1f - getPercentPastThreshold(amount, FADE_END_COLOR_OUT_THRESHOLD)
setRevealGradientBounds(
- scrim.width * initialWidthMultiplier +
- -scrim.width * ovalWidthIncreaseAmount,
- scrim.height * OVAL_INITIAL_TOP_PERCENT -
- scrim.height * interpolatedAmount,
- scrim.width * (1f - initialWidthMultiplier) +
- scrim.width * ovalWidthIncreaseAmount,
- scrim.height * OVAL_INITIAL_BOTTOM_PERCENT +
- scrim.height * interpolatedAmount)
+ scrim.width * initialWidthMultiplier + -scrim.width * ovalWidthIncreaseAmount,
+ scrim.height * OVAL_INITIAL_TOP_PERCENT - scrim.height * interpolatedAmount,
+ scrim.width * (1f - initialWidthMultiplier) + scrim.width * ovalWidthIncreaseAmount,
+ scrim.height * OVAL_INITIAL_BOTTOM_PERCENT + scrim.height * interpolatedAmount
+ )
}
}
}
class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect {
- private val INTERPOLATOR = Interpolators.FAST_OUT_SLOW_IN_REVERSE
+ // Interpolator that reveals >80% of the content at 0.5 progress, makes revealing faster
+ private val interpolator = PathInterpolator(/* controlX1= */ 0.4f, /* controlY1= */ 0f,
+ /* controlX2= */ 0.2f, /* controlY2= */ 1f)
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
- val interpolatedAmount = INTERPOLATOR.getInterpolation(amount)
+ val interpolatedAmount = interpolator.getInterpolation(amount)
scrim.interpolatedRevealAmount = interpolatedAmount
scrim.startColorAlpha =
- getPercentPastThreshold(1 - interpolatedAmount,
- threshold = 1 - START_COLOR_REVEAL_PERCENTAGE)
+ getPercentPastThreshold(
+ 1 - interpolatedAmount,
+ threshold = 1 - START_COLOR_REVEAL_PERCENTAGE
+ )
scrim.revealGradientEndColorAlpha =
- 1f - getPercentPastThreshold(interpolatedAmount,
- threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE)
+ 1f -
+ getPercentPastThreshold(
+ interpolatedAmount,
+ threshold = REVEAL_GRADIENT_END_COLOR_ALPHA_START_PERCENTAGE
+ )
// Start changing gradient bounds later to avoid harsh gradient in the beginning
val gradientBoundsAmount = lerp(GRADIENT_START_BOUNDS_PERCENTAGE, 1.0f, interpolatedAmount)
@@ -179,7 +186,7 @@
*/
private val OFF_SCREEN_START_AMOUNT = 0.05f
- private val WIDTH_INCREASE_MULTIPLIER = 1.25f
+ private val INCREASE_MULTIPLIER = 1.25f
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
val interpolatedAmount = Interpolators.FAST_OUT_SLOW_IN_REVERSE.getInterpolation(amount)
@@ -188,15 +195,36 @@
with(scrim) {
revealGradientEndColorAlpha = 1f - fadeAmount
interpolatedRevealAmount = interpolatedAmount
- setRevealGradientBounds(
+ @Rotation val rotation = RotationUtils.getRotation(scrim.getContext())
+ if (rotation == RotationUtils.ROTATION_NONE) {
+ setRevealGradientBounds(
width * (1f + OFF_SCREEN_START_AMOUNT) -
- width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount,
- powerButtonY -
- height * interpolatedAmount,
+ width * INCREASE_MULTIPLIER * interpolatedAmount,
+ powerButtonY - height * interpolatedAmount,
width * (1f + OFF_SCREEN_START_AMOUNT) +
- width * WIDTH_INCREASE_MULTIPLIER * interpolatedAmount,
- powerButtonY +
- height * interpolatedAmount)
+ width * INCREASE_MULTIPLIER * interpolatedAmount,
+ powerButtonY + height * interpolatedAmount
+ )
+ } else if (rotation == RotationUtils.ROTATION_LANDSCAPE) {
+ setRevealGradientBounds(
+ powerButtonY - width * interpolatedAmount,
+ (-height * OFF_SCREEN_START_AMOUNT) -
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
+ powerButtonY + width * interpolatedAmount,
+ (-height * OFF_SCREEN_START_AMOUNT) +
+ height * INCREASE_MULTIPLIER * interpolatedAmount
+ )
+ } else {
+ // RotationUtils.ROTATION_SEASCAPE
+ setRevealGradientBounds(
+ (width - powerButtonY) - width * interpolatedAmount,
+ height * (1f + OFF_SCREEN_START_AMOUNT) -
+ height * INCREASE_MULTIPLIER * interpolatedAmount,
+ (width - powerButtonY) + width * interpolatedAmount,
+ height * (1f + OFF_SCREEN_START_AMOUNT) +
+ height * INCREASE_MULTIPLIER * interpolatedAmount
+ )
+ }
}
}
}
@@ -208,9 +236,7 @@
*/
class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- /**
- * Listener that is called if the scrim's opaqueness changes
- */
+ /** Listener that is called if the scrim's opaqueness changes */
lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
/**
@@ -224,8 +250,11 @@
revealEffect.setRevealAmountOnScrim(value, this)
updateScrimOpaque()
- Trace.traceCounter(Trace.TRACE_TAG_APP, "light_reveal_amount",
- (field * 100).toInt())
+ Trace.traceCounter(
+ Trace.TRACE_TAG_APP,
+ "light_reveal_amount",
+ (field * 100).toInt()
+ )
invalidate()
}
}
@@ -250,10 +279,10 @@
/**
* Alpha of the fill that can be used in the beginning of the animation to hide the content.
- * Normally the gradient bounds are animated from small size so the content is not visible,
- * but if the start gradient bounds allow to see some content this could be used to make the
- * reveal smoother. It can help to add fade in effect in the beginning of the animation.
- * The color of the fill is determined by [revealGradientEndColor].
+ * Normally the gradient bounds are animated from small size so the content is not visible, but
+ * if the start gradient bounds allow to see some content this could be used to make the reveal
+ * smoother. It can help to add fade in effect in the beginning of the animation. The color of
+ * the fill is determined by [revealGradientEndColor].
*
* 0 - no fill and content is visible, 1 - the content is covered with the start color
*/
@@ -281,9 +310,7 @@
}
}
- /**
- * Is the scrim currently fully opaque
- */
+ /** Is the scrim currently fully opaque */
var isScrimOpaque = false
private set(value) {
if (field != value) {
@@ -318,16 +345,22 @@
* Paint used to draw a transparent-to-white radial gradient. This will be scaled and translated
* via local matrix in [onDraw] so we never need to construct a new shader.
*/
- private val gradientPaint = Paint().apply {
- shader = RadialGradient(
- 0f, 0f, 1f,
- intArrayOf(Color.TRANSPARENT, Color.WHITE), floatArrayOf(0f, 1f),
- Shader.TileMode.CLAMP)
+ private val gradientPaint =
+ Paint().apply {
+ shader =
+ RadialGradient(
+ 0f,
+ 0f,
+ 1f,
+ intArrayOf(Color.TRANSPARENT, Color.WHITE),
+ floatArrayOf(0f, 1f),
+ Shader.TileMode.CLAMP
+ )
- // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
- // window, rather than outright replacing them.
- xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
- }
+ // SRC_OVER ensures that we draw the semitransparent pixels over other views in the same
+ // window, rather than outright replacing them.
+ xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)
+ }
/**
* Matrix applied to [gradientPaint]'s RadialGradient shader to move the gradient to
@@ -347,8 +380,8 @@
* simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
* [revealGradientHeight] for you.
*
- * This method does not call [invalidate] - you should do so once you're done changing
- * properties.
+ * This method does not call [invalidate]
+ * - you should do so once you're done changing properties.
*/
fun setRevealGradientBounds(left: Float, top: Float, right: Float, bottom: Float) {
revealGradientWidth = right - left
@@ -359,8 +392,12 @@
}
override fun onDraw(canvas: Canvas?) {
- if (canvas == null || revealGradientWidth <= 0 || revealGradientHeight <= 0 ||
- revealAmount == 0f) {
+ if (
+ canvas == null ||
+ revealGradientWidth <= 0 ||
+ revealGradientHeight <= 0 ||
+ revealAmount == 0f
+ ) {
if (revealAmount < 1f) {
canvas?.drawColor(revealGradientEndColor)
}
@@ -383,8 +420,10 @@
}
private fun setPaintColorFilter() {
- gradientPaint.colorFilter = PorterDuffColorFilter(
- getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
- PorterDuff.Mode.MULTIPLY)
+ gradientPaint.colorFilter =
+ PorterDuffColorFilter(
+ getColorWithAlpha(revealGradientEndColor, revealGradientEndColorAlpha),
+ PorterDuff.Mode.MULTIPLY
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
new file mode 100644
index 0000000..39d7d66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import com.android.systemui.shade.NotificationShadeWindowView;
+
+/**
+ * Calculates insets for the notification shade window view.
+ */
+public abstract class NotificationInsetsController
+ implements NotificationShadeWindowView.LayoutInsetsController {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
new file mode 100644
index 0000000..1ed704e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static android.view.WindowInsets.Type.systemBars;
+
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.util.Pair;
+import android.view.DisplayCutout;
+import android.view.WindowInsets;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Default implementation of NotificationsInsetsController.
+ */
+@SysUISingleton
+public class NotificationInsetsImpl extends NotificationInsetsController {
+
+ @Inject
+ public NotificationInsetsImpl() {
+
+ }
+
+ @Override
+ public Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+ @Nullable DisplayCutout displayCutout) {
+ final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
+ int leftInset = 0;
+ int rightInset = 0;
+
+ if (displayCutout != null) {
+ leftInset = displayCutout.getSafeInsetLeft();
+ rightInset = displayCutout.getSafeInsetRight();
+ }
+ leftInset = Math.max(insets.left, leftInset);
+ rightInset = Math.max(insets.right, rightInset);
+
+ return new Pair(leftInset, rightInset);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
new file mode 100644
index 0000000..614bc0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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.dagger.SysUISingleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+@Module
+public interface NotificationInsetsModule {
+
+ @Binds
+ @SysUISingleton
+ NotificationInsetsController bindNotificationInsetsController(NotificationInsetsImpl impl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 824d3a3..56b689e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -31,7 +31,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 184dc25..f4cd985 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,7 +19,6 @@
import static com.android.systemui.DejankUtils.whitelistIpcs;
-import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.admin.DevicePolicyManager;
@@ -31,6 +30,7 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -38,6 +38,7 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.statusbar.NotificationVisibility;
@@ -50,6 +51,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -93,6 +95,7 @@
private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray();
private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray();
private final UserManager mUserManager;
+ private final UserTracker mUserTracker;
private final List<UserChangedListener> mListeners = new ArrayList<>();
private final BroadcastDispatcher mBroadcastDispatcher;
private final NotificationClickNotifier mClickNotifier;
@@ -126,21 +129,6 @@
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
- case Intent.ACTION_USER_SWITCHED:
- mCurrentUserId = intent.getIntExtra(
- Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL);
- updateCurrentProfilesCache();
-
- Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
-
- updateLockscreenNotificationSetting();
- updatePublicMode();
- mPresenter.onUserSwitched(mCurrentUserId);
-
- for (UserChangedListener listener : mListeners) {
- listener.onUserChanged(mCurrentUserId);
- }
- break;
case Intent.ACTION_USER_REMOVED:
int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (removedUserId != -1) {
@@ -180,6 +168,25 @@
}
};
+ protected final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mCurrentUserId = newUser;
+ updateCurrentProfilesCache();
+
+ Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+
+ updateLockscreenNotificationSetting();
+ updatePublicMode();
+ mPresenter.onUserSwitched(mCurrentUserId);
+
+ for (UserChangedListener listener : mListeners) {
+ listener.onUserChanged(mCurrentUserId);
+ }
+ }
+ };
+
protected final Context mContext;
private final Handler mMainHandler;
protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
@@ -195,6 +202,7 @@
BroadcastDispatcher broadcastDispatcher,
DevicePolicyManager devicePolicyManager,
UserManager userManager,
+ UserTracker userTracker,
Lazy<NotificationVisibilityProvider> visibilityProviderLazy,
Lazy<CommonNotifCollection> commonNotifCollectionLazy,
NotificationClickNotifier clickNotifier,
@@ -210,7 +218,8 @@
mMainHandler = mainHandler;
mDevicePolicyManager = devicePolicyManager;
mUserManager = userManager;
- mCurrentUserId = ActivityManager.getCurrentUser();
+ mUserTracker = userTracker;
+ mCurrentUserId = mUserTracker.getUserId();
mVisibilityProviderLazy = visibilityProviderLazy;
mCommonNotifCollectionLazy = commonNotifCollectionLazy;
mClickNotifier = clickNotifier;
@@ -281,7 +290,6 @@
null /* handler */, UserHandle.ALL);
IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
@@ -295,7 +303,9 @@
mContext.registerReceiver(mBaseBroadcastReceiver, internalFilter, PERMISSION_SELF, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
- mCurrentUserId = ActivityManager.getCurrentUser(); // in case we reg'd receiver too late
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+
+ mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
updateCurrentProfilesCache();
mSettingsObserver.onChange(false); // set up
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index b5879ec..8dc7842 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -304,7 +304,7 @@
}
init {
- dumpManager.registerDumpable(javaClass.name, this)
+ dumpManager.registerCriticalDumpable(javaClass.name, this)
if (WAKE_UP_ANIMATION_ENABLED) {
keyguardStateController.addCallback(keyguardStateCallback)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 815b86e..d7eddf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -71,6 +71,7 @@
private int[] mTmp = new int[2];
private boolean mHideBackground;
private int mStatusBarHeight;
+ private boolean mEnableNotificationClipping;
private AmbientState mAmbientState;
private NotificationStackScrollLayoutController mHostLayoutController;
private int mPaddingBetweenElements;
@@ -117,7 +118,7 @@
// Setting this to first in section to get the clipping to the top roundness correct. This
// value determines the way we are clipping to the top roundness of the overall shade
setFirstInSection(true);
- initDimens();
+ updateResources();
}
public void bind(AmbientState ambientState,
@@ -126,14 +127,17 @@
mHostLayoutController = hostLayoutController;
}
- private void initDimens() {
+ private void updateResources() {
Resources res = getResources();
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
ViewGroup.LayoutParams layoutParams = getLayoutParams();
- layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
- setLayoutParams(layoutParams);
+ final int newShelfHeight = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
+ if (newShelfHeight != layoutParams.height) {
+ layoutParams.height = newShelfHeight;
+ setLayoutParams(layoutParams);
+ }
final int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
mShelfIcons.setPadding(padding, 0, padding, 0);
@@ -141,6 +145,7 @@
mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
mCornerAnimationDistance = res.getDimensionPixelSize(
R.dimen.notification_corner_animation_distance);
+ mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
mShelfIcons.setInNotificationIconShelf(true);
if (!mShowNotificationShelf) {
@@ -151,7 +156,7 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- initDimens();
+ updateResources();
}
@Override
@@ -636,7 +641,8 @@
}
if (!isPinned) {
if (viewEnd > notificationClipEnd && !shouldClipOwnTop) {
- int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
+ int clipBottomAmount =
+ mEnableNotificationClipping ? (int) (viewEnd - notificationClipEnd) : 0;
view.setClipBottomAmount(clipBottomAmount);
} else {
view.setClipBottomAmount(0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
index 13d8adb..572c0e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
@@ -51,8 +51,8 @@
updateResources()
}
})
- dumpManager.registerDumpable("SplitShadeLockscreenOverScroller") { printWriter, _ ->
- dump(printWriter)
+ dumpManager.registerCriticalDumpable("SplitShadeLockscreenOverScroller") { pw, _ ->
+ dump(pw)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index c070fcc..324e972 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -24,17 +24,21 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import javax.inject.Inject;
/**
- *
+ * A Helper class that offloads {@link Vibrator} calls to a different thread.
+ * {@link Vibrator} makes blocking calls that may cause SysUI to ANR.
+ * TODO(b/245528624): Use regular Vibrator instance once new APIs are available.
*/
@SysUISingleton
public class VibratorHelper {
@@ -53,10 +57,18 @@
private final Executor mExecutor;
/**
- *
+ * Creates a vibrator helper on a new single threaded {@link Executor}.
*/
@Inject
- public VibratorHelper(@Nullable Vibrator vibrator, @Background Executor executor) {
+ public VibratorHelper(@Nullable Vibrator vibrator) {
+ this(vibrator, Executors.newSingleThreadExecutor());
+ }
+
+ /**
+ * Creates new vibrator helper on a specific {@link Executor}.
+ */
+ @VisibleForTesting
+ public VibratorHelper(@Nullable Vibrator vibrator, Executor executor) {
mExecutor = executor;
mVibrator = vibrator;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 3cc53c1..fe488a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -39,6 +39,7 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
@@ -76,7 +77,7 @@
import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DataSaverControllerImpl;
@@ -129,7 +130,7 @@
private final boolean mHasMobileDataFeature;
private final SubscriptionDefaults mSubDefaults;
private final DataSaverController mDataSaverController;
- private final CurrentUserTracker mUserTracker;
+ private final UserTracker mUserTracker;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DemoModeController mDemoModeController;
private final Object mLock = new Object();
@@ -213,6 +214,14 @@
}
};
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ NetworkControllerImpl.this.onUserSwitched(newUser);
+ }
+ };
+
/**
* Construct this controller object and register for updates.
*/
@@ -225,6 +234,7 @@
CallbackHandler callbackHandler,
DeviceProvisionedController deviceProvisionedController,
BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
ConnectivityManager connectivityManager,
TelephonyManager telephonyManager,
TelephonyListenerManager telephonyListenerManager,
@@ -252,6 +262,7 @@
new SubscriptionDefaults(),
deviceProvisionedController,
broadcastDispatcher,
+ userTracker,
demoModeController,
carrierConfigTracker,
trackerFactory,
@@ -278,6 +289,7 @@
SubscriptionDefaults defaultsHandler,
DeviceProvisionedController deviceProvisionedController,
BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
DemoModeController demoModeController,
CarrierConfigTracker carrierConfigTracker,
WifiStatusTrackerFactory trackerFactory,
@@ -334,13 +346,9 @@
// AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
updateAirplaneMode(true /* force callback */);
- mUserTracker = new CurrentUserTracker(broadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- NetworkControllerImpl.this.onUserSwitched(newUserId);
- }
- };
- mUserTracker.startTracking();
+ mUserTracker = userTracker;
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+
deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
@Override
public void onUserSetupChanged() {
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 e4a8f21..c6911b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -234,19 +234,24 @@
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
- activityStarter.startActivity(
- intent,
- true, /* dismissShade */
- null, /* launch animator - looks bad with the transparent smartspace bg */
- showOnLockscreen
- )
+ if (showOnLockscreen) {
+ activityStarter.startActivity(
+ intent,
+ true, /* dismissShade */
+ // launch animator - looks bad with the transparent smartspace bg
+ null,
+ true
+ )
+ } else {
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0)
+ }
}
override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) {
if (showOnLockscreen) {
pi.send()
} else {
- activityStarter.startPendingIntentDismissingKeyguard(pi)
+ activityStarter.postStartActivityDismissingKeyguard(pi)
}
}
})
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 2734511..39daa13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -40,4 +40,12 @@
val isSemiStableSortEnabled: Boolean by lazy {
featureFlags.isEnabled(Flags.SEMI_STABLE_SORT)
}
+
+ val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy {
+ featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
+ }
+
+ val isNoHunForOldWhenEnabled: Boolean by lazy {
+ featureFlags.isEnabled(Flags.NO_HUN_FOR_OLD_WHEN)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index d97b712..3e2dd05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -38,6 +38,7 @@
import javax.inject.Inject
import kotlin.math.min
+
@SysUISingleton
class NotificationWakeUpCoordinator @Inject constructor(
dumpManager: DumpManager,
@@ -45,7 +46,8 @@
private val statusBarStateController: StatusBarStateController,
private val bypassController: KeyguardBypassController,
private val dozeParameters: DozeParameters,
- private val screenOffAnimationController: ScreenOffAnimationController
+ private val screenOffAnimationController: ScreenOffAnimationController,
+ private val logger: NotificationWakeUpCoordinatorLogger,
) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, ShadeExpansionListener,
Dumpable {
@@ -242,6 +244,7 @@
}
override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ logger.logOnDozeAmountChanged(linear, eased)
if (overrideDozeAmountIfAnimatingScreenOff(linear)) {
return
}
@@ -273,6 +276,7 @@
}
override fun onStateChanged(newState: Int) {
+ logger.logOnStateChanged(newState)
if (state == StatusBarState.SHADE && newState == StatusBarState.SHADE) {
// The SHADE -> SHADE transition is only possible as part of cancelling the screen-off
// animation (e.g. by fingerprint unlock). This is done because the system is in an
@@ -320,8 +324,12 @@
private fun overrideDozeAmountIfBypass(): Boolean {
if (bypassController.bypassEnabled) {
if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+ logger.logSetDozeAmount("1.0", "1.0",
+ "Override: bypass (keyguard)", StatusBarState.KEYGUARD)
setDozeAmount(1f, 1f, source = "Override: bypass (keyguard)")
} else {
+ logger.logSetDozeAmount("0.0", "0.0",
+ "Override: bypass (shade)", statusBarStateController.state)
setDozeAmount(0f, 0f, source = "Override: bypass (shade)")
}
return true
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
new file mode 100644
index 0000000..b40ce25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
+import javax.inject.Inject
+
+class NotificationWakeUpCoordinatorLogger
+@Inject
+constructor(@NotificationLog private val buffer: LogBuffer) {
+ fun logSetDozeAmount(linear: String, eased: String, source: String, state: Int) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = linear
+ str2 = eased
+ str3 = source
+ int1 = state
+ },
+ { "setDozeAmount: linear: $str1, eased: $str2, source: $str3, state: $int1" }
+ )
+ }
+
+ fun logOnDozeAmountChanged(linear: Float, eased: Float) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ double1 = linear.toDouble()
+ str2 = eased.toString()
+ },
+ { "onDozeAmountChanged($double1, $str2)" }
+ )
+ }
+
+ fun logOnStateChanged(newState: Int) {
+ buffer.log(TAG, DEBUG, { int1 = newState }, { "onStateChanged($int1)" })
+ }
+}
+
+private const val TAG = "NotificationWakeUpCoordinator"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 585d871..37d82ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -676,9 +676,6 @@
return row != null && row.areChildrenExpanded();
}
- public boolean keepInParent() {
- return row != null && row.keepInParent();
- }
//TODO: probably less confusing to say "is group fully visible"
public boolean isGroupNotFullyVisible() {
@@ -698,10 +695,6 @@
return row != null && row.isSummaryWithChildren();
}
- public void setKeepInParent(boolean keep) {
- if (row != null) row.setKeepInParent(keep);
- }
-
public void onDensityOrFontScaleChanged() {
if (row != null) row.onDensityOrFontScaleChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 3ae2545..65a21a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -1160,12 +1160,21 @@
mLogger.logParentChanged(mIterationCount, prev.getParent(), curr.getParent());
}
- if (curr.getSuppressedChanges().getParent() != null) {
- mLogger.logParentChangeSuppressed(
+ GroupEntry currSuppressedParent = curr.getSuppressedChanges().getParent();
+ GroupEntry prevSuppressedParent = prev.getSuppressedChanges().getParent();
+ if (currSuppressedParent != null && (prevSuppressedParent == null
+ || !prevSuppressedParent.getKey().equals(currSuppressedParent.getKey()))) {
+ mLogger.logParentChangeSuppressedStarted(
mIterationCount,
- curr.getSuppressedChanges().getParent(),
+ currSuppressedParent,
curr.getParent());
}
+ if (prevSuppressedParent != null && currSuppressedParent == null) {
+ mLogger.logParentChangeSuppressedStopped(
+ mIterationCount,
+ prevSuppressedParent,
+ prev.getParent());
+ }
if (curr.getSuppressedChanges().getSection() != null) {
mLogger.logSectionChangeSuppressed(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
deleted file mode 100644
index e3d71c8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 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.notification.collection.coordinator;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-
-import javax.inject.Inject;
-
-/**
- * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
- */
-@CoordinatorScope
-public class KeyguardCoordinator implements Coordinator {
- private static final String TAG = "KeyguardCoordinator";
- private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
- private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
- private final StatusBarStateController mStatusBarStateController;
-
- @Inject
- public KeyguardCoordinator(
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
- SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider,
- StatusBarStateController statusBarStateController) {
- mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
- mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
- mStatusBarStateController = statusBarStateController;
- }
-
- @Override
- public void attach(NotifPipeline pipeline) {
-
- setupInvalidateNotifListCallbacks();
- // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
- pipeline.addFinalizeFilter(mNotifFilter);
- mKeyguardNotificationVisibilityProvider
- .addOnStateChangedListener(this::invalidateListFromFilter);
- updateSectionHeadersVisibility();
- }
-
- private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
- @Override
- public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
- return mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry);
- }
- };
-
- // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
- // these same updates
- private void setupInvalidateNotifListCallbacks() {
-
- }
-
- private void invalidateListFromFilter(String reason) {
- updateSectionHeadersVisibility();
- mNotifFilter.invalidateList(reason);
- }
-
- private void updateSectionHeadersVisibility() {
- boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
- boolean showSections = !onKeyguard && !neverShowSections;
- mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
- }
-}
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
new file mode 100644
index 0000000..6e5fceb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+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.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
+ * headers on the lockscreen.
+ */
+@CoordinatorScope
+class KeyguardCoordinator
+@Inject
+constructor(
+ private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
+ private val keyguardRepository: KeyguardRepository,
+ private val notifPipelineFlags: NotifPipelineFlags,
+ @Application private val scope: CoroutineScope,
+ private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
+ private val statusBarStateController: StatusBarStateController,
+) : Coordinator {
+
+ private val unseenNotifications = mutableSetOf<NotificationEntry>()
+
+ override fun attach(pipeline: NotifPipeline) {
+ setupInvalidateNotifListCallbacks()
+ // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
+ pipeline.addFinalizeFilter(notifFilter)
+ keyguardNotificationVisibilityProvider.addOnStateChangedListener(::invalidateListFromFilter)
+ updateSectionHeadersVisibility()
+ if (notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard) {
+ attachUnseenFilter(pipeline)
+ }
+ }
+
+ private fun attachUnseenFilter(pipeline: NotifPipeline) {
+ pipeline.addFinalizeFilter(unseenNotifFilter)
+ pipeline.addCollectionListener(collectionListener)
+ scope.launch { clearUnseenWhenKeyguardIsDismissed() }
+ }
+
+ private suspend fun clearUnseenWhenKeyguardIsDismissed() {
+ // Use collectLatest so that the suspending block is cancelled if isKeyguardShowing changes
+ // during the timeout period
+ keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
+ if (!isKeyguardShowing) {
+ unseenNotifFilter.invalidateList("keyguard no longer showing")
+ delay(SEEN_TIMEOUT)
+ unseenNotifications.clear()
+ }
+ }
+ }
+
+ private val collectionListener =
+ object : NotifCollectionListener {
+ override fun onEntryAdded(entry: NotificationEntry) {
+ if (keyguardRepository.isKeyguardShowing()) {
+ unseenNotifications.add(entry)
+ }
+ }
+
+ override fun onEntryUpdated(entry: NotificationEntry) {
+ if (keyguardRepository.isKeyguardShowing()) {
+ unseenNotifications.add(entry)
+ }
+ }
+
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ unseenNotifications.remove(entry)
+ }
+ }
+
+ @VisibleForTesting
+ internal val unseenNotifFilter =
+ object : NotifFilter("$TAG-unseen") {
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+ when {
+ // Don't apply filter if the keyguard isn't currently showing
+ !keyguardRepository.isKeyguardShowing() -> false
+ // Don't apply the filter if the notification is unseen
+ unseenNotifications.contains(entry) -> false
+ // Don't apply the filter to (non-promoted) group summaries
+ // - summary will be pruned if necessary, depending on if children are filtered
+ entry.parent?.summary == entry -> false
+ else -> true
+ }
+ }
+
+ private val notifFilter: NotifFilter =
+ object : NotifFilter(TAG) {
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+ keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
+ }
+
+ // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
+ // these same updates
+ private fun setupInvalidateNotifListCallbacks() {}
+
+ private fun invalidateListFromFilter(reason: String) {
+ updateSectionHeadersVisibility()
+ notifFilter.invalidateList(reason)
+ }
+
+ private fun updateSectionHeadersVisibility() {
+ val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
+ val neverShowSections = sectionHeaderVisibilityProvider.neverShowSectionHeaders
+ val showSections = !onKeyguard && !neverShowSections
+ sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections
+ }
+
+ companion object {
+ private const val TAG = "KeyguardCoordinator"
+ private val SEEN_TIMEOUT = 5.seconds
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 3002a68..a2379b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeStateEvents;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -62,6 +63,7 @@
private final HeadsUpManager mHeadsUpManager;
private final ShadeStateEvents mShadeStateEvents;
private final StatusBarStateController mStatusBarStateController;
+ private final VisibilityLocationProvider mVisibilityLocationProvider;
private final VisualStabilityProvider mVisualStabilityProvider;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -94,9 +96,11 @@
HeadsUpManager headsUpManager,
ShadeStateEvents shadeStateEvents,
StatusBarStateController statusBarStateController,
+ VisibilityLocationProvider visibilityLocationProvider,
VisualStabilityProvider visualStabilityProvider,
WakefulnessLifecycle wakefulnessLifecycle) {
mHeadsUpManager = headsUpManager;
+ mVisibilityLocationProvider = visibilityLocationProvider;
mVisualStabilityProvider = visualStabilityProvider;
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
@@ -123,6 +127,11 @@
// HUNs to the top of the shade
private final NotifStabilityManager mNotifStabilityManager =
new NotifStabilityManager("VisualStabilityCoordinator") {
+ private boolean canMoveForHeadsUp(NotificationEntry entry) {
+ return entry != null && mHeadsUpManager.isAlerting(entry.getKey())
+ && !mVisibilityLocationProvider.isInVisibleLocation(entry);
+ }
+
@Override
public void onBeginRun() {
mIsSuppressingPipelineRun = false;
@@ -140,7 +149,7 @@
@Override
public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isGroupChangeAllowedForEntry =
- mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
+ mReorderingAllowed || canMoveForHeadsUp(entry);
mIsSuppressingGroupChange |= !isGroupChangeAllowedForEntry;
return isGroupChangeAllowedForEntry;
}
@@ -156,7 +165,7 @@
public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isSectionChangeAllowedForEntry =
mReorderingAllowed
- || mHeadsUpManager.isAlerting(entry.getKey())
+ || canMoveForHeadsUp(entry)
|| mEntriesThatCanChangeSection.containsKey(entry.getKey());
if (!isSectionChangeAllowedForEntry) {
mEntriesWithSuppressedSectionChange.add(entry.getKey());
@@ -165,8 +174,8 @@
}
@Override
- public boolean isEntryReorderingAllowed(@NonNull ListEntry section) {
- return mReorderingAllowed;
+ public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) {
+ return mReorderingAllowed || canMoveForHeadsUp(entry.getRepresentativeEntry());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 8e052c7..4adc90a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -193,7 +193,7 @@
})
}
- fun logParentChangeSuppressed(
+ fun logParentChangeSuppressedStarted(
buildId: Int,
suppressedParent: GroupEntry?,
keepingParent: GroupEntry?
@@ -207,6 +207,21 @@
})
}
+ fun logParentChangeSuppressedStopped(
+ buildId: Int,
+ previouslySuppressedParent: GroupEntry?,
+ previouslyKeptParent: GroupEntry?
+ ) {
+ buffer.log(TAG, INFO, {
+ long1 = buildId.toLong()
+ str1 = previouslySuppressedParent?.logKey
+ str2 = previouslyKeptParent?.logKey
+ }, {
+ "(Build $long1) Change of parent to '$str1' no longer suppressed; " +
+ "replaced parent '$str2'"
+ })
+ }
+
fun logGroupPruningSuppressed(
buildId: Int,
keepingParent: GroupEntry?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt
new file mode 100644
index 0000000..4bc4ecf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/**
+ * An injectable component which delegates the visibility location computation to a delegate which
+ * can be initialized after the initial injection, generally because it's provided by a view.
+ */
+@SysUISingleton
+class VisibilityLocationProviderDelegator @Inject constructor() : VisibilityLocationProvider {
+ private var delegate: VisibilityLocationProvider? = null
+
+ fun setDelegate(provider: VisibilityLocationProvider) {
+ delegate = provider
+ }
+
+ override fun isInVisibleLocation(entry: NotificationEntry): Boolean =
+ requireNotNull(this.delegate) { "delegate not initialized" }.isInVisibleLocation(entry)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
index f949af0..8de0381 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt
@@ -55,4 +55,8 @@
override val view: View
get() = mediaContainerView!!
+
+ override fun offerToKeepInParentForAnimation(): Boolean = false
+ override fun removeFromParentIfKeptForAnimation(): Boolean = false
+ override fun resetKeepInParentForAnimation() {}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
index 26ba12c..ae72a3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt
@@ -30,6 +30,7 @@
* below.
*/
interface NodeController {
+
/** A string that uniquely(ish) represents the node in the tree. Used for debugging. */
val nodeLabel: String
@@ -64,6 +65,27 @@
/** Called when this view has been removed */
fun onViewRemoved() {}
+
+ /**
+ * Called before removing a node from its parent
+ *
+ * If returned true, the ShadeViewDiffer won't detach this row and the view system is
+ * responsible for ensuring the row is in eventually removed from the parent.
+ *
+ * @return false to opt out from this feature
+ */
+ fun offerToKeepInParentForAnimation(): Boolean
+
+ /**
+ * Called before a node is reattached. Removes the view from its parent
+ * if it was flagged to be kept before.
+ *
+ * @return whether it did a removal
+ */
+ fun removeFromParentIfKeptForAnimation(): Boolean
+
+ /** Called when a node is being reattached */
+ fun resetKeepInParentForAnimation()
}
/**
@@ -90,7 +112,7 @@
}
private fun treeSpecToStrHelper(tree: NodeSpec, sb: StringBuilder, indent: String) {
- sb.append("${indent}{${tree.controller.nodeLabel}}\n")
+ sb.append("$indent{${tree.controller.nodeLabel}}\n")
if (tree.children.isNotEmpty()) {
val childIndent = "$indent "
for (child in tree.children) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
index 2073e92..5ff686a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt
@@ -32,6 +32,9 @@
override val view: View
) : NodeController, PipelineDumpable {
override val nodeLabel: String = "<root>"
+ override fun offerToKeepInParentForAnimation(): Boolean = false
+ override fun removeFromParentIfKeptForAnimation(): Boolean = false
+ override fun resetKeepInParentForAnimation() {}
override fun getChildAt(index: Int): View? {
return listContainer.getContainerChildAt(index)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 2c9508e..7b59266 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -100,4 +100,7 @@
override val view: View
get() = _view!!
+ override fun offerToKeepInParentForAnimation(): Boolean = false
+ override fun removeFromParentIfKeptForAnimation(): Boolean = false
+ override fun resetKeepInParentForAnimation() {}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 9a9941e..18ee481 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -86,10 +86,10 @@
}
private fun maybeDetachChild(
- parentNode: ShadeNode,
- parentSpec: NodeSpec?,
- childNode: ShadeNode,
- childSpec: NodeSpec?
+ parentNode: ShadeNode,
+ parentSpec: NodeSpec?,
+ childNode: ShadeNode,
+ childSpec: NodeSpec?
) {
val newParentNode = childSpec?.parent?.let { getNode(it) }
@@ -100,14 +100,27 @@
nodes.remove(childNode.controller)
}
- logger.logDetachingChild(
- key = childNode.label,
- isTransfer = !childCompletelyRemoved,
- isParentRemoved = parentSpec == null,
- oldParent = parentNode.label,
- newParent = newParentNode?.label)
- parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
- childNode.parent = null
+ if (childCompletelyRemoved && parentSpec == null &&
+ childNode.offerToKeepInParentForAnimation()) {
+ // If both the child and the parent are being removed at the same time, then
+ // keep the child attached to the parent for animation purposes
+ logger.logSkipDetachingChild(
+ key = childNode.label,
+ parentKey = parentNode.label,
+ isTransfer = !childCompletelyRemoved,
+ isParentRemoved = true
+ )
+ } else {
+ logger.logDetachingChild(
+ key = childNode.label,
+ isTransfer = !childCompletelyRemoved,
+ isParentRemoved = parentSpec == null,
+ oldParent = parentNode.label,
+ newParent = newParentNode?.label
+ )
+ parentNode.removeChild(childNode, isTransfer = !childCompletelyRemoved)
+ childNode.parent = null
+ }
}
}
@@ -119,6 +132,16 @@
val childNode = getNode(childSpec)
if (childNode.view != currView) {
+ val removedFromParent = childNode.removeFromParentIfKeptForAnimation()
+ if (removedFromParent) {
+ logger.logDetachingChild(
+ key = childNode.label,
+ isTransfer = false,
+ isParentRemoved = true,
+ oldParent = null,
+ newParent = null
+ )
+ }
when (childNode.parent) {
null -> {
@@ -142,6 +165,8 @@
}
}
+ childNode.resetKeepInParentForAnimation()
+
if (childSpec.children.isNotEmpty()) {
attachChildren(childNode, specMap)
}
@@ -213,4 +238,16 @@
controller.removeChild(child.controller, isTransfer)
child.controller.onViewRemoved()
}
+
+ fun offerToKeepInParentForAnimation(): Boolean {
+ return controller.offerToKeepInParentForAnimation()
+ }
+
+ fun removeFromParentIfKeptForAnimation(): Boolean {
+ return controller.removeFromParentIfKeptForAnimation()
+ }
+
+ fun resetKeepInParentForAnimation() {
+ controller.resetKeepInParentForAnimation()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index b4b9438..1e22c2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -43,6 +43,20 @@
})
}
+ fun logSkipDetachingChild(
+ key: String,
+ parentKey: String?,
+ isTransfer: Boolean,
+ isParentRemoved: Boolean
+ ) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = key
+ str2 = parentKey
+ bool1 = isTransfer
+ bool2 = isParentRemoved
+ }, { "Skip detaching $str1 from $str2 isTransfer=$bool1 isParentRemoved=$bool2" })
+ }
+
fun logAttachingChild(key: String, parent: String, atIndex: Int) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = key
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index df2de56..a7b7a23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -37,6 +37,7 @@
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
@@ -51,6 +52,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -151,6 +153,11 @@
@Binds
NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
+ /** Provides an instance of {@link VisibilityLocationProvider} */
+ @Binds
+ VisibilityLocationProvider bindVisibilityLocationProvider(
+ VisibilityLocationProviderDelegator visibilityLocationProviderDelegator);
+
/** Provides an instance of {@link NotificationLogger} */
@SysUISingleton
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index e6dbcee..7513aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -2,22 +2,20 @@
import android.app.Notification
import android.app.Notification.VISIBILITY_SECRET
-import android.content.BroadcastReceiver
import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
+import android.os.HandlerExecutor
import android.os.UserHandle
import android.provider.Settings
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.CoreStartable
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -78,7 +76,7 @@
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val highPriorityProvider: HighPriorityProvider,
private val statusBarStateController: SysuiStatusBarStateController,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val secureSettings: SecureSettings,
private val globalSettings: GlobalSettings
) : CoreStartable, KeyguardNotificationVisibilityProvider {
@@ -87,6 +85,15 @@
private val onStateChangedListeners = ListenerSet<Consumer<String>>()
private var hideSilentNotificationsOnLockscreen: Boolean = false
+ private val userTrackerCallback = object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ if (isLockedOrLocking) {
+ // maybe public mode changed
+ notifyStateChanged("onUserSwitched")
+ }
+ }
+ }
+
override fun start() {
readShowSilentNotificationSetting()
keyguardStateController.addCallback(object : KeyguardStateController.Callback {
@@ -143,14 +150,7 @@
notifyStateChanged("onStatusBarUpcomingStateChanged")
}
})
- broadcastDispatcher.registerReceiver(object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (isLockedOrLocking) {
- // maybe public mode changed
- notifyStateChanged(intent.action!!)
- }
- }
- }, IntentFilter(Intent.ACTION_USER_SWITCHED))
+ userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
}
override fun addOnStateChangedListener(listener: Consumer<String>) {
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 073b6b0..13b3aca 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
@@ -106,6 +106,36 @@
})
}
+ fun logNoHeadsUpOldWhen(
+ entry: NotificationEntry,
+ notifWhen: Long,
+ notifAge: Long
+ ) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ long1 = notifWhen
+ long2 = notifAge
+ }, {
+ "No heads up: old when $long1 (age=$long2 ms): $str1"
+ })
+ }
+
+ fun logMaybeHeadsUpDespiteOldWhen(
+ entry: NotificationEntry,
+ notifWhen: Long,
+ notifAge: Long,
+ reason: String
+ ) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ str2 = reason
+ long1 = notifWhen
+ long2 = notifAge
+ }, {
+ "Maybe heads up: old when $long1 (age=$long2 ms) but $str2: $str1"
+ })
+ }
+
fun logNoHeadsUpSuppressedBy(
entry: NotificationEntry,
suppressor: NotificationInterruptSuppressor
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 c4f5a3a..ec5bd68 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
@@ -20,6 +20,7 @@
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 android.app.Notification;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.database.ContentObserver;
@@ -82,7 +83,10 @@
FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235),
@UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard")
- FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236);
+ FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236),
+
+ @UiEvent(doc = "HUN suppressed for old when")
+ HUN_SUPPRESSED_OLD_WHEN(1237);
private final int mId;
@@ -346,6 +350,10 @@
return false;
}
+ if (shouldSuppressHeadsUpWhenAwakeForOldWhen(entry, log)) {
+ return false;
+ }
+
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) {
if (log) mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i));
@@ -470,4 +478,51 @@
private boolean isSnoozedPackage(StatusBarNotification sbn) {
return mHeadsUpManager.isSnoozed(sbn.getPackageName());
}
+
+ private boolean shouldSuppressHeadsUpWhenAwakeForOldWhen(NotificationEntry entry, boolean log) {
+ if (!mFlags.isNoHunForOldWhenEnabled()) {
+ return false;
+ }
+
+ final Notification notification = entry.getSbn().getNotification();
+ if (notification == null) {
+ return false;
+ }
+
+ final long when = notification.when;
+ final long now = System.currentTimeMillis();
+ final long age = now - when;
+
+ if (age < MAX_HUN_WHEN_AGE_MS) {
+ return false;
+ }
+
+ if (when <= 0) {
+ // Some notifications (including many system notifications) are posted with the "when"
+ // field set to 0. Nothing in the Javadocs for Notification mentions a special meaning
+ // for a "when" of 0, but Android didn't even exist at the dawn of the Unix epoch.
+ // Therefore, assume that these notifications effectively don't have a "when" value,
+ // and don't suppress HUNs.
+ if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "when <= 0");
+ return false;
+ }
+
+ if (notification.fullScreenIntent != null) {
+ if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "full-screen intent");
+ return false;
+ }
+
+ if (notification.isForegroundService()) {
+ if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "foreground service");
+ return false;
+ }
+
+ if (log) mLogger.logNoHeadsUpOldWhen(entry, when, age);
+ final int uid = entry.getSbn().getUid();
+ final String packageName = entry.getSbn().getPackageName();
+ mUiEventLogger.log(NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN, uid, packageName);
+ return true;
+ }
+
+ public static final long MAX_HUN_WHEN_AGE_MS = 24 * 60 * 60 * 1000;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 3021414..d7d5ac9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -19,6 +19,7 @@
import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.PARENT_DISMISSED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
import android.animation.Animator;
@@ -135,6 +136,8 @@
private static final String TAG = "ExpandableNotifRow";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
+ private static final boolean DEBUG_ONMEASURE =
+ Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
private static final int COLORED_DIVIDER_ALPHA = 0x7B;
private static final int MENU_VIEW_INDEX = 0;
@@ -238,7 +241,7 @@
private NotificationContentView mPrivateLayout;
private NotificationContentView[] mLayouts;
private int mNotificationColor;
- private ExpansionLogger mLogger;
+ private ExpandableNotificationRowLogger mLogger;
private String mLoggingKey;
private NotificationGuts mGuts;
private NotificationEntry mEntry;
@@ -337,7 +340,7 @@
}
}
};
- private boolean mKeepInParent;
+ private boolean mKeepInParentForDismissAnimation;
private boolean mRemoved;
private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =
new FloatProperty<ExpandableNotificationRow>("translate") {
@@ -823,6 +826,12 @@
if (mChildrenContainer == null) {
mChildrenContainerStub.inflate();
}
+
+ if (row.keepInParentForDismissAnimation()) {
+ logSkipAttachingKeepInParentChild(row);
+ return;
+ }
+
mChildrenContainer.addNotification(row, childIndex);
onAttachedChildrenCountChanged();
row.setIsChildInGroup(true, this);
@@ -831,6 +840,7 @@
public void removeChildNotification(ExpandableNotificationRow row) {
if (mChildrenContainer != null) {
mChildrenContainer.removeNotification(row);
+ row.setKeepInParentForDismissAnimation(false);
}
onAttachedChildrenCountChanged();
row.setIsChildInGroup(false, null);
@@ -838,6 +848,31 @@
}
/**
+ * Removes the children notifications which were marked to keep for the dismissal animation.
+ */
+ public void removeChildrenWithKeepInParent() {
+ if (mChildrenContainer == null) return;
+
+ List<ExpandableNotificationRow> clonedList = new ArrayList<>(
+ mChildrenContainer.getAttachedChildren());
+ boolean childCountChanged = false;
+ for (ExpandableNotificationRow child : clonedList) {
+ if (child.keepInParentForDismissAnimation()) {
+ mChildrenContainer.removeNotification(child);
+ child.setIsChildInGroup(false, null);
+ child.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+ child.setKeepInParentForDismissAnimation(false);
+ logKeepInParentChildDetached(child);
+ childCountChanged = true;
+ }
+ }
+
+ if (childCountChanged) {
+ onAttachedChildrenCountChanged();
+ }
+ }
+
+ /**
* Returns the child notification at [index], or null if no such child.
*/
@Nullable
@@ -1359,12 +1394,20 @@
}
}
- public boolean keepInParent() {
- return mKeepInParent;
+ /**
+ * @return if this entry should be kept in its parent during removal.
+ */
+ public boolean keepInParentForDismissAnimation() {
+ return mKeepInParentForDismissAnimation;
}
- public void setKeepInParent(boolean keepInParent) {
- mKeepInParent = keepInParent;
+ public void setKeepInParentForDismissAnimation(boolean keepInParent) {
+ mKeepInParentForDismissAnimation = keepInParent;
+ }
+
+ /** @return true if the User has dismissed this notif's parent */
+ public boolean isParentDismissed() {
+ return getEntry().getDismissState() == PARENT_DISMISSED;
}
@Override
@@ -1535,8 +1578,29 @@
mUseIncreasedHeadsUpHeight = use;
}
- public interface ExpansionLogger {
+ /**
+ * Interface for logging {{@link ExpandableNotificationRow} events.}
+ */
+ public interface ExpandableNotificationRowLogger {
+ /**
+ * Called when the notification is expanded / collapsed.
+ */
void logNotificationExpansion(String key, boolean userAction, boolean expanded);
+
+ /**
+ * Called when a notification which was previously kept in its parent for the
+ * dismiss animation is finally detached from its parent.
+ */
+ void logKeepInParentChildDetached(NotificationEntry child, NotificationEntry oldParent);
+
+ /**
+ * Called when we want to attach a notification to a new parent,
+ * but it still has the keepInParent flag set, so we skip it.
+ */
+ void logSkipAttachingKeepInParentChild(
+ NotificationEntry child,
+ NotificationEntry newParent
+ );
}
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
@@ -1554,7 +1618,7 @@
RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
String appName,
String notificationKey,
- ExpansionLogger logger,
+ ExpandableNotificationRowLogger logger,
KeyguardBypassController bypassController,
GroupMembershipManager groupMembershipManager,
GroupExpansionManager groupExpansionManager,
@@ -1724,6 +1788,11 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Trace.beginSection(appendTraceStyleTag("ExpNotRow#onMeasure"));
+ if (DEBUG_ONMEASURE) {
+ Log.d(TAG, "onMeasure("
+ + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")");
+ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Trace.endSection();
}
@@ -3560,6 +3629,18 @@
});
}
+ private void logKeepInParentChildDetached(ExpandableNotificationRow child) {
+ if (mLogger != null) {
+ mLogger.logKeepInParentChildDetached(child.getEntry(), getEntry());
+ }
+ }
+
+ private void logSkipAttachingKeepInParentChild(ExpandableNotificationRow child) {
+ if (mLogger != null) {
+ mLogger.logSkipAttachingKeepInParentChild(child.getEntry(), getEntry());
+ }
+ }
+
private void setTargetPoint(Point p) {
mTargetPoint = p;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 842526e..8a400d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -33,9 +33,9 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.FeedbackIcon;
@@ -83,13 +83,11 @@
private final GroupExpansionManager mGroupExpansionManager;
private final RowContentBindStage mRowContentBindStage;
private final NotificationLogger mNotificationLogger;
+ private final NotificationRowLogger mLogBufferLogger;
private final HeadsUpManager mHeadsUpManager;
private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
private final StatusBarStateController mStatusBarStateController;
private final MetricsLogger mMetricsLogger;
-
- private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
- this::logNotificationExpansion;
private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
private final NotificationGutsManager mNotificationGutsManager;
private final OnUserInteractionCallback mOnUserInteractionCallback;
@@ -101,8 +99,32 @@
private final Optional<BubblesManager> mBubblesManagerOptional;
private final SmartReplyConstants mSmartReplyConstants;
private final SmartReplyController mSmartReplyController;
-
private final ExpandableNotificationRowDragController mDragController;
+ private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
+ new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
+ @Override
+ public void logNotificationExpansion(String key, boolean userAction,
+ boolean expanded) {
+ mNotificationLogger.onExpansionChanged(key, userAction, expanded);
+ }
+
+ @Override
+ public void logKeepInParentChildDetached(
+ NotificationEntry child,
+ NotificationEntry oldParent
+ ) {
+ mLogBufferLogger.logKeepInParentChildDetached(child, oldParent);
+ }
+
+ @Override
+ public void logSkipAttachingKeepInParentChild(
+ NotificationEntry child,
+ NotificationEntry newParent
+ ) {
+ mLogBufferLogger.logSkipAttachingKeepInParentChild(child, newParent);
+ }
+ };
+
@Inject
public ExpandableNotificationRowController(
@@ -110,6 +132,7 @@
ActivatableNotificationViewController activatableNotificationViewController,
RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
MetricsLogger metricsLogger,
+ NotificationRowLogger logBufferLogger,
NotificationListContainer listContainer,
NotificationMediaManager mediaManager,
SmartReplyConstants smartReplyConstants,
@@ -163,6 +186,7 @@
mBubblesManagerOptional = bubblesManagerOptional;
mDragController = dragController;
mMetricsLogger = metricsLogger;
+ mLogBufferLogger = logBufferLogger;
mSmartReplyConstants = smartReplyConstants;
mSmartReplyController = smartReplyController;
}
@@ -177,7 +201,7 @@
mRemoteInputViewSubcomponentFactory,
mAppName,
mNotificationKey,
- mExpansionLogger,
+ mLoggerCallback,
mKeyguardBypassController,
mGroupMembershipManager,
mGroupExpansionManager,
@@ -243,10 +267,6 @@
}
};
- private void logNotificationExpansion(String key, boolean userAction, boolean expanded) {
- mNotificationLogger.onExpansionChanged(key, userAction, expanded);
- }
-
@Override
@NonNull
public String getNodeLabel() {
@@ -336,4 +356,34 @@
public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
mView.setFeedbackIcon(icon);
}
+
+ @Override
+ public boolean offerToKeepInParentForAnimation() {
+ //If the User dismissed the notification's parent, we want to keep it attached until the
+ //dismiss animation is ongoing. Therefore we don't want to remove it in the ShadeViewDiffer.
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION)
+ && mView.isParentDismissed()) {
+ mView.setKeepInParentForDismissAnimation(true);
+ return true;
+ }
+
+ //Otherwise the view system doesn't do the removal, so we rely on the ShadeViewDiffer
+ return false;
+ }
+
+ @Override
+ public boolean removeFromParentIfKeptForAnimation() {
+ ExpandableNotificationRow parent = mView.getNotificationParent();
+ if (mView.keepInParentForDismissAnimation() && parent != null) {
+ parent.removeChildNotification(mView);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void resetKeepInParentForAnimation() {
+ mView.setKeepInParentForDismissAnimation(false);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 64f87ca..b56bae1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -54,8 +54,6 @@
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import java.util.Collections;
-
import javax.inject.Inject;
/**
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 4fde5d0..2324627 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
@@ -84,7 +84,7 @@
int right;
int bottom;
int height;
- float topRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getTopCornerRadius();
+ float topRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getTopCornerRadius();
if (!mCustomOutline) {
// The outline just needs to be shifted if we're translating the contents. Otherwise
// it's already in the right place.
@@ -97,7 +97,7 @@
// 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.
bottom = Math.max(mMinimumHeightForClipping,
- Math.max(getActualHeight() - mClipBottomAmount, (int) (top + topRoundness)));
+ Math.max(getActualHeight() - mClipBottomAmount, (int) (top + topRadius)));
} else {
left = mOutlineRect.left;
top = mOutlineRect.top;
@@ -108,17 +108,17 @@
if (height == 0) {
return EMPTY_PATH;
}
- float bottomRoundness = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
- if (topRoundness + bottomRoundness > height) {
- float overShoot = topRoundness + bottomRoundness - height;
+ float bottomRadius = mAlwaysRoundBothCorners ? getMaxRadius() : getBottomCornerRadius();
+ if (topRadius + bottomRadius > height) {
+ float overShoot = topRadius + bottomRadius - height;
float currentTopRoundness = getTopRoundness();
float currentBottomRoundness = getBottomRoundness();
- topRoundness -= overShoot * currentTopRoundness
+ topRadius -= overShoot * currentTopRoundness
/ (currentTopRoundness + currentBottomRoundness);
- bottomRoundness -= overShoot * currentBottomRoundness
+ bottomRadius -= overShoot * currentBottomRoundness
/ (currentTopRoundness + currentBottomRoundness);
}
- getRoundedRectPath(left, top, right, bottom, topRoundness, bottomRoundness, mTmpPath);
+ getRoundedRectPath(left, top, right, bottom, topRadius, bottomRadius, mTmpPath);
return mTmpPath;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 0ce9656..f21db0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -154,7 +154,7 @@
// If the user selected Priority and the previous selection was not priority, show a
// People Tile add request.
if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
}
mGutsContainer.closeControls(v, /* save= */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
new file mode 100644
index 0000000..ce11be3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class NotificationRowLogger @Inject constructor(@NotificationLog private val buffer: LogBuffer) {
+ fun logKeepInParentChildDetached(child: NotificationEntry, oldParent: NotificationEntry?) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = child.logKey
+ str2 = oldParent.logKey
+ },
+ { "Detach child $str1 kept in parent $str2" }
+ )
+ }
+
+ fun logSkipAttachingKeepInParentChild(child: NotificationEntry, newParent: NotificationEntry?) {
+ buffer.log(
+ TAG,
+ LogLevel.WARNING,
+ {
+ str1 = child.logKey
+ str2 = newParent.logKey
+ },
+ { "Skipping to attach $str1 to $str2, because it still flagged to keep in parent" }
+ )
+ }
+}
+
+private const val TAG = "NotifRow"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 645a02d..d43ca823 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -25,6 +25,7 @@
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.drawable.ColorDrawable;
+import android.os.Trace;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
@@ -219,6 +220,7 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("NotificationChildrenContainer#onMeasure");
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
@@ -267,6 +269,7 @@
}
setMeasuredDimension(width, height);
+ Trace.endSection();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 2c3330e..b519aef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack;
+import static android.os.Trace.TRACE_TAG_ALWAYS;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
@@ -30,6 +32,7 @@
import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.annotation.ColorInt;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,6 +47,7 @@
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Trace;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
@@ -188,6 +192,7 @@
/** Used to track the Y positions that were already used to draw debug text labels. */
private Set<Integer> mDebugTextUsedYPositions;
private final boolean mDebugRemoveAnimation;
+ private final boolean mSimplifiedAppearFraction;
private int mContentHeight;
private float mIntrinsicContentHeight;
@@ -568,6 +573,7 @@
FeatureFlags featureFlags = Dependency.get(FeatureFlags.class);
mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
+ mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
mScreenOffAnimationController =
Dependency.get(ScreenOffAnimationController.class);
@@ -1074,6 +1080,12 @@
@Override
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("NotificationStackScrollLayout#onMeasure");
+ if (SPEW) {
+ Log.d(TAG, "onMeasure("
+ + "widthMeasureSpec=" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ + "heightMeasureSpec=" + MeasureSpec.toString(heightMeasureSpec) + ")");
+ }
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
@@ -1090,6 +1102,13 @@
for (int i = 0; i < size; i++) {
measureChild(getChildAt(i), childWidthSpec, childHeightSpec);
}
+ Trace.endSection();
+ }
+
+ @Override
+ public void requestLayout() {
+ Trace.instant(TRACE_TAG_ALWAYS, "NotificationStackScrollLayout#requestLayout");
+ super.requestLayout();
}
@Override
@@ -1382,10 +1401,10 @@
mExpandedHeight = height;
setIsExpanded(height > 0);
int minExpansionHeight = getMinExpansionHeight();
- if (height < minExpansionHeight) {
+ if (height < minExpansionHeight && !mShouldUseSplitNotificationShade) {
mClipRect.left = 0;
mClipRect.right = getWidth();
- mClipRect.top = getNotificationsClippingTopBound();
+ mClipRect.top = 0;
mClipRect.bottom = (int) height;
height = minExpansionHeight;
setRequestedClipBounds(mClipRect);
@@ -1395,10 +1414,8 @@
}
int stackHeight;
float translationY;
- float appearEndPosition = getAppearEndPosition();
- float appearStartPosition = getAppearStartPosition();
float appearFraction = 1.0f;
- boolean appearing = height < appearEndPosition;
+ boolean appearing = calculateAppearFraction(height) < 1;
mAmbientState.setAppearing(appearing);
if (!appearing) {
translationY = 0;
@@ -1429,11 +1446,14 @@
} else {
// This may happen when pushing up a heads up. We linearly push it up from the
// start
- translationY = height - appearStartPosition + getExpandTranslationStart();
+ translationY = height - getAppearStartPosition() + getExpandTranslationStart();
}
stackHeight = (int) (height - translationY);
- if (isHeadsUpTransition()) {
- translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction);
+ if (isHeadsUpTransition() && appearFraction >= 0) {
+ int topSpacing = mShouldUseSplitNotificationShade
+ ? mAmbientState.getStackTopMargin() : mTopPadding;
+ float startPos = mHeadsUpInset - topSpacing;
+ translationY = MathUtils.lerp(startPos, 0, appearFraction);
}
}
mAmbientState.setAppearFraction(appearFraction);
@@ -1446,17 +1466,6 @@
notifyAppearChangedListeners();
}
- private int getNotificationsClippingTopBound() {
- if (isHeadsUpTransition()) {
- // HUN in split shade can go higher than bottom of NSSL when swiping up so we want
- // to give it extra clipping margin. Because clipping has rounded corners, we also
- // need to account for that corner clipping.
- return -mAmbientState.getStackTopMargin() - mCornerRadius;
- } else {
- return 0;
- }
- }
-
private void notifyAppearChangedListeners() {
float appear;
float expandAmount;
@@ -1533,7 +1542,7 @@
final int pinnedHeight = firstVisibleSection != null
? firstVisibleSection.getFirstVisibleChild().getPinnedHeadsUpHeight()
: 0;
- return mHeadsUpInset + pinnedHeight;
+ return mHeadsUpInset - mAmbientState.getStackTopMargin() + pinnedHeight;
}
return getMinExpansionHeight();
}
@@ -1565,7 +1574,7 @@
*/
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private float getAppearEndPosition() {
- int appearPosition = 0;
+ int appearPosition = mAmbientState.getStackTopMargin();
int visibleNotifCount = mController.getVisibleNotificationCount();
if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) {
if (isHeadsUpTransition()
@@ -1589,18 +1598,49 @@
return mAmbientState.getTrackedHeadsUpRow() != null;
}
- /**
- * @param height the height of the panel
- * @return the fraction of the appear animation that has been performed
- */
+ // TODO(b/246353296): remove it when Flags.SIMPLIFIED_APPEAR_FRACTION is removed
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- public float calculateAppearFraction(float height) {
+ public float calculateAppearFractionOld(float height) {
float appearEndPosition = getAppearEndPosition();
float appearStartPosition = getAppearStartPosition();
return (height - appearStartPosition)
/ (appearEndPosition - appearStartPosition);
}
+ /**
+ * @param height the height of the panel
+ * @return Fraction of the appear animation that has been performed. Normally follows expansion
+ * fraction so goes from 0 to 1, the only exception is HUN where it can go negative, down to -1,
+ * when HUN is swiped up.
+ */
+ @FloatRange(from = -1.0, to = 1.0)
+ public float simplifiedAppearFraction(float height) {
+ if (isHeadsUpTransition()) {
+ // HUN is a special case because fraction can go negative if swiping up. And for now
+ // it must go negative as other pieces responsible for proper translation up assume
+ // negative value for HUN going up.
+ // This can't use expansion fraction as that goes only from 0 to 1. Also when
+ // appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3
+ // and that makes translation jump immediately. Let's use old implementation for now and
+ // see if we can figure out something better
+ return MathUtils.constrain(calculateAppearFractionOld(height), -1, 1);
+ } else {
+ return mAmbientState.getExpansionFraction();
+ }
+ }
+
+ public float calculateAppearFraction(float height) {
+ if (mSimplifiedAppearFraction) {
+ return simplifiedAppearFraction(height);
+ } else if (mShouldUseSplitNotificationShade) {
+ // for split shade we want to always use the new way of calculating appear fraction
+ // because without it heads-up experience is very broken and it's less risky change
+ return simplifiedAppearFraction(height);
+ } else {
+ return calculateAppearFractionOld(height);
+ }
+ }
+
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public float getStackTranslation() {
return mStackTranslation;
@@ -1758,7 +1798,8 @@
@Override
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mBottomInset = insets.getSystemWindowInsetBottom();
+ mBottomInset = insets.getSystemWindowInsetBottom()
+ + insets.getInsets(WindowInsets.Type.ime()).bottom;
mWaterfallTopInset = 0;
final DisplayCutout cutout = insets.getDisplayCutout();
@@ -2723,6 +2764,10 @@
}
} else {
mSwipedOutViews.remove(child);
+
+ if (child instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) child).removeChildrenWithKeepInParent();
+ }
}
updateAnimationState(false, child);
@@ -3717,7 +3762,7 @@
}
}
- private void debugLog(@CompileTimeConstant String s) {
+ private void debugLog(@CompileTimeConstant final String s) {
if (mLogger == null) {
return;
}
@@ -4180,7 +4225,7 @@
mShadeNeedsToClose = false;
postDelayed(
() -> {
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
},
DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
}
@@ -5083,6 +5128,7 @@
println(pw, "intrinsicPadding", mIntrinsicPadding);
println(pw, "topPadding", mTopPadding);
println(pw, "bottomPadding", mBottomPadding);
+ mNotificationStackSizeCalculator.dump(pw, args);
});
pw.println();
pw.println("Contents:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e1337826..ad4501a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -89,6 +89,7 @@
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
@@ -157,6 +158,7 @@
private final NotifCollection mNotifCollection;
private final UiEventLogger mUiEventLogger;
private final NotificationRemoteInputManager mRemoteInputManager;
+ private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
private final ShadeController mShadeController;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
@@ -441,7 +443,11 @@
if (!row.isDismissed()) {
handleChildViewDismissed(view);
}
+
row.removeFromTransientContainer();
+ if (row instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) row).removeChildrenWithKeepInParent();
+ }
}
/**
@@ -638,6 +644,7 @@
ShadeTransitionController shadeTransitionController,
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
+ VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
@@ -679,6 +686,7 @@
mNotifCollection = notifCollection;
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
+ mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
mShadeController = shadeController;
mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
@@ -750,6 +758,8 @@
mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+ mVisibilityLocationProviderDelegator.setDelegate(this::isInVisibleLocation);
+
mTunerService.addTunable(
(key, newValue) -> {
switch (key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index ae854e2..25f99c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.util.Compile
import com.android.systemui.util.children
+import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
@@ -53,6 +54,8 @@
@Main private val resources: Resources
) {
+ private lateinit var lastComputeHeightLog : String
+
/**
* Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
* If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -114,7 +117,9 @@
shelfIntrinsicHeight: Float
): Int {
log { "\n" }
- val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+
+ val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+ /* computeHeight= */ false)
var maxNotifications =
stackHeightSequence.lastIndexWhile { heightResult ->
@@ -157,18 +162,21 @@
shelfIntrinsicHeight: Float
): Float {
log { "\n" }
+ lastComputeHeightLog = ""
val heightPerMaxNotifications =
- computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+ computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+ /* computeHeight= */ true)
val (notificationsHeight, shelfHeightWithSpaceBefore) =
heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
heightPerMaxNotifications.last() // Height with all notifications visible.
}
- log {
- "computeHeight(maxNotifications=$maxNotifications," +
+ lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
"shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
"${notificationsHeight + shelfHeightWithSpaceBefore}" +
" = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
+ log {
+ lastComputeHeightLog
}
return notificationsHeight + shelfHeightWithSpaceBefore
}
@@ -184,7 +192,8 @@
private fun computeHeightPerNotificationLimit(
stack: NotificationStackScrollLayout,
- shelfHeight: Float
+ shelfHeight: Float,
+ computeHeight: Boolean
): Sequence<StackHeight> = sequence {
log { "computeHeightPerNotificationLimit" }
@@ -213,9 +222,14 @@
currentIndex = firstViewInShelfIndex)
spaceBeforeShelf + shelfHeight
}
+
+ val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
+ "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+ if (computeHeight) {
+ lastComputeHeightLog += "\n" + currentLog
+ }
log {
- "i=$i notificationsHeight=$notifications " +
- "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+ currentLog
}
yield(
StackHeight(
@@ -260,6 +274,10 @@
return size
}
+ fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+ }
+
private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
if (visibility == GONE || hasNoContentHeight()) return false
if (onLockscreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index eea1d911..d8c6878 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -57,11 +57,13 @@
private float mGapHeight;
private float mGapHeightOnLockscreen;
private int mCollapsedSize;
+ private boolean mEnableNotificationClipping;
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
private boolean mIsExpanded;
private boolean mClipNotificationScrollToTop;
- @VisibleForTesting float mHeadsUpInset;
+ @VisibleForTesting
+ float mHeadsUpInset;
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
private int mMarginBottom;
@@ -85,6 +87,7 @@
mPaddingBetweenElements = res.getDimensionPixelSize(
R.dimen.notification_divider_height);
mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop);
int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize(
@@ -289,7 +292,7 @@
// The bottom of this view is peeking out from under the previous view.
// Clip the part that is peeking out.
float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
- state.clipBottomAmount = (int) overlapAmount;
+ state.clipBottomAmount = mEnableNotificationClipping ? (int) overlapAmount : 0;
} else {
state.clipBottomAmount = 0;
}
@@ -454,7 +457,7 @@
/**
* @return Fraction to apply to view height and gap between views.
- * Does not include shelf height even if shelf is showing.
+ * Does not include shelf height even if shelf is showing.
*/
protected float getExpansionFractionWithoutShelf(
StackScrollAlgorithmState algorithmState,
@@ -468,7 +471,7 @@
&& (!ambientState.isBypassEnabled() || !ambientState.isPulseExpanding())
? 0 : mNotificationScrimPadding;
- final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding;
+ final float stackHeight = ambientState.getStackHeight() - shelfHeight - scrimPadding;
final float stackEndHeight = ambientState.getStackEndHeight() - shelfHeight - scrimPadding;
if (stackEndHeight == 0f) {
// This should not happen, since even when the shade is empty we show EmptyShadeView
@@ -502,13 +505,14 @@
}
// TODO(b/172289889) polish shade open from HUN
+
/**
* Populates the {@link ExpandableViewState} for a single child.
*
- * @param i The index of the child in
- * {@link StackScrollAlgorithmState#visibleChildren}.
- * @param algorithmState The overall output state of the algorithm.
- * @param ambientState The input state provided to the algorithm.
+ * @param i The index of the child in
+ * {@link StackScrollAlgorithmState#visibleChildren}.
+ * @param algorithmState The overall output state of the algorithm.
+ * @param ambientState The input state provided to the algorithm.
*/
protected void updateChild(
int i,
@@ -582,8 +586,8 @@
final float stackBottom = !ambientState.isShadeExpanded()
|| ambientState.getDozeAmount() == 1f
|| bypassPulseNotExpanding
- ? ambientState.getInnerHeight()
- : ambientState.getStackHeight();
+ ? ambientState.getInnerHeight()
+ : ambientState.getStackHeight();
final float shelfStart = stackBottom
- ambientState.getShelf().getIntrinsicHeight()
- mPaddingBetweenElements;
@@ -619,9 +623,9 @@
* Get the gap height needed for before a view
*
* @param sectionProvider the sectionProvider used to understand the sections
- * @param visibleIndex the visible index of this view in the list
- * @param child the child asked about
- * @param previousChild the child right before it or null if none
+ * @param visibleIndex the visible index of this view in the list
+ * @param child the child asked about
+ * @param previousChild the child right before it or null if none
* @return the size of the gap needed or 0 if none is needed
*/
public float getGapHeightForChild(
@@ -655,9 +659,9 @@
* Does a given child need a gap, i.e spacing before a view?
*
* @param sectionProvider the sectionProvider used to understand the sections
- * @param visibleIndex the visible index of this view in the list
- * @param child the child asked about
- * @param previousChild the child right before it or null if none
+ * @param visibleIndex the visible index of this view in the list
+ * @param child the child asked about
+ * @param previousChild the child right before it or null if none
* @return if the child needs a gap height
*/
private boolean childNeedsGapHeight(
@@ -860,30 +864,53 @@
}
}
+ /**
+ * Calculate and update the Z positions for a given child. We currently only give shadows to
+ * HUNs to distinguish a HUN from its surroundings.
+ *
+ * @param isTopHun Whether the child is a top HUN. A top HUN means a HUN that shows on the
+ * vertically top of screen. Top HUNs should have drop shadows
+ * @param childrenOnTop It is greater than 0 when there's an existing HUN that is elevated
+ * @return childrenOnTop The decimal part represents the fraction of the elevated HUN's height
+ * that overlaps with QQS Panel. The integer part represents the count of
+ * previous HUNs whose Z positions are greater than 0.
+ */
protected float updateChildZValue(int i, float childrenOnTop,
StackScrollAlgorithmState algorithmState,
AmbientState ambientState,
- boolean shouldElevateHun) {
+ boolean isTopHun) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
- int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
float baseZ = ambientState.getBaseZHeight();
+
+ // Handles HUN shadow when Shade is opened
+
if (child.mustStayOnScreen() && !childViewState.headsUpIsVisible
&& !ambientState.isDozingAndNotPulsing(child)
&& childViewState.getYTranslation() < ambientState.getTopPadding()
+ ambientState.getStackTranslation()) {
+ // Handles HUN shadow when Shade is opened, and AmbientState.mScrollY > 0
+ // Calculate the HUN's z-value based on its overlapping fraction with QQS Panel.
+ // When scrolling down shade to make HUN back to in-position in Notification Panel,
+ // The over-lapping fraction goes to 0, and shadows hides gradually.
if (childrenOnTop != 0.0f) {
+ // To elevate the later HUN over previous HUN
childrenOnTop++;
} else {
float overlap = ambientState.getTopPadding()
+ ambientState.getStackTranslation() - childViewState.getYTranslation();
- childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
+ // To prevent over-shadow during HUN entry
+ childrenOnTop += Math.min(
+ 1.0f,
+ overlap / childViewState.height
+ );
+ MathUtils.saturate(childrenOnTop);
}
childViewState.setZTranslation(baseZ
- + childrenOnTop * zDistanceBetweenElements);
- } else if (shouldElevateHun) {
+ + childrenOnTop * mPinnedZTranslationExtra);
+ } else if (isTopHun) {
// In case this is a new view that has never been measured before, we don't want to
- // elevate if we are currently expanded more then the notification
+ // elevate if we are currently expanded more than the notification
int shelfHeight = ambientState.getShelf() == null ? 0 :
ambientState.getShelf().getIntrinsicHeight();
float shelfStart = ambientState.getInnerHeight()
@@ -892,23 +919,28 @@
float notificationEnd = childViewState.getYTranslation() + child.getIntrinsicHeight()
+ mPaddingBetweenElements;
if (shelfStart > notificationEnd) {
+ // When the notification doesn't overlap with Notification Shelf, there's no shadow
childViewState.setZTranslation(baseZ);
} else {
+ // Give shadow to the notification if it overlaps with Notification Shelf
float factor = (notificationEnd - shelfStart) / shelfHeight;
if (Float.isNaN(factor)) { // Avoid problems when the above is 0/0.
factor = 1.0f;
}
factor = Math.min(factor, 1.0f);
- childViewState.setZTranslation(baseZ + factor * zDistanceBetweenElements);
+ childViewState.setZTranslation(baseZ + factor * mPinnedZTranslationExtra);
}
} else {
childViewState.setZTranslation(baseZ);
}
- // We need to scrim the notification more from its surrounding content when we are pinned,
- // and we therefore elevate it higher.
- // We can use the headerVisibleAmount for this, since the value nicely goes from 0 to 1 when
- // expanding after which we have a normal elevation again.
+ // Handles HUN shadow when shade is closed.
+ // While HUN is showing and Shade is closed: headerVisibleAmount stays 0, shadow stays.
+ // During HUN-to-Shade (eg. dragging down HUN to open Shade): headerVisibleAmount goes
+ // gradually from 0 to 1, shadow hides gradually.
+ // Header visibility is a deprecated concept, we are using headerVisibleAmount only because
+ // this value nicely goes from 0 to 1 during the HUN-to-Shade process.
+
childViewState.setZTranslation(childViewState.getZTranslation()
+ (1.0f - child.getHeaderVisibleAmount()) * mPinnedZTranslationExtra);
return childrenOnTop;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index f72f1bc..679bcea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -29,7 +29,6 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.Trace;
-import android.util.Log;
import androidx.annotation.Nullable;
@@ -41,10 +40,10 @@
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.logging.BiometricUnlockLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
@@ -79,9 +78,6 @@
*/
@SysUISingleton
public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable {
-
- private static final String TAG = "BiometricUnlockCtrl";
- private static final boolean DEBUG_BIO_WAKELOCK = KeyguardConstants.DEBUG_BIOMETRIC_WAKELOCK;
private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
@@ -176,6 +172,7 @@
private final StatusBarStateController mStatusBarStateController;
private final LatencyTracker mLatencyTracker;
private final VibratorHelper mVibratorHelper;
+ private final BiometricUnlockLogger mLogger;
private long mLastFpFailureUptimeMillis;
private int mNumConsecutiveFpFailures;
@@ -262,7 +259,8 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
@Inject
- public BiometricUnlockController(DozeScrimController dozeScrimController,
+ public BiometricUnlockController(
+ DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
ShadeController shadeController,
NotificationShadeWindowController notificationShadeWindowController,
@@ -272,6 +270,7 @@
KeyguardBypassController keyguardBypassController,
MetricsLogger metricsLogger, DumpManager dumpManager,
PowerManager powerManager,
+ BiometricUnlockLogger biometricUnlockLogger,
NotificationMediaManager notificationMediaManager,
WakefulnessLifecycle wakefulnessLifecycle,
ScreenLifecycle screenLifecycle,
@@ -308,6 +307,7 @@
mSessionTracker = sessionTracker;
mScreenOffAnimationController = screenOffAnimationController;
mVibratorHelper = vibrator;
+ mLogger = biometricUnlockLogger;
dumpManager.registerDumpable(getClass().getName(), this);
}
@@ -329,9 +329,7 @@
private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@Override
public void run() {
- if (DEBUG_BIO_WAKELOCK) {
- Log.i(TAG, "biometric wakelock: TIMEOUT!!");
- }
+ mLogger.i("biometric wakelock: TIMEOUT!!");
releaseBiometricWakeLock();
}
};
@@ -339,9 +337,7 @@
private void releaseBiometricWakeLock() {
if (mWakeLock != null) {
mHandler.removeCallbacks(mReleaseBiometricWakeLockRunnable);
- if (DEBUG_BIO_WAKELOCK) {
- Log.i(TAG, "releasing biometric wakelock");
- }
+ mLogger.i("releasing biometric wakelock");
mWakeLock.release();
mWakeLock = null;
}
@@ -372,9 +368,7 @@
Trace.beginSection("acquiring wake-and-unlock");
mWakeLock.acquire();
Trace.endSection();
- if (DEBUG_BIO_WAKELOCK) {
- Log.i(TAG, "biometric acquired, grabbing biometric wakelock");
- }
+ mLogger.i("biometric acquired, grabbing biometric wakelock");
mHandler.postDelayed(mReleaseBiometricWakeLockRunnable,
BIOMETRIC_WAKELOCK_TIMEOUT_MS);
}
@@ -411,7 +405,7 @@
mKeyguardViewMediator.userActivity();
startWakeAndUnlock(biometricSourceType, isStrongBiometric);
} else {
- Log.d(TAG, "onBiometricAuthenticated aborted by bypass controller");
+ mLogger.d("onBiometricAuthenticated aborted by bypass controller");
}
}
@@ -427,7 +421,7 @@
}
public void startWakeAndUnlock(@WakeAndUnlockMode int mode) {
- Log.v(TAG, "startWakeAndUnlock(" + mode + ")");
+ mLogger.logStartWakeAndUnlock(mode);
boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
mMode = mode;
mHasScreenTurnedOnSinceAuthenticating = false;
@@ -442,9 +436,7 @@
// brightness changes due to display state transitions.
Runnable wakeUp = ()-> {
if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
- if (DEBUG_BIO_WAKELOCK) {
- Log.i(TAG, "bio wakelock: Authenticated, waking up...");
- }
+ mLogger.i("bio wakelock: Authenticated, waking up...");
mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
"android.policy:BIOMETRIC");
}
@@ -537,13 +529,16 @@
}
private @WakeAndUnlockMode int calculateModeForFingerprint(boolean isStrongBiometric) {
- boolean unlockingAllowed =
+ final boolean unlockingAllowed =
mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
- boolean deviceDreaming = mUpdateMonitor.isDreaming();
+ final boolean deviceInteractive = mUpdateMonitor.isDeviceInteractive();
+ final boolean keyguardShowing = mKeyguardStateController.isShowing();
+ final boolean deviceDreaming = mUpdateMonitor.isDreaming();
- if (!mUpdateMonitor.isDeviceInteractive()) {
- if (!mKeyguardStateController.isShowing()
- && !mScreenOffAnimationController.isKeyguardShowDelayed()) {
+ logCalculateModeForFingerprint(unlockingAllowed, deviceInteractive,
+ keyguardShowing, deviceDreaming, isStrongBiometric);
+ if (!deviceInteractive) {
+ if (!keyguardShowing && !mScreenOffAnimationController.isKeyguardShowDelayed()) {
if (mKeyguardStateController.isUnlocked()) {
return MODE_WAKE_AND_UNLOCK;
}
@@ -559,7 +554,7 @@
if (unlockingAllowed && deviceDreaming) {
return MODE_WAKE_AND_UNLOCK_FROM_DREAM;
}
- if (mKeyguardStateController.isShowing()) {
+ if (keyguardShowing) {
if (mKeyguardViewController.primaryBouncerIsOrWillBeShowing() && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
} else if (unlockingAllowed) {
@@ -571,14 +566,39 @@
return MODE_NONE;
}
+ private void logCalculateModeForFingerprint(boolean unlockingAllowed, boolean deviceInteractive,
+ boolean keyguardShowing, boolean deviceDreaming, boolean strongBiometric) {
+ if (unlockingAllowed) {
+ mLogger.logCalculateModeForFingerprintUnlockingAllowed(deviceInteractive,
+ keyguardShowing, deviceDreaming);
+ } else {
+ // if unlocking isn't allowed, log more information about why unlocking may not
+ // have been allowed
+ final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
+ KeyguardUpdateMonitor.getCurrentUser());
+ final boolean nonStrongBiometricAllowed =
+ mUpdateMonitor.getStrongAuthTracker()
+ .isNonStrongBiometricAllowedAfterIdleTimeout(
+ KeyguardUpdateMonitor.getCurrentUser());
+
+ mLogger.logCalculateModeForFingerprintUnlockingNotAllowed(strongBiometric,
+ strongAuthFlags, nonStrongBiometricAllowed, deviceInteractive, keyguardShowing);
+ }
+ }
+
private @WakeAndUnlockMode int calculateModeForPassiveAuth(boolean isStrongBiometric) {
- boolean unlockingAllowed =
+ final boolean deviceInteractive = mUpdateMonitor.isDeviceInteractive();
+ final boolean isKeyguardShowing = mKeyguardStateController.isShowing();
+ final boolean unlockingAllowed =
mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
- boolean deviceDreaming = mUpdateMonitor.isDreaming();
- boolean bypass = mKeyguardBypassController.getBypassEnabled()
+ final boolean deviceDreaming = mUpdateMonitor.isDreaming();
+ final boolean bypass = mKeyguardBypassController.getBypassEnabled()
|| mAuthController.isUdfpsFingerDown();
- if (!mUpdateMonitor.isDeviceInteractive()) {
- if (!mKeyguardStateController.isShowing()) {
+
+ logCalculateModeForPassiveAuth(unlockingAllowed, deviceInteractive, isKeyguardShowing,
+ deviceDreaming, bypass, isStrongBiometric);
+ if (!deviceInteractive) {
+ if (!isKeyguardShowing) {
return bypass ? MODE_WAKE_AND_UNLOCK : MODE_ONLY_WAKE;
} else if (!unlockingAllowed) {
return bypass ? MODE_SHOW_BOUNCER : MODE_NONE;
@@ -602,11 +622,11 @@
if (unlockingAllowed && mKeyguardStateController.isOccluded()) {
return MODE_UNLOCK_COLLAPSING;
}
- if (mKeyguardStateController.isShowing()) {
+ if (isKeyguardShowing) {
if ((mKeyguardViewController.primaryBouncerIsOrWillBeShowing()
|| mKeyguardBypassController.getAltBouncerShowing()) && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
- } else if (unlockingAllowed && (bypass || mAuthController.isUdfpsFingerDown())) {
+ } else if (unlockingAllowed && bypass) {
return MODE_UNLOCK_COLLAPSING;
} else {
return bypass ? MODE_SHOW_BOUNCER : MODE_NONE;
@@ -615,6 +635,28 @@
return MODE_NONE;
}
+ private void logCalculateModeForPassiveAuth(boolean unlockingAllowed,
+ boolean deviceInteractive, boolean keyguardShowing, boolean deviceDreaming,
+ boolean bypass, boolean strongBiometric) {
+ if (unlockingAllowed) {
+ mLogger.logCalculateModeForPassiveAuthUnlockingAllowed(
+ deviceInteractive, keyguardShowing, deviceDreaming, bypass);
+ } else {
+ // if unlocking isn't allowed, log more information about why unlocking may not
+ // have been allowed
+ final int strongAuthFlags = mUpdateMonitor.getStrongAuthTracker().getStrongAuthForUser(
+ KeyguardUpdateMonitor.getCurrentUser());
+ final boolean nonStrongBiometricAllowed =
+ mUpdateMonitor.getStrongAuthTracker()
+ .isNonStrongBiometricAllowedAfterIdleTimeout(
+ KeyguardUpdateMonitor.getCurrentUser());
+
+ mLogger.logCalculateModeForPassiveAuthUnlockingNotAllowed(
+ strongBiometric, strongAuthFlags, nonStrongBiometricAllowed,
+ deviceInteractive, keyguardShowing, bypass);
+ }
+ }
+
@Override
public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
@@ -632,6 +674,7 @@
if (!mVibratorHelper.hasVibrator()
&& (!mUpdateMonitor.isDeviceInteractive() || mUpdateMonitor.isDreaming())) {
+ mLogger.d("wakeup device on authentication failure (device doesn't have a vibrator)");
startWakeAndUnlock(MODE_ONLY_WAKE);
} else if (biometricSourceType == BiometricSourceType.FINGERPRINT
&& mUpdateMonitor.isUdfpsSupported()) {
@@ -644,6 +687,7 @@
mLastFpFailureUptimeMillis = currUptimeMillis;
if (mNumConsecutiveFpFailures >= UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER) {
+ mLogger.logUdfpsAttemptThresholdMet(mNumConsecutiveFpFailures);
startWakeAndUnlock(MODE_SHOW_BOUNCER);
UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
mNumConsecutiveFpFailures = 0;
@@ -674,6 +718,7 @@
&& (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
|| msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT);
if (fingerprintLockout) {
+ mLogger.d("fingerprint locked out");
startWakeAndUnlock(MODE_SHOW_BOUNCER);
UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId());
}
@@ -763,6 +808,15 @@
}
@Override
+ public void onKeyguardBouncerStateChanged(boolean bouncerIsOrWillBeShowing) {
+ // When the bouncer is dismissed, treat this as a reset of the unlock mode. The user
+ // may have gone back instead of successfully unlocking
+ if (!bouncerIsOrWillBeShowing) {
+ resetMode();
+ }
+ }
+
+ @Override
public void dump(PrintWriter pw, String[] args) {
pw.println(" BiometricUnlockController:");
pw.print(" mMode="); pw.println(mMode);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index bd0678f..c6f64f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -51,10 +51,8 @@
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
-import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import java.io.PrintWriter;
@@ -192,8 +190,6 @@
void animateExpandSettingsPanel(@Nullable String subpanel);
- void animateCollapsePanels(int flags, boolean force);
-
void collapsePanelOnMainThread();
void togglePanel();
@@ -254,8 +250,6 @@
boolean isWakeUpComingFromTouch();
- boolean isFalsingThresholdNeeded();
-
void onKeyguardViewManagerStatesUpdated();
ViewGroup getNotificationScrollLayout();
@@ -283,8 +277,6 @@
void postAnimateOpenPanels();
- boolean isExpandedVisible();
-
boolean isPanelExpanded();
void onInputFocusTransfer(boolean start, boolean cancel, float velocity);
@@ -293,8 +285,6 @@
void onTouchEvent(MotionEvent event);
- GestureRecorder getGestureRecorder();
-
BiometricUnlockController getBiometricUnlockController();
void showWirelessChargingAnimation(int batteryLevel);
@@ -409,16 +399,6 @@
LightRevealScrim getLightRevealScrim();
- void onTrackingStarted();
-
- void onClosingFinished();
-
- void onUnlockHintStarted();
-
- void onHintFinished();
-
- void onTrackingStopped(boolean expand);
-
// TODO: Figure out way to remove these.
NavigationBarView getNavigationBarView();
@@ -500,16 +480,8 @@
boolean isKeyguardSecure();
- NotificationGutsManager getGutsManager();
-
void updateNotificationPanelTouchState();
- void makeExpandedVisible(boolean force);
-
- void instantCollapseNotificationPanel();
-
- void visibilityChanged(boolean visible);
-
int getDisplayId();
int getRotation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 9e5a66f..72ada0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -209,7 +209,7 @@
public void animateExpandNotificationsPanel() {
if (CentralSurfaces.SPEW) {
Log.d(CentralSurfaces.TAG,
- "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+ "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
}
if (!mCommandQueue.panelsEnabled()) {
return;
@@ -222,7 +222,7 @@
public void animateExpandSettingsPanel(@Nullable String subPanel) {
if (CentralSurfaces.SPEW) {
Log.d(CentralSurfaces.TAG,
- "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+ "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
}
if (!mCommandQueue.panelsEnabled()) {
return;
@@ -276,7 +276,7 @@
if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
}
@@ -293,7 +293,7 @@
if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
mCentralSurfaces.updateQsExpansionEnabled();
if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
}
@@ -550,7 +550,7 @@
@Override
public void togglePanel() {
if (mCentralSurfaces.isPanelExpanded()) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
} else {
animateExpandNotificationsPanel();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 4e0d7ac..00a9916 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -58,7 +58,6 @@
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -168,13 +167,13 @@
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
-import com.android.systemui.ripple.RippleShader.RippleShape;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shade.CameraLauncher;
@@ -184,7 +183,6 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CircleReveal;
@@ -234,6 +232,7 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
import com.android.systemui.util.DumpUtilsKt;
import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -281,6 +280,7 @@
// 1020-1040 reserved for BaseStatusBar
/**
+ * TODO(b/249277686) delete this
* The delay to reset the hint text when the hint animation is finished running.
*/
private static final int HINT_RESET_DELAY_MS = 1200;
@@ -422,12 +422,6 @@
/** */
@Override
- public void animateCollapsePanels(int flags, boolean force) {
- mCommandQueueCallbacks.animateCollapsePanels(flags, force);
- }
-
- /** */
- @Override
public void togglePanel() {
mCommandQueueCallbacks.togglePanel();
}
@@ -509,8 +503,6 @@
private View mReportRejectedTouch;
- private boolean mExpandedVisible;
-
private final NotificationGutsManager mGutsManager;
private final NotificationLogger mNotificationLogger;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -909,6 +901,8 @@
updateDisplaySize();
mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId);
+ initShadeVisibilityListener();
+
// start old BaseStatusBar.start().
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
@@ -993,6 +987,11 @@
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy.init();
+ // Based on teamfood flag, turn predictive back dispatch on at runtime.
+ if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
+ mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
+ }
+
mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
@@ -1094,6 +1093,25 @@
requestTopUi, componentTag))));
}
+ @VisibleForTesting
+ void initShadeVisibilityListener() {
+ mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
+ @Override
+ public void visibilityChanged(boolean visible) {
+ onShadeVisibilityChanged(visible);
+ }
+
+ @Override
+ public void expandedVisibleChanged(boolean expandedVisible) {
+ if (expandedVisible) {
+ setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
+ } else {
+ onExpandedInvisible();
+ }
+ }
+ });
+ }
+
private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) {
Trace.beginSection("CentralSurfaces#onFoldedStateChanged");
onFoldedStateChangedInternal(isFolded, willGoToSleep);
@@ -1148,7 +1166,6 @@
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationIconAreaController.setupShelf(mNotificationShelfController);
mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
- mUserSwitcherController.init(mNotificationShadeWindowView);
// Allow plugins to reference DarkIconDispatcher and StatusBarStateController
mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
@@ -1240,7 +1257,8 @@
mNotificationPanelViewController.initDependencies(
this,
- this::makeExpandedInvisible,
+ mGestureRec,
+ mShadeController::makeExpandedInvisible,
mNotificationShelfController);
BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
@@ -1443,6 +1461,7 @@
mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
+ mShadeController.setNotificationPresenter(mPresenter);
mNotificationsController.initialize(
this,
mPresenter,
@@ -1492,11 +1511,7 @@
return (v, event) -> {
mAutoHideController.checkUserAutoHide(event);
mRemoteInputManager.checkRemoteInputOutside(event);
- if (event.getAction() == MotionEvent.ACTION_UP) {
- if (mExpandedVisible) {
- mShadeController.animateCollapsePanels();
- }
- }
+ mShadeController.onStatusBarTouch(event);
return mNotificationShadeWindowView.onTouchEvent(event);
};
}
@@ -1518,6 +1533,9 @@
mNotificationShadeWindowViewController.setupExpandedStatusBar();
mNotificationPanelViewController =
mCentralSurfacesComponent.getNotificationPanelViewController();
+ mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+ mShadeController.setNotificationShadeWindowViewController(
+ mNotificationShadeWindowViewController);
mCentralSurfacesComponent.getLockIconViewController().init();
mStackScrollerController =
mCentralSurfacesComponent.getNotificationStackScrollLayoutController();
@@ -1803,11 +1821,6 @@
return mWakeUpComingFromTouch;
}
- @Override
- public boolean isFalsingThresholdNeeded() {
- return true;
- }
-
/**
* To be called when there's a state change in StatusBarKeyguardViewManager.
*/
@@ -1843,9 +1856,9 @@
public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing()
&& isLaunchForActivity) {
- onClosingFinished();
+ mShadeController.onClosingFinished();
} else {
- mShadeController.collapsePanel(true /* animate */);
+ mShadeController.collapseShade(true /* animate */);
}
}
@@ -1853,10 +1866,10 @@
@Override
public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
if (!mPresenter.isCollapsing()) {
- onClosingFinished();
+ mShadeController.onClosingFinished();
}
if (launchIsFullScreen) {
- instantCollapseNotificationPanel();
+ mShadeController.instantCollapseShade();
}
}
@@ -1948,33 +1961,13 @@
}
@Override
- public void makeExpandedVisible(boolean force) {
- if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
- if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
- return;
- }
-
- mExpandedVisible = true;
-
- // Expand the window to encompass the full screen in anticipation of the drag.
- // This is only possible to do atomically because the status bar is at the top of the screen!
- mNotificationShadeWindowController.setPanelVisible(true);
-
- visibilityChanged(true);
- mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
- setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
- }
-
- @Override
public void postAnimateCollapsePanels() {
- mMainExecutor.execute(mShadeController::animateCollapsePanels);
+ mMainExecutor.execute(mShadeController::animateCollapseShade);
}
@Override
public void postAnimateForceCollapsePanels() {
- mMainExecutor.execute(
- () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE,
- true /* force */));
+ mMainExecutor.execute(mShadeController::animateCollapseShadeForced);
}
@Override
@@ -1983,11 +1976,6 @@
}
@Override
- public boolean isExpandedVisible() {
- return mExpandedVisible;
- }
-
- @Override
public boolean isPanelExpanded() {
return mPanelExpanded;
}
@@ -2016,46 +2004,13 @@
}
}
- void makeExpandedInvisible() {
- if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
-
- if (!mExpandedVisible || mNotificationShadeWindowView == null) {
- return;
- }
-
- // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
- mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/,
- 1.0f /* speedUpFactor */);
-
- mNotificationPanelViewController.closeQs();
-
- mExpandedVisible = false;
- visibilityChanged(false);
-
- // Update the visibility of notification shade and status bar window.
- mNotificationShadeWindowController.setPanelVisible(false);
- mStatusBarWindowController.setForceStatusBarVisible(false);
-
- // Close any guts that might be visible
- mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
-
- mShadeController.runPostCollapseRunnables();
+ private void onExpandedInvisible() {
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) {
showBouncerOrLockScreenIfKeyguard();
} else if (DEBUG) {
Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen");
}
- mCommandQueue.recomputeDisableFlags(
- mDisplayId,
- mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
-
- // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
- // the bouncer appear animation.
- if (!mKeyguardStateController.isShowing()) {
- WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- }
}
/** Called when a touch event occurred on {@link PhoneStatusBarView}. */
@@ -2092,16 +2047,12 @@
final boolean upOrCancel =
event.getAction() == MotionEvent.ACTION_UP ||
event.getAction() == MotionEvent.ACTION_CANCEL;
- setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible);
+ setInteracting(StatusBarManager.WINDOW_STATUS_BAR,
+ !upOrCancel || mShadeController.isExpandedVisible());
}
}
@Override
- public GestureRecorder getGestureRecorder() {
- return mGestureRec;
- }
-
- @Override
public BiometricUnlockController getBiometricUnlockController() {
return mBiometricUnlockController;
}
@@ -2241,7 +2192,7 @@
IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
synchronized (mQueueLock) {
pw.println("Current Status Bar state:");
- pw.println(" mExpandedVisible=" + mExpandedVisible);
+ pw.println(" mExpandedVisible=" + mShadeController.isExpandedVisible());
pw.println(" mDisplayMetrics=" + mDisplayMetrics);
pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller));
pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller)
@@ -2556,10 +2507,8 @@
}
}
if (dismissShade) {
- if (mExpandedVisible && !mBouncerShowing) {
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed*/);
+ if (mShadeController.isExpandedVisible() && !mBouncerShowing) {
+ mShadeController.animateCollapseShadeDelayed();
} else {
// Do it after DismissAction has been processed to conserve the needed
// ordering.
@@ -2601,7 +2550,7 @@
flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL;
}
}
- mShadeController.animateCollapsePanels(flags);
+ mShadeController.animateCollapseShade(flags);
}
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
if (mNotificationShadeWindowController != null) {
@@ -2716,10 +2665,9 @@
com.android.systemui.R.dimen.physical_power_button_center_screen_location_y));
}
- // Visibility reporting
protected void handleVisibleToUserChanged(boolean visibleToUser) {
if (visibleToUser) {
- handleVisibleToUserChangedImpl(visibleToUser);
+ onVisibleToUser();
mNotificationLogger.startNotificationLogging();
if (!mIsBackCallbackRegistered) {
@@ -2736,7 +2684,7 @@
}
} else {
mNotificationLogger.stopNotificationLogging();
- handleVisibleToUserChangedImpl(visibleToUser);
+ onInvisibleToUser();
if (mIsBackCallbackRegistered) {
ViewRootImpl viewRootImpl = getViewRootImpl();
@@ -2756,41 +2704,38 @@
}
}
- // Visibility reporting
- void handleVisibleToUserChangedImpl(boolean visibleToUser) {
- if (visibleToUser) {
- /* The LEDs are turned off when the notification panel is shown, even just a little bit.
- * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
- * this.
- */
- boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
- boolean clearNotificationEffects =
- !mPresenter.isPresenterFullyCollapsed() &&
- (mState == StatusBarState.SHADE
- || mState == StatusBarState.SHADE_LOCKED);
- int notificationLoad = mNotificationsController.getActiveNotificationsCount();
- if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
- notificationLoad = 1;
- }
- final int finalNotificationLoad = notificationLoad;
- mUiBgExecutor.execute(() -> {
- try {
- mBarService.onPanelRevealed(clearNotificationEffects,
- finalNotificationLoad);
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- });
- } else {
- mUiBgExecutor.execute(() -> {
- try {
- mBarService.onPanelHidden();
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- });
+ void onVisibleToUser() {
+ /* The LEDs are turned off when the notification panel is shown, even just a little bit.
+ * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
+ * this.
+ */
+ boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+ boolean clearNotificationEffects =
+ !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE
+ || mState == StatusBarState.SHADE_LOCKED);
+ int notificationLoad = mNotificationsController.getActiveNotificationsCount();
+ if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
+ notificationLoad = 1;
}
+ final int finalNotificationLoad = notificationLoad;
+ mUiBgExecutor.execute(() -> {
+ try {
+ mBarService.onPanelRevealed(clearNotificationEffects,
+ finalNotificationLoad);
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ }
+ });
+ }
+ void onInvisibleToUser() {
+ mUiBgExecutor.execute(() -> {
+ try {
+ mBarService.onPanelHidden();
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ }
+ });
}
private void logStateToEventlog() {
@@ -2968,7 +2913,7 @@
private void updatePanelExpansionForKeyguard() {
if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
!= BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
- mShadeController.instantExpandNotificationsPanel();
+ mShadeController.instantExpandShade();
}
}
@@ -3087,7 +3032,7 @@
// too heavy for the CPU and GPU on any device.
mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay);
} else if (!mNotificationPanelViewController.isCollapsing()) {
- instantCollapseNotificationPanel();
+ mShadeController.instantCollapseShade();
}
// Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile
@@ -3245,8 +3190,7 @@
@Override
public boolean onMenuPressed() {
if (shouldUnlockOnMenuPressed()) {
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+ mShadeController.animateCollapseShadeForced();
return true;
}
return false;
@@ -3291,7 +3235,7 @@
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
&& !isBouncerShowingOverDream()) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
return true;
}
@@ -3301,8 +3245,7 @@
@Override
public boolean onSpacePressed() {
if (mDeviceInteractive && mState != StatusBarState.SHADE) {
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+ mShadeController.animateCollapseShadeForced();
return true;
}
return false;
@@ -3341,12 +3284,6 @@
}
}
- @Override
- public void instantCollapseNotificationPanel() {
- mNotificationPanelViewController.instantCollapse();
- mShadeController.runPostCollapseRunnables();
- }
-
/**
* Collapse the panel directly if we are on the main thread, post the collapsing on the main
* thread if we are not.
@@ -3354,9 +3291,9 @@
@Override
public void collapsePanelOnMainThread() {
if (Looper.getMainLooper().isCurrentThread()) {
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
} else {
- mContext.getMainExecutor().execute(mShadeController::collapsePanel);
+ mContext.getMainExecutor().execute(mShadeController::collapseShade);
}
}
@@ -3397,37 +3334,6 @@
return mLightRevealScrim;
}
- @Override
- public void onTrackingStarted() {
- mShadeController.runPostCollapseRunnables();
- }
-
- @Override
- public void onClosingFinished() {
- mShadeController.runPostCollapseRunnables();
- if (!mPresenter.isPresenterFullyCollapsed()) {
- // if we set it not to be focusable when collapsing, we have to undo it when we aborted
- // the closing
- mNotificationShadeWindowController.setNotificationShadeFocusable(true);
- }
- }
-
- @Override
- public void onUnlockHintStarted() {
- mFalsingCollector.onUnlockHintStarted();
- mKeyguardIndicationController.showActionToUnlock();
- }
-
- @Override
- public void onHintFinished() {
- // Delay the reset a bit so the user can read the text.
- mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
- }
-
- @Override
- public void onTrackingStopped(boolean expand) {
- }
-
// TODO: Figure out way to remove these.
@Override
public NavigationBarView getNavigationBarView() {
@@ -3512,7 +3418,7 @@
mNotificationShadeWindowViewController.cancelCurrentTouch();
}
if (mPanelExpanded && mState == StatusBarState.SHADE) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
}
@@ -3575,7 +3481,7 @@
// The unlocked screen off and fold to aod animations might use our LightRevealScrim -
// we need to be expanded for it to be visible.
if (mDozeParameters.shouldShowLightRevealScrim()) {
- makeExpandedVisible(true);
+ mShadeController.makeExpandedVisible(true);
}
DejankUtils.stopDetectingBlockingIpcs(tag);
@@ -3604,7 +3510,7 @@
// If we are waking up during the screen off animation, we should undo making the
// expanded visible (we did that so the LightRevealScrim would be visible).
if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
- makeExpandedInvisible();
+ mShadeController.makeExpandedInvisible();
}
});
@@ -3939,8 +3845,7 @@
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
if (BANNER_ACTION_SETUP.equals(action)) {
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */);
+ mShadeController.animateCollapseShadeForced();
mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -4002,7 +3907,7 @@
action.run();
}).start();
- return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard;
+ return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard;
}
@Override
@@ -4097,8 +4002,7 @@
mMainExecutor.execute(runnable);
}
- @Override
- public void visibilityChanged(boolean visible) {
+ private void onShadeVisibilityChanged(boolean visible) {
if (mVisible != visible) {
mVisible = visible;
if (!visible) {
@@ -4158,11 +4062,6 @@
// End Extra BaseStatusBarMethods.
- @Override
- public NotificationGutsManager getGutsManager() {
- return mGutsManager;
- }
-
boolean isTransientShown() {
return mTransientShown;
}
@@ -4285,7 +4184,6 @@
}
// TODO: Bring these out of CentralSurfaces.
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
- mUserSwitcherController.onDensityOrFontScaleChanged();
mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
mHeadsUpManager.onDensityOrFontScaleChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 34cd1ce..7dcdc0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -33,7 +33,7 @@
private val lastConfig = Configuration()
private var density: Int = 0
private var smallestScreenWidth: Int = 0
- private var maxBounds: Rect? = null
+ private var maxBounds = Rect()
private var fontScale: Float = 0.toFloat()
private val inCarMode: Boolean
private var uiMode: Int = 0
@@ -47,6 +47,7 @@
fontScale = currentConfig.fontScale
density = currentConfig.densityDpi
smallestScreenWidth = currentConfig.smallestScreenWidthDp
+ maxBounds.set(currentConfig.windowConfiguration.maxBounds)
inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
Configuration.UI_MODE_TYPE_CAR
uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
@@ -92,7 +93,11 @@
val maxBounds = newConfig.windowConfiguration.maxBounds
if (maxBounds != this.maxBounds) {
- this.maxBounds = maxBounds
+ // Update our internal rect to have the same bounds, instead of using
+ // `this.maxBounds = maxBounds` directly. Setting it directly means that `maxBounds`
+ // would be a direct reference to windowConfiguration.maxBounds, so the if statement
+ // above would always fail. See b/245799099 for more information.
+ this.maxBounds.set(maxBounds)
listeners.filterForEach({ this.listeners.contains(it) }) {
it.onMaxBoundsChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 5f8da41..000fe14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -240,8 +240,8 @@
&& !mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
KeyguardUpdateMonitor.getCurrentUser())
&& !needsFullscreenBouncer()
- && !mKeyguardUpdateMonitor.isFaceLockedOut()
- && !mKeyguardUpdateMonitor.userNeedsStrongAuth()
+ && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE)
&& !mKeyguardBypassController.getBypassEnabled()) {
mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
} else {
@@ -267,7 +267,7 @@
private void onFullyShown() {
mFalsingCollector.onBouncerShown();
if (mKeyguardViewController == null) {
- Log.wtf(TAG, "onFullyShown when view was null");
+ Log.e(TAG, "onFullyShown when view was null");
} else {
mKeyguardViewController.onResume();
mContainer.announceForAccessibility(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 18877f9..13566ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -26,6 +26,7 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue;
@@ -47,6 +48,9 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -69,7 +73,7 @@
private ImageView mMultiUserAvatar;
private BatteryMeterView mBatteryView;
private StatusIconContainer mStatusIconContainer;
- private ViewGroup mUserSwitcherContainer;
+ private StatusBarUserSwitcherContainer mUserSwitcherContainer;
private boolean mKeyguardUserSwitcherEnabled;
private boolean mKeyguardUserAvatarEnabled;
@@ -120,8 +124,12 @@
loadDimens();
}
- public ViewGroup getUserSwitcherContainer() {
- return mUserSwitcherContainer;
+ /**
+ * Should only be called from {@link KeyguardStatusBarViewController}
+ * @param viewModel view model for the status bar user chip
+ */
+ void init(StatusBarUserChipViewModel viewModel) {
+ StatusBarUserChipViewBinder.bind(mUserSwitcherContainer, viewModel);
}
@Override
@@ -303,10 +311,7 @@
lp = (LayoutParams) mStatusIconArea.getLayoutParams();
lp.removeRule(RelativeLayout.RIGHT_OF);
lp.width = LayoutParams.WRAP_CONTENT;
-
- LinearLayout.LayoutParams llp =
- (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
- llp.setMarginStart(getResources().getDimensionPixelSize(
+ lp.setMarginStart(getResources().getDimensionPixelSize(
R.dimen.system_icons_super_container_margin_start));
return true;
}
@@ -338,10 +343,7 @@
lp = (LayoutParams) mStatusIconArea.getLayoutParams();
lp.addRule(RelativeLayout.RIGHT_OF, R.id.cutout_space_view);
lp.width = LayoutParams.MATCH_PARENT;
-
- LinearLayout.LayoutParams llp =
- (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
- llp.setMarginStart(0);
+ lp.setMarginStart(0);
return true;
}
@@ -527,4 +529,11 @@
mClipRect.set(0, mTopClipping, getWidth(), getHeight());
setClipBounds(mClipRect);
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ Trace.beginSection("KeyguardStatusBarView#onMeasure");
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ Trace.endSection();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 14cebf4..d4dc1dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -59,13 +59,11 @@
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.fragment.StatusBarIconBlocklistKt;
import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventAnimator;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
@@ -110,9 +108,7 @@
private final SysuiStatusBarStateController mStatusBarStateController;
private final StatusBarContentInsetsProvider mInsetsProvider;
private final UserManager mUserManager;
- private final StatusBarUserSwitcherFeatureController mFeatureController;
- private final StatusBarUserSwitcherController mUserSwitcherController;
- private final StatusBarUserInfoTracker mStatusBarUserInfoTracker;
+ private final StatusBarUserChipViewModel mStatusBarUserChipViewModel;
private final SecureSettings mSecureSettings;
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
@@ -276,9 +272,7 @@
SysuiStatusBarStateController statusBarStateController,
StatusBarContentInsetsProvider statusBarContentInsetsProvider,
UserManager userManager,
- StatusBarUserSwitcherFeatureController featureController,
- StatusBarUserSwitcherController userSwitcherController,
- StatusBarUserInfoTracker statusBarUserInfoTracker,
+ StatusBarUserChipViewModel userChipViewModel,
SecureSettings secureSettings,
CommandQueue commandQueue,
@Main Executor mainExecutor,
@@ -301,9 +295,7 @@
mStatusBarStateController = statusBarStateController;
mInsetsProvider = statusBarContentInsetsProvider;
mUserManager = userManager;
- mFeatureController = featureController;
- mUserSwitcherController = userSwitcherController;
- mStatusBarUserInfoTracker = statusBarUserInfoTracker;
+ mStatusBarUserChipViewModel = userChipViewModel;
mSecureSettings = secureSettings;
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
@@ -328,8 +320,7 @@
R.dimen.header_notifications_collide_distance);
mView.setKeyguardUserAvatarEnabled(
- !mFeatureController.isStatusBarUserSwitcherFeatureEnabled());
- mFeatureController.addCallback(enabled -> mView.setKeyguardUserAvatarEnabled(!enabled));
+ !mStatusBarUserChipViewModel.getChipEnabled());
mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, r);
mDisableStateTracker = new DisableStateTracker(
@@ -344,11 +335,11 @@
super.onInit();
mCarrierTextController.init();
mBatteryMeterViewController.init();
- mUserSwitcherController.init();
}
@Override
protected void onViewAttached() {
+ mView.init(mStatusBarUserChipViewModel);
mConfigurationController.addCallback(mConfigurationListener);
mAnimationScheduler.addCallback(mAnimationCallback);
mUserInfoController.addCallback(mOnUserInfoChangedListener);
@@ -394,9 +385,6 @@
/** Sets whether user switcher is enabled. */
public void setKeyguardUserSwitcherEnabled(boolean enabled) {
mView.setKeyguardUserSwitcherEnabled(enabled);
- // We don't have a listener for when the user switcher setting changes, so this is
- // where we re-check the state
- mStatusBarUserInfoTracker.checkEnabled();
}
/** Sets whether this controller should listen to battery updates. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
index 4496607..3989854 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -62,7 +62,7 @@
private var statusBarBoundsProvider: StatusBarBoundsProvider? = null
override fun start() {
- dumpManager.registerDumpable(javaClass.simpleName) { printWriter, _ -> dump(printWriter) }
+ dumpManager.registerCriticalDumpable(javaClass.simpleName) { pw, _ -> dump(pw) }
}
override fun stop() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 8793a57..1d7dfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
import android.app.WallpaperColors;
@@ -45,6 +44,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
import libcore.io.IoUtils;
@@ -82,10 +82,11 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
DumpManager dumpManager,
NotificationMediaManager mediaManager,
- @Main Handler mainHandler) {
+ @Main Handler mainHandler,
+ UserTracker userTracker) {
dumpManager.registerDumpable(getClass().getSimpleName(), this);
mWallpaperManager = wallpaperManager;
- mCurrentUserId = ActivityManager.getCurrentUser();
+ mCurrentUserId = userTracker.getUserId();
mUpdateMonitor = keyguardUpdateMonitor;
mMediaManager = mediaManager;
mH = mainHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 94d1bf4..4beb87d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -14,24 +14,22 @@
package com.android.systemui.statusbar.phone;
-import android.app.ActivityManager;
import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.annotation.NonNull;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -43,8 +41,9 @@
private final List<Callback> mCallbacks = new ArrayList<>();
private final Context mContext;
+ private final Executor mMainExecutor;
private final UserManager mUserManager;
- private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker mUserTracker;
private final LinkedList<UserInfo> mProfiles;
private boolean mListening;
private int mCurrentUser;
@@ -52,10 +51,12 @@
/**
*/
@Inject
- public ManagedProfileControllerImpl(Context context, BroadcastDispatcher broadcastDispatcher) {
+ public ManagedProfileControllerImpl(Context context, @Main Executor mainExecutor,
+ UserTracker userTracker) {
mContext = context;
+ mMainExecutor = mainExecutor;
mUserManager = UserManager.get(mContext);
- mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
mProfiles = new LinkedList<UserInfo>();
}
@@ -90,7 +91,7 @@
private void reloadManagedProfiles() {
synchronized (mProfiles) {
boolean hadProfile = mProfiles.size() > 0;
- int user = ActivityManager.getCurrentUser();
+ int user = mUserTracker.getUserId();
mProfiles.clear();
for (UserInfo ui : mUserManager.getEnabledProfiles(user)) {
@@ -127,30 +128,34 @@
}
private void setListening(boolean listening) {
+ if (mListening == listening) {
+ return;
+ }
mListening = listening;
if (listening) {
reloadManagedProfiles();
-
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
- mBroadcastDispatcher.registerReceiver(
- mReceiver, filter, null /* handler */, UserHandle.ALL);
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
} else {
- mBroadcastDispatcher.unregisterReceiver(mReceiver);
+ mUserTracker.removeCallback(mUserChangedCallback);
}
}
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- reloadManagedProfiles();
- for (Callback callback : mCallbacks) {
- callback.onManagedProfileChanged();
- }
- }
- };
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ reloadManagedProfiles();
+ for (Callback callback : mCallbacks) {
+ callback.onManagedProfileChanged();
+ }
+ }
+
+ @Override
+ public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
+ reloadManagedProfiles();
+ for (Callback callback : mCallbacks) {
+ callback.onManagedProfileChanged();
+ }
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
index 3811689..4fe03017 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
@@ -25,7 +25,7 @@
import com.android.systemui.plugins.NotificationListenerController;
import com.android.systemui.plugins.NotificationListenerController.NotificationProvider;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 7aeb08d..28bc64d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -38,6 +38,9 @@
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.leak.RotationUtils;
import java.util.Objects;
@@ -73,6 +76,11 @@
mTouchEventHandler = handler;
}
+ void init(StatusBarUserChipViewModel viewModel) {
+ StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container);
+ StatusBarUserChipViewBinder.bind(container, viewModel);
+ }
+
@Override
public void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index f9c4c8f..a6c2b2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -23,11 +23,11 @@
import android.view.ViewTreeObserver
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UNFOLD_STATUS_BAR
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.ViewController
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.view.ViewUtil
@@ -40,7 +40,7 @@
view: PhoneStatusBarView,
@Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
- private val userSwitcherController: StatusBarUserSwitcherController,
+ private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
touchEventHandler: PhoneStatusBarView.TouchEventHandler,
private val configurationController: ConfigurationController
@@ -91,10 +91,10 @@
init {
mView.setTouchEventHandler(touchEventHandler)
+ mView.init(userChipViewModel)
}
override fun onInit() {
- userSwitcherController.init()
}
fun setImportantForAccessibility(mode: Int) {
@@ -156,9 +156,9 @@
private val unfoldComponent: Optional<SysUIUnfoldComponent>,
@Named(UNFOLD_STATUS_BAR)
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
- private val userSwitcherController: StatusBarUserSwitcherController,
+ private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
- private val configurationController: ConfigurationController
+ private val configurationController: ConfigurationController,
) {
fun create(
view: PhoneStatusBarView,
@@ -168,7 +168,7 @@
view,
progressProvider.getOrNull(),
unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
- userSwitcherController,
+ userChipViewModel,
viewUtil,
touchEventHandler,
configurationController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d54a863..d500f99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -53,7 +53,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -111,6 +110,12 @@
private boolean mClipsQsScrim;
/**
+ * Whether an activity is launching over the lockscreen. During the launch animation, we want to
+ * delay certain scrim changes until after the animation ends.
+ */
+ private boolean mOccludeAnimationPlaying = false;
+
+ /**
* The amount of progress we are currently in if we're transitioning to the full shade.
* 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
* shade.
@@ -205,7 +210,6 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private KeyguardViewMediator mKeyguardViewMediator;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
@@ -275,8 +279,7 @@
@Main Executor mainExecutor,
ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- KeyguardViewMediator keyguardViewMediator) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
mScrimStateListener = lightBarController::setScrimState;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
@@ -315,8 +318,6 @@
}
});
mColors = new GradientColors();
-
- mKeyguardViewMediator = keyguardViewMediator;
}
/**
@@ -351,6 +352,11 @@
.getBoolean(R.bool.notification_scrim_transparent);
updateScrims();
mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
+
+ // prepare() sets proper initial values for most states
+ for (ScrimState state : ScrimState.values()) {
+ state.prepare(state);
+ }
}
/**
@@ -640,10 +646,6 @@
private void setTransitionToFullShade(boolean transitioning) {
if (transitioning != mTransitioningToFullShade) {
mTransitioningToFullShade = transitioning;
- if (transitioning) {
- // Let's make sure the shade locked is ready
- ScrimState.SHADE_LOCKED.prepare(mState);
- }
}
}
@@ -738,6 +740,11 @@
return mClipsQsScrim;
}
+ public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) {
+ mOccludeAnimationPlaying = occludeAnimationPlaying;
+ applyAndDispatchState();
+ }
+
private void setOrAdaptCurrentAnimation(@Nullable View scrim) {
if (scrim == null) {
return;
@@ -777,11 +784,15 @@
}
if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) {
- // Darken scrim as you pull down the shade when unlocked, unless the shade is expanding
- // because we're doing the screen off animation OR the shade is collapsing because
- // we're playing the unlock animation
+ final boolean occluding =
+ mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview;
+
+ // Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the
+ // screen off/occlusion animations, ignore expansion changes while those animations
+ // play.
if (!mScreenOffAnimationController.shouldExpandNotifications()
- && !mAnimatingPanelExpansionOnUnlock) {
+ && !mAnimatingPanelExpansionOnUnlock
+ && !occluding) {
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
if (mClipsQsScrim) {
@@ -812,13 +823,6 @@
mBehindTint,
interpolatedFraction);
}
-
- // If we're unlocked but still playing the occlude animation, remain at the keyguard
- // alpha temporarily.
- if (mKeyguardViewMediator.isOccludeAnimationPlaying()
- || mState.mLaunchingAffordanceWithPreview) {
- mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
- }
} else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
mNotificationsAlpha = (float) Math.pow(getInterpolatedFraction(), 0.8f);
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 52430d3..0e9d3ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -146,18 +146,12 @@
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mNotifAlpha = 1f;
mFrontAlpha = 0f;
- mBehindTint = Color.BLACK;
+ mBehindTint = mClipQsScrim ? Color.TRANSPARENT : Color.BLACK;
if (mClipQsScrim) {
updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
}
}
-
- // to make sure correct color is returned before "prepare" is called
- @Override
- public int getBehindTint() {
- return Color.BLACK;
- }
},
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 5512bed..3d6bebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -72,9 +72,9 @@
//resize the layout. Let's
// make sure that the window stays small for one frame until the
//touchableRegion is set.
- mNotificationPanelViewController.getView().requestLayout();
+ mNotificationPanelViewController.requestLayoutOnView();
mNotificationShadeWindowController.setForceWindowCollapsed(true);
- mNotificationPanelViewController.getView().post(() -> {
+ mNotificationPanelViewController.postToView(() -> {
mNotificationShadeWindowController.setForceWindowCollapsed(false);
});
}
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 045173e..3b160c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -20,6 +20,7 @@
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER;
+import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_SHOW_BOUNCER;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_UNLOCK_COLLAPSING;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
@@ -77,7 +78,6 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
import com.android.systemui.statusbar.phone.KeyguardBouncer.PrimaryBouncerExpansionCallback;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -218,7 +218,7 @@
protected LockPatternUtils mLockPatternUtils;
protected ViewMediatorCallback mViewMediatorCallback;
- protected CentralSurfaces mCentralSurfaces;
+ @Nullable protected CentralSurfaces mCentralSurfaces;
private NotificationPanelViewController mNotificationPanelViewController;
private BiometricUnlockController mBiometricUnlockController;
private boolean mCentralSurfacesRegistered;
@@ -266,7 +266,7 @@
private final KeyguardUpdateMonitor mKeyguardUpdateManager;
private final LatencyTracker mLatencyTracker;
private final KeyguardSecurityModel mKeyguardSecurityModel;
- private KeyguardBypassController mBypassController;
+ @Nullable private KeyguardBypassController mBypassController;
@Nullable private AlternateBouncer mAlternateBouncer;
private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
@@ -469,15 +469,19 @@
// Don't expand to the bouncer. Instead transition back to the lock screen (see
// CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
return;
- } else if (primaryBouncerNeedsScrimming()) {
+ } else if (mKeyguardStateController.isOccluded()
+ && !mDreamOverlayStateController.isOverlayActive()) {
+ return;
+ } else if (needsFullscreenBouncer()) {
if (mPrimaryBouncer != null) {
mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else {
mPrimaryBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
- } else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
+ } else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
+ && !(mBiometricUnlockController.getMode() == MODE_SHOW_BOUNCER)
&& !isUnlockCollapsing()) {
if (mPrimaryBouncer != null) {
mPrimaryBouncer.setExpansion(fraction);
@@ -742,6 +746,12 @@
}
private void updateAlternateBouncerShowing(boolean updateScrim) {
+ if (!mCentralSurfacesRegistered) {
+ // if CentralSurfaces hasn't been registered yet, then the controllers below haven't
+ // been initialized yet so there's no need to attempt to forward them events.
+ return;
+ }
+
final boolean isShowingAlternateBouncer = isShowingAlternateBouncer();
if (mKeyguardMessageAreaController != null) {
mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
@@ -1009,7 +1019,7 @@
public void onKeyguardFadedAway() {
mNotificationContainer.postDelayed(() -> mNotificationShadeWindowController
.setKeyguardFadingAway(false), 100);
- ViewGroupFadeHelper.reset(mNotificationPanelViewController.getView());
+ mNotificationPanelViewController.resetViewAlphas();
mCentralSurfaces.finishKeyguardFadingAway();
mBiometricUnlockController.finishKeyguardFadingAway();
WindowManagerGlobal.getInstance().trimMemory(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index b6ae4a0..05bf860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -260,11 +260,11 @@
if (showOverLockscreen) {
mShadeController.addPostCollapseAction(runnable);
- mShadeController.collapsePanel(true /* animate */);
+ mShadeController.collapseShade(true /* animate */);
} else if (mKeyguardStateController.isShowing()
&& mCentralSurfaces.isOccluded()) {
mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
} else {
runnable.run();
}
@@ -406,7 +406,7 @@
private void expandBubbleStack(NotificationEntry entry) {
mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
}
private void startNotificationIntent(
@@ -593,9 +593,9 @@
private void collapseOnMainThread() {
if (Looper.getMainLooper().isCurrentThread()) {
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
} else {
- mMainThreadHandler.post(mShadeController::collapsePanel);
+ mMainThreadHandler.post(mShadeController::collapseShade);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 8a49850..7fe01825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -180,7 +180,7 @@
}
};
mShadeController.postOnShadeExpanded(clickPendingViewRunnable);
- mShadeController.instantExpandNotificationsPanel();
+ mShadeController.instantExpandShade();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index dc90266..206c0aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -28,13 +28,13 @@
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.biometrics.AuthRippleView;
-import com.android.systemui.broadcast.BroadcastDispatcher;
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.statusbar.StatusBarStateController;
import com.android.systemui.privacy.OngoingPrivacyChip;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CombinedShadeHeadersConstraintManager;
import com.android.systemui.shade.CombinedShadeHeadersConstraintManagerImpl;
import com.android.systemui.shade.NotificationPanelView;
@@ -221,20 +221,22 @@
@Named(LARGE_SCREEN_BATTERY_CONTROLLER)
static BatteryMeterViewController getBatteryMeterViewController(
@Named(SPLIT_SHADE_BATTERY_VIEW) BatteryMeterView batteryMeterView,
+ UserTracker userTracker,
ConfigurationController configurationController,
TunerService tunerService,
- BroadcastDispatcher broadcastDispatcher,
@Main Handler mainHandler,
ContentResolver contentResolver,
+ FeatureFlags featureFlags,
BatteryController batteryController
) {
return new BatteryMeterViewController(
batteryMeterView,
+ userTracker,
configurationController,
tunerService,
- broadcastDispatcher,
mainHandler,
contentResolver,
+ featureFlags,
batteryController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 41f1f95..efec270 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -29,8 +29,6 @@
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -39,7 +37,6 @@
import javax.inject.Named;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.Multibinds;
@@ -126,12 +123,6 @@
}
/** */
- @Binds
- @StatusBarFragmentScope
- StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
- StatusBarUserSwitcherControllerImpl controller);
-
- /** */
@Provides
@StatusBarFragmentScope
static PhoneStatusBarViewController providePhoneStatusBarViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
deleted file mode 100644
index f6b8cb0..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.userswitcher
-
-import android.graphics.drawable.Drawable
-import android.os.UserManager
-import com.android.systemui.Dumpable
-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.statusbar.policy.CallbackController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
-import java.io.PrintWriter
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/**
- * Since every user switcher chip will user the exact same information and logic on whether or not
- * to show, and what data to show, it makes sense to create a single tracker here
- */
-@SysUISingleton
-class StatusBarUserInfoTracker @Inject constructor(
- private val userInfoController: UserInfoController,
- private val userManager: UserManager,
- private val dumpManager: DumpManager,
- @Main private val mainExecutor: Executor,
- @Background private val backgroundExecutor: Executor
-) : CallbackController<CurrentUserChipInfoUpdatedListener>, Dumpable {
- var currentUserName: String? = null
- private set
- var currentUserAvatar: Drawable? = null
- private set
- var userSwitcherEnabled = false
- private set
- private var listening = false
-
- private val listeners = mutableListOf<CurrentUserChipInfoUpdatedListener>()
-
- private val userInfoChangedListener = OnUserInfoChangedListener { name, picture, _ ->
- currentUserAvatar = picture
- currentUserName = name
- notifyListenersUserInfoChanged()
- }
-
- init {
- dumpManager.registerDumpable(TAG, this)
- }
-
- override fun addCallback(listener: CurrentUserChipInfoUpdatedListener) {
- if (listeners.isEmpty()) {
- startListening()
- }
-
- if (!listeners.contains(listener)) {
- listeners.add(listener)
- }
- }
-
- override fun removeCallback(listener: CurrentUserChipInfoUpdatedListener) {
- listeners.remove(listener)
-
- if (listeners.isEmpty()) {
- stopListening()
- }
- }
-
- private fun notifyListenersUserInfoChanged() {
- listeners.forEach {
- it.onCurrentUserChipInfoUpdated()
- }
- }
-
- private fun notifyListenersSettingChanged() {
- listeners.forEach {
- it.onStatusBarUserSwitcherSettingChanged(userSwitcherEnabled)
- }
- }
-
- private fun startListening() {
- listening = true
- userInfoController.addCallback(userInfoChangedListener)
- }
-
- private fun stopListening() {
- listening = false
- userInfoController.removeCallback(userInfoChangedListener)
- }
-
- /**
- * Force a check to [UserManager.isUserSwitcherEnabled], and update listeners if the value has
- * changed
- */
- fun checkEnabled() {
- backgroundExecutor.execute {
- // Check on a background thread to avoid main thread Binder calls
- val wasEnabled = userSwitcherEnabled
- userSwitcherEnabled = userManager.isUserSwitcherEnabled
-
- if (wasEnabled != userSwitcherEnabled) {
- mainExecutor.execute {
- notifyListenersSettingChanged()
- }
- }
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println(" userSwitcherEnabled=$userSwitcherEnabled")
- pw.println(" listening=$listening")
- }
-}
-
-interface CurrentUserChipInfoUpdatedListener {
- fun onCurrentUserChipInfoUpdated()
- fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {}
-}
-
-private const val TAG = "StatusBarUserInfoTracker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
deleted file mode 100644
index e498ae4..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.view.View
-import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.UserSwitcherActivity
-import com.android.systemui.util.ViewController
-
-import javax.inject.Inject
-
-/**
- * ViewController for [StatusBarUserSwitcherContainer]
- */
-class StatusBarUserSwitcherControllerImpl @Inject constructor(
- view: StatusBarUserSwitcherContainer,
- private val tracker: StatusBarUserInfoTracker,
- private val featureController: StatusBarUserSwitcherFeatureController,
- private val userSwitcherDialogController: UserSwitchDialogController,
- private val featureFlags: FeatureFlags,
- private val activityStarter: ActivityStarter,
- private val falsingManager: FalsingManager
-) : ViewController<StatusBarUserSwitcherContainer>(view),
- StatusBarUserSwitcherController {
- private val listener = object : CurrentUserChipInfoUpdatedListener {
- override fun onCurrentUserChipInfoUpdated() {
- updateChip()
- }
-
- override fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {
- updateEnabled()
- }
- }
-
- private val featureFlagListener = object : OnUserSwitcherPreferenceChangeListener {
- override fun onUserSwitcherPreferenceChange(enabled: Boolean) {
- updateEnabled()
- }
- }
-
- public override fun onViewAttached() {
- tracker.addCallback(listener)
- featureController.addCallback(featureFlagListener)
- mView.setOnClickListener { view: View ->
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return@setOnClickListener
- }
-
- if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- val intent = Intent(context, UserSwitcherActivity::class.java)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
-
- activityStarter.startActivity(intent, true /* dismissShade */,
- null /* ActivityLaunchAnimator.Controller */,
- true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM)
- } else {
- userSwitcherDialogController.showDialog(view.context, Expandable.fromView(view))
- }
- }
-
- updateEnabled()
- }
-
- override fun onViewDetached() {
- tracker.removeCallback(listener)
- featureController.removeCallback(featureFlagListener)
- mView.setOnClickListener(null)
- }
-
- private fun updateChip() {
- mView.text.text = tracker.currentUserName
- mView.avatar.setImageDrawable(tracker.currentUserAvatar)
- }
-
- private fun updateEnabled() {
- if (featureController.isStatusBarUserSwitcherFeatureEnabled() &&
- tracker.userSwitcherEnabled) {
- mView.visibility = View.VISIBLE
- updateChip()
- } else {
- mView.visibility = View.GONE
- }
- }
-}
-
-interface StatusBarUserSwitcherController {
- fun init()
-}
-
-private const val TAG = "SbUserSwitcherController"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
deleted file mode 100644
index 7bae9ff..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.userswitcher
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.policy.CallbackController
-
-import javax.inject.Inject
-
-@SysUISingleton
-class StatusBarUserSwitcherFeatureController @Inject constructor(
- private val flags: FeatureFlags
-) : CallbackController<OnUserSwitcherPreferenceChangeListener> {
- private val listeners = mutableListOf<OnUserSwitcherPreferenceChangeListener>()
-
- init {
- flags.addListener(Flags.STATUS_BAR_USER_SWITCHER) {
- it.requestNoRestart()
- notifyListeners()
- }
- }
-
- fun isStatusBarUserSwitcherFeatureEnabled(): Boolean {
- return flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
- }
-
- override fun addCallback(listener: OnUserSwitcherPreferenceChangeListener) {
- if (!listeners.contains(listener)) {
- listeners.add(listener)
- }
- }
-
- override fun removeCallback(listener: OnUserSwitcherPreferenceChangeListener) {
- listeners.remove(listener)
- }
-
- private fun notifyListeners() {
- val enabled = flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
- listeners.forEach {
- it.onUserSwitcherPreferenceChange(enabled)
- }
- }
-}
-
-interface OnUserSwitcherPreferenceChangeListener {
- fun onUserSwitcherPreferenceChange(enabled: Boolean)
-}
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 946d7e4..4d914fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -52,6 +52,6 @@
* Returns true if we should apply some coloring to the wifi icon that was rendered with the new
* pipeline to help with debugging.
*/
- // For now, just always apply the debug coloring if we've enabled the new icon.
- fun useWifiDebugColoring(): Boolean = useNewWifiIcon()
+ fun useWifiDebugColoring(): Boolean =
+ featureFlags.isEnabled(Flags.NEW_STATUS_BAR_ICONS_DEBUG_COLORING)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
index 7aa5ee1..8ff9198 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt
@@ -23,9 +23,10 @@
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.qs.SettingObserver
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.statusbar.pipeline.dagger.AirplaneTableLog
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -58,7 +59,7 @@
constructor(
@Background private val bgHandler: Handler,
private val globalSettings: GlobalSettings,
- logger: ConnectivityPipelineLogger,
+ @AirplaneTableLog logger: TableLogBuffer,
@Application scope: CoroutineScope,
) : AirplaneModeRepository {
// TODO(b/254848912): Replace this with a generic SettingObserver coroutine once we have it.
@@ -82,7 +83,12 @@
awaitClose { observer.isListening = false }
}
.distinctUntilChanged()
- .logInputChange(logger, "isAirplaneMode")
+ .logDiffsForTable(
+ logger,
+ columnPrefix = "",
+ columnName = "isAirplaneMode",
+ initialValue = false
+ )
.stateIn(
scope,
started = SharingStarted.WhileSubscribed(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
index fe30c01..4a5342e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModel.kt
@@ -36,16 +36,20 @@
* [com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository] for
* more details.
*/
+interface AirplaneModeViewModel {
+ /** True if the airplane mode icon is currently visible in the status bar. */
+ val isAirplaneModeIconVisible: StateFlow<Boolean>
+}
+
@SysUISingleton
-class AirplaneModeViewModel
+class AirplaneModeViewModelImpl
@Inject
constructor(
interactor: AirplaneModeInteractor,
logger: ConnectivityPipelineLogger,
@Application private val scope: CoroutineScope,
-) {
- /** True if the airplane mode icon is currently visible in the status bar. */
- val isAirplaneModeIconVisible: StateFlow<Boolean> =
+) : AirplaneModeViewModel {
+ override val isAirplaneModeIconVisible: StateFlow<Boolean> =
combine(interactor.isAirplaneMode, interactor.isForceHidden) {
isAirplaneMode,
isAirplaneIconForceHidden ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt
new file mode 100644
index 0000000..4f70f66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/AirplaneTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** Airplane mode logs in table format. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class AirplaneTableLog
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 fcd1b8a..fb67f1a 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
@@ -16,8 +16,13 @@
package com.android.systemui.statusbar.pipeline.dagger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
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.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
@@ -30,8 +35,11 @@
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module
abstract class StatusBarPipelineModule {
@@ -39,12 +47,18 @@
abstract fun airplaneModeRepository(impl: AirplaneModeRepositoryImpl): AirplaneModeRepository
@Binds
+ abstract fun airplaneModeViewModel(impl: AirplaneModeViewModelImpl): AirplaneModeViewModel
+
+ @Binds
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
@Binds
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
@Binds
+ abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
+
+ @Binds
abstract fun mobileConnectionsRepository(
impl: MobileConnectionsRepositoryImpl
): MobileConnectionsRepository
@@ -57,4 +71,23 @@
@Binds
abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
+
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ @WifiTableLog
+ fun provideWifiTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("WifiTableLog", 100)
+ }
+
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ @AirplaneTableLog
+ fun provideAirplaneTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("AirplaneTableLog", 30)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
new file mode 100644
index 0000000..ac395a9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/WifiTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** Wifi logs in table format. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class WifiTableLog
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/data/model/WifiNetworkModel.kt
index 062c3d1..a682a57 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/data/model/WifiNetworkModel.kt
@@ -17,12 +17,34 @@
package com.android.systemui.statusbar.pipeline.wifi.data.model
import androidx.annotation.VisibleForTesting
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.log.table.Diffable
/** Provides information about the current wifi network. */
-sealed class WifiNetworkModel {
+sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
+
/** A model representing that we have no active wifi network. */
object Inactive : WifiNetworkModel() {
override fun toString() = "WifiNetwork.Inactive"
+
+ override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+ if (prevVal is Inactive) {
+ return
+ }
+
+ if (prevVal is CarrierMerged) {
+ // The only difference between CarrierMerged and Inactive is the type
+ row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE)
+ return
+ }
+
+ // When changing from Active to Inactive, we need to log diffs to all the fields.
+ logFullNonActiveNetwork(TYPE_INACTIVE, row)
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ logFullNonActiveNetwork(TYPE_INACTIVE, row)
+ }
}
/**
@@ -33,6 +55,21 @@
*/
object CarrierMerged : WifiNetworkModel() {
override fun toString() = "WifiNetwork.CarrierMerged"
+
+ override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+ if (prevVal is CarrierMerged) {
+ return
+ }
+
+ if (prevVal is Inactive) {
+ // The only difference between CarrierMerged and Inactive is the type.
+ row.logChange(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)
+ return
+ }
+
+ // When changing from Active to CarrierMerged, we need to log diffs to all the fields.
+ logFullNonActiveNetwork(TYPE_CARRIER_MERGED, row)
+ }
}
/** Provides information about an active wifi network. */
@@ -76,6 +113,44 @@
}
}
+ override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
+ if (prevVal !is Active) {
+ row.logChange(COL_NETWORK_TYPE, TYPE_ACTIVE)
+ }
+
+ if (prevVal !is Active || prevVal.networkId != networkId) {
+ row.logChange(COL_NETWORK_ID, networkId)
+ }
+ if (prevVal !is Active || prevVal.isValidated != isValidated) {
+ row.logChange(COL_VALIDATED, isValidated)
+ }
+ if (prevVal !is Active || prevVal.level != level) {
+ if (level != null) {
+ row.logChange(COL_LEVEL, level)
+ } else {
+ row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+ }
+ }
+ if (prevVal !is Active || prevVal.ssid != ssid) {
+ row.logChange(COL_SSID, ssid)
+ }
+
+ // TODO(b/238425913): The passpoint-related values are frequently never used, so it
+ // would be great to not log them when they're not used.
+ if (prevVal !is Active || prevVal.isPasspointAccessPoint != isPasspointAccessPoint) {
+ row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
+ }
+ if (prevVal !is Active ||
+ prevVal.isOnlineSignUpForPasspointAccessPoint !=
+ isOnlineSignUpForPasspointAccessPoint) {
+ row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
+ }
+ if (prevVal !is Active ||
+ prevVal.passpointProviderFriendlyName != passpointProviderFriendlyName) {
+ row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
+ }
+ }
+
override fun toString(): String {
// 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.)
@@ -101,4 +176,31 @@
internal const val MAX_VALID_LEVEL = 4
}
}
+
+ internal fun logFullNonActiveNetwork(type: String, row: TableRowLogger) {
+ row.logChange(COL_NETWORK_TYPE, type)
+ row.logChange(COL_NETWORK_ID, NETWORK_ID_DEFAULT)
+ row.logChange(COL_VALIDATED, false)
+ row.logChange(COL_LEVEL, LEVEL_DEFAULT)
+ row.logChange(COL_SSID, null)
+ row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
+ row.logChange(COL_ONLINE_SIGN_UP, false)
+ row.logChange(COL_PASSPOINT_NAME, null)
+ }
}
+
+const val TYPE_CARRIER_MERGED = "CarrierMerged"
+const val TYPE_INACTIVE = "Inactive"
+const val TYPE_ACTIVE = "Active"
+
+const val COL_NETWORK_TYPE = "type"
+const val COL_NETWORK_ID = "networkId"
+const val COL_VALIDATED = "isValidated"
+const val COL_LEVEL = "level"
+const val COL_SSID = "ssid"
+const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint"
+const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint"
+const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName"
+
+val LEVEL_DEFAULT: String? = null
+val NETWORK_ID_DEFAULT: String? = null
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 93448c1d..a663536 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
@@ -36,6 +36,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
@@ -82,6 +85,7 @@
broadcastDispatcher: BroadcastDispatcher,
connectivityManager: ConnectivityManager,
logger: ConnectivityPipelineLogger,
+ @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
@Main mainExecutor: Executor,
@Application scope: CoroutineScope,
wifiManager: WifiManager?,
@@ -199,6 +203,12 @@
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "wifiNetwork",
+ initialValue = WIFI_NETWORK_DEFAULT,
+ )
// There will be multiple wifi icons in different places that will frequently
// subscribe/unsubscribe to flows as the views attach/detach. Using [stateIn] ensures that
// new subscribes will get the latest value immediately upon subscription. Otherwise, the
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 3a3e611..ec935fe 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
@@ -34,16 +34,36 @@
* This interactor processes information from our data layer into information that the UI layer can
* use.
*/
-@SysUISingleton
-class WifiInteractor @Inject constructor(
- connectivityRepository: ConnectivityRepository,
- wifiRepository: WifiRepository,
-) {
+interface WifiInteractor {
/**
* The SSID (service set identifier) of the wifi network. Null if we don't have a network, or
* have a network but no valid SSID.
*/
- val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
+ val ssid: Flow<String?>
+
+ /** Our current enabled status. */
+ val isEnabled: Flow<Boolean>
+
+ /** Our current default status. */
+ val isDefault: Flow<Boolean>
+
+ /** Our current wifi network. See [WifiNetworkModel]. */
+ val wifiNetwork: Flow<WifiNetworkModel>
+
+ /** Our current wifi activity. See [WifiActivityModel]. */
+ val activity: StateFlow<WifiActivityModel>
+
+ /** True if we're configured to force-hide the wifi icon and false otherwise. */
+ val isForceHidden: Flow<Boolean>
+}
+
+@SysUISingleton
+class WifiInteractorImpl @Inject constructor(
+ connectivityRepository: ConnectivityRepository,
+ wifiRepository: WifiRepository,
+) : WifiInteractor {
+
+ override val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
when (info) {
is WifiNetworkModel.Inactive -> null
is WifiNetworkModel.CarrierMerged -> null
@@ -56,20 +76,15 @@
}
}
- /** Our current enabled status. */
- val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
+ override val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
- /** Our current default status. */
- val isDefault: Flow<Boolean> = wifiRepository.isWifiDefault
+ override val isDefault: Flow<Boolean> = wifiRepository.isWifiDefault
- /** Our current wifi network. See [WifiNetworkModel]. */
- val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
+ override val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
- /** Our current wifi activity. See [WifiActivityModel]. */
- val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
+ override val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
- /** True if we're configured to force-hide the wifi icon and false otherwise. */
- val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
+ override val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
it.contains(ConnectivitySlot.WIFI)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index cf4106c..68d30d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -21,7 +21,6 @@
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.drawable.Drawable
-import android.os.UserHandle
import android.widget.BaseAdapter
import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
import com.android.systemui.user.data.source.UserRecord
@@ -84,7 +83,7 @@
}
fun refresh() {
- controller.refreshUsers(UserHandle.USER_NULL)
+ controller.refreshUsers()
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 149ed0a..d10d7cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -155,6 +155,9 @@
default void onWirelessChargingChanged(boolean isWirlessCharging) {
}
+
+ default void onIsOverheatedChanged(boolean isOverheated) {
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index c7ad767..2ee5232 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.policy;
+import static android.os.BatteryManager.BATTERY_HEALTH_OVERHEAT;
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_HEALTH;
import static android.os.BatteryManager.EXTRA_PRESENT;
import android.annotation.WorkerThread;
@@ -87,6 +90,7 @@
protected boolean mPowerSave;
private boolean mAodPowerSave;
private boolean mWirelessCharging;
+ private boolean mIsOverheated = false;
private boolean mTestMode = false;
@VisibleForTesting
boolean mHasReceivedBattery = false;
@@ -152,6 +156,7 @@
pw.print(" mPluggedIn="); pw.println(mPluggedIn);
pw.print(" mCharging="); pw.println(mCharging);
pw.print(" mCharged="); pw.println(mCharged);
+ pw.print(" mIsOverheated="); pw.println(mIsOverheated);
pw.print(" mPowerSave="); pw.println(mPowerSave);
pw.print(" mStateUnknown="); pw.println(mStateUnknown);
}
@@ -184,6 +189,7 @@
cb.onPowerSaveChanged(mPowerSave);
cb.onBatteryUnknownStateChanged(mStateUnknown);
cb.onWirelessChargingChanged(mWirelessCharging);
+ cb.onIsOverheatedChanged(mIsOverheated);
}
@Override
@@ -222,6 +228,13 @@
fireBatteryUnknownStateChanged();
}
+ int batteryHealth = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+ boolean isOverheated = batteryHealth == BATTERY_HEALTH_OVERHEAT;
+ if (isOverheated != mIsOverheated) {
+ mIsOverheated = isOverheated;
+ fireIsOverheatedChanged();
+ }
+
fireBatteryLevelChanged();
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
updatePowerSave();
@@ -292,6 +305,10 @@
return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
+ public boolean isOverheated() {
+ return mIsOverheated;
+ }
+
@Override
public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
// Need to fetch or refresh the estimate, but it may involve binder calls so offload the
@@ -402,6 +419,15 @@
}
}
+ private void fireIsOverheatedChanged() {
+ synchronized (mChangeCallbacks) {
+ final int n = mChangeCallbacks.size();
+ for (int i = 0; i < n; i++) {
+ mChangeCallbacks.get(i).onIsOverheatedChanged(mIsOverheated);
+ }
+ }
+ }
+
@Override
public void dispatchDemoCommand(String command, Bundle args) {
if (!mDemoModeController.isInDemoMode()) {
@@ -412,6 +438,7 @@
String plugged = args.getString("plugged");
String powerSave = args.getString("powersave");
String present = args.getString("present");
+ String overheated = args.getString("overheated");
if (level != null) {
mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
}
@@ -426,6 +453,10 @@
mStateUnknown = !present.equals("true");
fireBatteryUnknownStateChanged();
}
+ if (overheated != null) {
+ mIsOverheated = overheated.equals("true");
+ fireIsOverheatedChanged();
+ }
fireBatteryLevelChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index aae0f93..acdf0d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
@@ -41,6 +40,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -83,6 +83,7 @@
@Inject
public BluetoothControllerImpl(
Context context,
+ UserTracker userTracker,
DumpManager dumpManager,
BluetoothLogger logger,
@Background Looper bgLooper,
@@ -100,7 +101,7 @@
mLocalBluetoothManager.getBluetoothAdapter().getBluetoothState());
}
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
- mCurrentUser = ActivityManager.getCurrentUser();
+ mCurrentUser = userTracker.getUserId();
mDumpManager.registerDumpable(TAG, this);
}
@@ -145,7 +146,13 @@
}
private String getDeviceString(CachedBluetoothDevice device) {
- return device.getName() + " " + device.getBondState() + " " + device.isConnected();
+ return device.getName()
+ + " bondState=" + device.getBondState()
+ + " connected=" + device.isConnected()
+ + " active[A2DP]=" + device.isActiveDevice(BluetoothProfile.A2DP)
+ + " active[HEADSET]=" + device.isActiveDevice(BluetoothProfile.HEADSET)
+ + " active[HEARING_AID]=" + device.isActiveDevice(BluetoothProfile.HEARING_AID)
+ + " active[LE_AUDIO]=" + device.isActiveDevice(BluetoothProfile.LE_AUDIO);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 576962d..6875b52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -49,7 +50,7 @@
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -79,7 +80,7 @@
private static final String SHOW_SECONDS = "show_seconds";
private static final String VISIBILITY = "visibility";
- private final CurrentUserTracker mCurrentUserTracker;
+ private final UserTracker mUserTracker;
private final CommandQueue mCommandQueue;
private int mCurrentUserId;
@@ -114,6 +115,15 @@
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mCurrentUserId = newUser;
+ updateClock();
+ }
+ };
+
public Clock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -132,12 +142,7 @@
a.recycle();
}
mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
- mCurrentUserTracker = new CurrentUserTracker(mBroadcastDispatcher) {
- @Override
- public void onUserSwitched(int newUserId) {
- mCurrentUserId = newUserId;
- }
- };
+ mUserTracker = Dependency.get(UserTracker.class);
}
@Override
@@ -186,7 +191,6 @@
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
// NOTE: This receiver could run before this method returns, as it's not dispatching
// on the main thread and BroadcastDispatcher may not need to register with Context.
@@ -196,8 +200,8 @@
Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
StatusBarIconController.ICON_HIDE_LIST);
mCommandQueue.addCallback(this);
- mCurrentUserTracker.startTracking();
- mCurrentUserId = mCurrentUserTracker.getCurrentUserId();
+ mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mCurrentUserId = mUserTracker.getUserId();
}
// The time zone may have changed while the receiver wasn't registered, so update the Time
@@ -227,7 +231,7 @@
mAttached = false;
Dependency.get(TunerService.class).removeTunable(this);
mCommandQueue.removeCallback(this);
- mCurrentUserTracker.stopTracking();
+ mUserTracker.removeCallback(mUserChangedCallback);
}
}
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 e326611..6c66f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -140,6 +140,9 @@
// is out of sync, perhaps through a device restore, and update the
// preference
addPackageToSeededSet(prefs, pkg)
+ } else if (it.panelActivity != null) {
+ // Do not seed for packages with panels
+ addPackageToSeededSet(prefs, pkg)
} else {
componentsToSeed.add(it.componentName)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
index 4c6c7e0..3d0e69c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java
@@ -22,7 +22,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 69b55c8..a4821e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -18,7 +18,6 @@
import static android.net.TetheringManager.TETHERING_WIFI;
-import android.app.ActivityManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.TetheringManager;
@@ -38,6 +37,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -59,6 +59,7 @@
private final WifiManager mWifiManager;
private final Handler mMainHandler;
private final Context mContext;
+ private final UserTracker mUserTracker;
private int mHotspotState;
private volatile int mNumConnectedDevices;
@@ -95,10 +96,12 @@
@Inject
public HotspotControllerImpl(
Context context,
+ UserTracker userTracker,
@Main Handler mainHandler,
@Background Handler backgroundHandler,
DumpManager dumpManager) {
mContext = context;
+ mUserTracker = userTracker;
mTetheringManager = context.getSystemService(TetheringManager.class);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mMainHandler = mainHandler;
@@ -125,7 +128,7 @@
@Override
public boolean isHotspotSupported() {
return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs
- && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
+ && UserManager.get(mContext).isUserAdmin(mUserTracker.getUserId());
}
public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index b234e9c..63b9ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -28,11 +28,14 @@
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -45,22 +48,34 @@
private final ArrayList<NextAlarmChangeCallback> mChangeCallbacks = new ArrayList<>();
+ private final UserTracker mUserTracker;
private AlarmManager mAlarmManager;
private AlarmManager.AlarmClockInfo mNextAlarm;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ updateNextAlarm();
+ }
+ };
+
/**
*/
@Inject
public NextAlarmControllerImpl(
+ @Main Executor mainExecutor,
AlarmManager alarmManager,
BroadcastDispatcher broadcastDispatcher,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ UserTracker userTracker) {
dumpManager.registerDumpable("NextAlarmController", this);
mAlarmManager = alarmManager;
+ mUserTracker = userTracker;
IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
broadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
+ mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
updateNextAlarm();
}
@@ -98,14 +113,13 @@
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (action.equals(Intent.ACTION_USER_SWITCHED)
- || action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
+ if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
updateNextAlarm();
}
}
private void updateNextAlarm() {
- mNextAlarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
+ mNextAlarm = mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
fireNextAlarmChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index cc241d9..ba94714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.DeviceOwnerType;
@@ -55,8 +54,9 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
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.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import org.xmlpull.v1.XmlPullParserException;
@@ -70,7 +70,7 @@
/**
*/
@SysUISingleton
-public class SecurityControllerImpl extends CurrentUserTracker implements SecurityController {
+public class SecurityControllerImpl implements SecurityController {
private static final String TAG = "SecurityController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -84,11 +84,13 @@
private static final int CA_CERT_LOADING_RETRY_TIME_IN_MS = 30_000;
private final Context mContext;
+ private final UserTracker mUserTracker;
private final ConnectivityManager mConnectivityManager;
private final VpnManager mVpnManager;
private final DevicePolicyManager mDevicePolicyManager;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
+ private final Executor mMainExecutor;
private final Executor mBgExecutor;
@GuardedBy("mCallbacks")
@@ -102,18 +104,28 @@
// Needs to be cached here since the query has to be asynchronous
private ArrayMap<Integer, Boolean> mHasCACerts = new ArrayMap<Integer, Boolean>();
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ onUserSwitched(newUser);
+ }
+ };
+
/**
*/
@Inject
public SecurityControllerImpl(
Context context,
+ UserTracker userTracker,
@Background Handler bgHandler,
BroadcastDispatcher broadcastDispatcher,
+ @Main Executor mainExecutor,
@Background Executor bgExecutor,
DumpManager dumpManager
) {
- super(broadcastDispatcher);
mContext = context;
+ mUserTracker = userTracker;
mDevicePolicyManager = (DevicePolicyManager)
context.getSystemService(Context.DEVICE_POLICY_SERVICE);
mConnectivityManager = (ConnectivityManager)
@@ -121,6 +133,7 @@
mVpnManager = context.getSystemService(VpnManager.class);
mPackageManager = context.getPackageManager();
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
dumpManager.registerDumpable(getClass().getSimpleName(), this);
@@ -133,8 +146,8 @@
// TODO: re-register network callback on user change.
mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
- onUserSwitched(ActivityManager.getCurrentUser());
- startTracking();
+ onUserSwitched(mUserTracker.getUserId());
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
}
public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 29285f8..a593d51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -28,7 +27,6 @@
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract;
@@ -40,8 +38,11 @@
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -53,6 +54,7 @@
private static final String TAG = "UserInfoController";
private final Context mContext;
+ private final UserTracker mUserTracker;
private final ArrayList<OnUserInfoChangedListener> mCallbacks =
new ArrayList<OnUserInfoChangedListener>();
private AsyncTask<Void, Void, UserInfoQueryResult> mUserInfoTask;
@@ -64,11 +66,11 @@
/**
*/
@Inject
- public UserInfoControllerImpl(Context context) {
+ public UserInfoControllerImpl(Context context, @Main Executor mainExecutor,
+ UserTracker userTracker) {
mContext = context;
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- mContext.registerReceiver(mReceiver, filter);
+ mUserTracker = userTracker;
+ mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
IntentFilter profileFilter = new IntentFilter();
profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
@@ -88,15 +90,13 @@
mCallbacks.remove(callback);
}
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- reloadUserInfo();
- }
- }
- };
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ reloadUserInfo();
+ }
+ };
private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
@Override
@@ -104,15 +104,11 @@
final String action = intent.getAction();
if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
- try {
- final int currentUser = ActivityManager.getService().getCurrentUser().id;
- final int changedUser =
- intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
- if (changedUser == currentUser) {
- reloadUserInfo();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't get current user id for profile change", e);
+ final int currentUser = mUserTracker.getUserId();
+ final int changedUser =
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+ if (changedUser == currentUser) {
+ reloadUserInfo();
}
}
}
@@ -130,15 +126,12 @@
Context currentUserContext;
UserInfo userInfo;
try {
- userInfo = ActivityManager.getService().getCurrentUser();
+ userInfo = mUserTracker.getUserInfo();
currentUserContext = mContext.createPackageContextAsUser("android", 0,
new UserHandle(userInfo.id));
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Couldn't create user context", e);
throw new RuntimeException(e);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't get user info", e);
- throw new RuntimeException(e);
}
final int userId = userInfo.id;
final boolean isGuest = userInfo.isGuest();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
index 146b222..bdb656b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt
@@ -14,35 +14,74 @@
* limitations under the License.
*
*/
+
package com.android.systemui.statusbar.policy
-import android.annotation.UserIdInt
+import android.content.Context
import android.content.Intent
import android.view.View
-import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
+import dagger.Lazy
+import java.io.PrintWriter
import java.lang.ref.WeakReference
-import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
-/** Defines interface for a class that provides user switching functionality and state. */
-interface UserSwitcherController : Dumpable {
+/** Access point into multi-user switching logic. */
+@Deprecated("Use UserInteractor or GuestUserInteractor instead.")
+@SysUISingleton
+class UserSwitcherController
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val userInteractorLazy: Lazy<UserInteractor>,
+ private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
+ private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
+ private val activityStarter: ActivityStarter,
+) {
+
+ /** Defines interface for classes that can be called back when the user is switched. */
+ fun interface UserSwitchCallback {
+ /** Notifies that the user has switched. */
+ fun onUserSwitched()
+ }
+
+ private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
+ private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
+ private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
+
+ private val callbackCompatMap = mutableMapOf<UserSwitchCallback, UserInteractor.UserCallback>()
/** The current list of [UserRecord]. */
val users: ArrayList<UserRecord>
+ get() = userInteractor.userRecords.value
/** Whether the user switcher experience should use the simple experience. */
val isSimpleUserSwitcher: Boolean
-
- /** Require a view for jank detection */
- fun init(view: View)
+ get() = userInteractor.isSimpleUserSwitcher
/** The [UserRecord] of the current user or `null` when none. */
val currentUserRecord: UserRecord?
+ get() = userInteractor.selectedUserRecord.value
/** The name of the current user of the device or `null`, when none is selected. */
val currentUserName: String?
+ get() =
+ currentUserRecord?.let {
+ LegacyUserUiHelper.getUserRecordName(
+ context = applicationContext,
+ record = it,
+ isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
+ isGuestUserResetting = userInteractor.isGuestUserResetting,
+ )
+ }
/**
* Notifies that a user has been selected.
@@ -55,34 +94,40 @@
* @param userId The ID of the user to switch to.
* @param dialogShower An optional [DialogShower] in case we need to show dialogs.
*/
- fun onUserSelected(userId: Int, dialogShower: DialogShower?)
-
- /** Whether it is allowed to add users while the device is locked. */
- val isAddUsersFromLockScreenEnabled: Flow<Boolean>
+ fun onUserSelected(userId: Int, dialogShower: DialogShower?) {
+ userInteractor.selectUser(userId, dialogShower)
+ }
/** Whether the guest user is configured to always be present on the device. */
val isGuestUserAutoCreated: Boolean
+ get() = userInteractor.isGuestUserAutoCreated
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean
-
- /** Creates and switches to the guest user. */
- fun createAndSwitchToGuestUser(dialogShower: DialogShower?)
-
- /** Shows the add user dialog. */
- fun showAddUserDialog(dialogShower: DialogShower?)
-
- /** Starts an activity to add a supervised user to the device. */
- fun startSupervisedUserActivity()
-
- /** Notifies when the display density or font scale has changed. */
- fun onDensityOrFontScaleChanged()
+ get() = userInteractor.isGuestUserResetting
/** Registers an adapter to notify when the users change. */
- fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>)
+ fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
+ userInteractor.addCallback(
+ object : UserInteractor.UserCallback {
+ override fun isEvictable(): Boolean {
+ return adapter.get() == null
+ }
+
+ override fun onUserStateChanged() {
+ adapter.get()?.notifyDataSetChanged()
+ }
+ }
+ )
+ }
/** Notifies the item for a user has been clicked. */
- fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?)
+ fun onUserListItemClicked(
+ record: UserRecord,
+ dialogShower: DialogShower?,
+ ) {
+ userInteractor.onRecordSelected(record, dialogShower)
+ }
/**
* Removes guest user and switches to target user. The guest must be the current user and its id
@@ -103,7 +148,12 @@
* @param targetUserId id of the user to switch to after guest is removed. If
* `UserHandle.USER_NULL`, then switch immediately to the newly created guest user.
*/
- fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int)
+ fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
+ userInteractor.removeGuestUser(
+ guestUserId = guestUserId,
+ targetUserId = targetUserId,
+ )
+ }
/**
* Exits guest user and switches to previous non-guest user. The guest must be the current user.
@@ -114,43 +164,58 @@
* @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest
* only if its ephemeral, else keep guest
*/
- fun exitGuestUser(
- @UserIdInt guestUserId: Int,
- @UserIdInt targetUserId: Int,
- forceRemoveGuestOnExit: Boolean
- )
+ fun exitGuestUser(guestUserId: Int, targetUserId: Int, forceRemoveGuestOnExit: Boolean) {
+ userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
+ }
/**
* Guarantee guest is present only if the device is provisioned. Otherwise, create a content
* observer to wait until the device is provisioned, then schedule the guest creation.
*/
- fun schedulePostBootGuestCreation()
+ fun schedulePostBootGuestCreation() {
+ guestUserInteractor.onDeviceBootCompleted()
+ }
/** Whether keyguard is showing. */
val isKeyguardShowing: Boolean
+ get() = keyguardInteractor.isKeyguardShowing()
/** Starts an activity with the given [Intent]. */
- fun startActivity(intent: Intent)
+ fun startActivity(intent: Intent) {
+ activityStarter.startActivity(intent, /* dismissShade= */ true)
+ }
/**
* Refreshes users from UserManager.
*
* The pictures are only loaded if they have not been loaded yet.
- *
- * @param forcePictureLoadForId forces the picture of the given user to be reloaded.
*/
- fun refreshUsers(forcePictureLoadForId: Int)
+ fun refreshUsers() {
+ userInteractor.refreshUsers()
+ }
/** Adds a subscriber to when user switches. */
- fun addUserSwitchCallback(callback: UserSwitchCallback)
+ fun addUserSwitchCallback(callback: UserSwitchCallback) {
+ val interactorCallback =
+ object : UserInteractor.UserCallback {
+ override fun onUserStateChanged() {
+ callback.onUserSwitched()
+ }
+ }
+ callbackCompatMap[callback] = interactorCallback
+ userInteractor.addCallback(interactorCallback)
+ }
/** Removes a previously-added subscriber. */
- fun removeUserSwitchCallback(callback: UserSwitchCallback)
+ fun removeUserSwitchCallback(callback: UserSwitchCallback) {
+ val interactorCallback = callbackCompatMap.remove(callback)
+ if (interactorCallback != null) {
+ userInteractor.removeCallback(interactorCallback)
+ }
+ }
- /** Defines interface for classes that can be called back when the user is switched. */
- fun interface UserSwitchCallback {
- /** Notifies that the user has switched. */
- fun onUserSwitched()
+ fun dump(pw: PrintWriter, args: Array<out String>) {
+ userInteractor.dump(pw)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
deleted file mode 100644
index 935fc7f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.statusbar.policy
-
-import android.content.Context
-import android.content.Intent
-import android.view.View
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.interactor.GuestUserInteractor
-import com.android.systemui.user.domain.interactor.UserInteractor
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import dagger.Lazy
-import java.io.PrintWriter
-import java.lang.ref.WeakReference
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Implementation of [UserSwitcherController]. */
-@SysUISingleton
-class UserSwitcherControllerImpl
-@Inject
-constructor(
- @Application private val applicationContext: Context,
- flags: FeatureFlags,
- @Suppress("DEPRECATION") private val oldImpl: Lazy<UserSwitcherControllerOldImpl>,
- private val userInteractorLazy: Lazy<UserInteractor>,
- private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
- private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
- private val activityStarter: ActivityStarter,
-) : UserSwitcherController {
-
- private val useInteractor: Boolean =
- flags.isEnabled(Flags.USER_CONTROLLER_USES_INTERACTOR) &&
- !flags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
- private val _oldImpl: UserSwitcherControllerOldImpl
- get() = oldImpl.get()
- private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() }
- private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() }
- private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() }
-
- private val callbackCompatMap =
- mutableMapOf<UserSwitcherController.UserSwitchCallback, UserInteractor.UserCallback>()
-
- private fun notSupported(): Nothing {
- error("Not supported in the new implementation!")
- }
-
- override val users: ArrayList<UserRecord>
- get() =
- if (useInteractor) {
- userInteractor.userRecords.value
- } else {
- _oldImpl.users
- }
-
- override val isSimpleUserSwitcher: Boolean
- get() =
- if (useInteractor) {
- userInteractor.isSimpleUserSwitcher
- } else {
- _oldImpl.isSimpleUserSwitcher
- }
-
- override fun init(view: View) {
- if (!useInteractor) {
- _oldImpl.init(view)
- }
- }
-
- override val currentUserRecord: UserRecord?
- get() =
- if (useInteractor) {
- userInteractor.selectedUserRecord.value
- } else {
- _oldImpl.currentUserRecord
- }
-
- override val currentUserName: String?
- get() =
- if (useInteractor) {
- currentUserRecord?.let {
- LegacyUserUiHelper.getUserRecordName(
- context = applicationContext,
- record = it,
- isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated,
- isGuestUserResetting = userInteractor.isGuestUserResetting,
- )
- }
- } else {
- _oldImpl.currentUserName
- }
-
- override fun onUserSelected(
- userId: Int,
- dialogShower: UserSwitchDialogController.DialogShower?
- ) {
- if (useInteractor) {
- userInteractor.selectUser(userId, dialogShower)
- } else {
- _oldImpl.onUserSelected(userId, dialogShower)
- }
- }
-
- override val isAddUsersFromLockScreenEnabled: Flow<Boolean>
- get() =
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.isAddUsersFromLockScreenEnabled
- }
-
- override val isGuestUserAutoCreated: Boolean
- get() =
- if (useInteractor) {
- userInteractor.isGuestUserAutoCreated
- } else {
- _oldImpl.isGuestUserAutoCreated
- }
-
- override val isGuestUserResetting: Boolean
- get() =
- if (useInteractor) {
- userInteractor.isGuestUserResetting
- } else {
- _oldImpl.isGuestUserResetting
- }
-
- override fun createAndSwitchToGuestUser(
- dialogShower: UserSwitchDialogController.DialogShower?,
- ) {
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.createAndSwitchToGuestUser(dialogShower)
- }
- }
-
- override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) {
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.showAddUserDialog(dialogShower)
- }
- }
-
- override fun startSupervisedUserActivity() {
- if (useInteractor) {
- notSupported()
- } else {
- _oldImpl.startSupervisedUserActivity()
- }
- }
-
- override fun onDensityOrFontScaleChanged() {
- if (!useInteractor) {
- _oldImpl.onDensityOrFontScaleChanged()
- }
- }
-
- override fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
- if (useInteractor) {
- userInteractor.addCallback(
- object : UserInteractor.UserCallback {
- override fun isEvictable(): Boolean {
- return adapter.get() == null
- }
-
- override fun onUserStateChanged() {
- adapter.get()?.notifyDataSetChanged()
- }
- }
- )
- } else {
- _oldImpl.addAdapter(adapter)
- }
- }
-
- override fun onUserListItemClicked(
- record: UserRecord,
- dialogShower: UserSwitchDialogController.DialogShower?,
- ) {
- if (useInteractor) {
- userInteractor.onRecordSelected(record, dialogShower)
- } else {
- _oldImpl.onUserListItemClicked(record, dialogShower)
- }
- }
-
- override fun removeGuestUser(guestUserId: Int, targetUserId: Int) {
- if (useInteractor) {
- userInteractor.removeGuestUser(
- guestUserId = guestUserId,
- targetUserId = targetUserId,
- )
- } else {
- _oldImpl.removeGuestUser(guestUserId, targetUserId)
- }
- }
-
- override fun exitGuestUser(
- guestUserId: Int,
- targetUserId: Int,
- forceRemoveGuestOnExit: Boolean
- ) {
- if (useInteractor) {
- userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
- } else {
- _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit)
- }
- }
-
- override fun schedulePostBootGuestCreation() {
- if (useInteractor) {
- guestUserInteractor.onDeviceBootCompleted()
- } else {
- _oldImpl.schedulePostBootGuestCreation()
- }
- }
-
- override val isKeyguardShowing: Boolean
- get() =
- if (useInteractor) {
- keyguardInteractor.isKeyguardShowing()
- } else {
- _oldImpl.isKeyguardShowing
- }
-
- override fun startActivity(intent: Intent) {
- if (useInteractor) {
- activityStarter.startActivity(intent, /* dismissShade= */ true)
- } else {
- _oldImpl.startActivity(intent)
- }
- }
-
- override fun refreshUsers(forcePictureLoadForId: Int) {
- if (useInteractor) {
- userInteractor.refreshUsers()
- } else {
- _oldImpl.refreshUsers(forcePictureLoadForId)
- }
- }
-
- override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
- if (useInteractor) {
- val interactorCallback =
- object : UserInteractor.UserCallback {
- override fun onUserStateChanged() {
- callback.onUserSwitched()
- }
- }
- callbackCompatMap[callback] = interactorCallback
- userInteractor.addCallback(interactorCallback)
- } else {
- _oldImpl.addUserSwitchCallback(callback)
- }
- }
-
- override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) {
- if (useInteractor) {
- val interactorCallback = callbackCompatMap.remove(callback)
- if (interactorCallback != null) {
- userInteractor.removeCallback(interactorCallback)
- }
- } else {
- _oldImpl.removeUserSwitchCallback(callback)
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- if (useInteractor) {
- userInteractor.dump(pw)
- } else {
- _oldImpl.dump(pw, args)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
deleted file mode 100644
index c294c37..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java
+++ /dev/null
@@ -1,1063 +0,0 @@
-/*
- * Copyright (C) 2014 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 static android.os.UserManager.SWITCHABILITY_STATUS_OK;
-
-import android.annotation.UserIdInt;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.IActivityManager;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.graphics.Bitmap;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.telephony.TelephonyCallback;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.view.View;
-import android.view.WindowManagerGlobal;
-import android.widget.Toast;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.users.UserCreatingDialog;
-import com.android.systemui.GuestResetOrExitSessionReceiver;
-import com.android.systemui.GuestResumeSessionReceiver;
-import com.android.systemui.SystemUISecondaryUserService;
-import com.android.systemui.animation.DialogCuj;
-import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.LongRunning;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.qs.QSUserSwitcherEvent;
-import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.telephony.TelephonyListenerManager;
-import com.android.systemui.user.data.source.UserRecord;
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper;
-import com.android.systemui.user.shared.model.UserActionModel;
-import com.android.systemui.user.ui.dialog.AddUserDialog;
-import com.android.systemui.user.ui.dialog.ExitGuestDialog;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
-/**
- * Old implementation. Keeps a list of all users on the device for user switching.
- *
- * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController}
- * instead.
- */
-@Deprecated
-@SysUISingleton
-public class UserSwitcherControllerOldImpl implements UserSwitcherController {
-
- private static final String TAG = "UserSwitcherController";
- private static final boolean DEBUG = false;
- private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING =
- "lockscreenSimpleUserSwitcher";
- private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000;
-
- private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
- private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L;
-
- private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user";
- private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode";
-
- protected final Context mContext;
- protected final UserTracker mUserTracker;
- protected final UserManager mUserManager;
- private final ContentObserver mSettingsObserver;
- private final ArrayList<WeakReference<BaseUserSwitcherAdapter>> mAdapters = new ArrayList<>();
- @VisibleForTesting
- final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
- @VisibleForTesting
- final GuestResetOrExitSessionReceiver mGuestResetOrExitSessionReceiver;
- private final KeyguardStateController mKeyguardStateController;
- private final DeviceProvisionedController mDeviceProvisionedController;
- private final DevicePolicyManager mDevicePolicyManager;
- protected final Handler mHandler;
- private final ActivityStarter mActivityStarter;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final BroadcastSender mBroadcastSender;
- private final TelephonyListenerManager mTelephonyListenerManager;
- private final InteractionJankMonitor mInteractionJankMonitor;
- private final LatencyTracker mLatencyTracker;
- private final DialogLaunchAnimator mDialogLaunchAnimator;
-
- private ArrayList<UserRecord> mUsers = new ArrayList<>();
- @VisibleForTesting
- AlertDialog mExitGuestDialog;
- @VisibleForTesting
- Dialog mAddUserDialog;
- private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
- private boolean mSimpleUserSwitcher;
- // When false, there won't be any visual affordance to add a new user from the keyguard even if
- // the user is unlocked
- private final MutableStateFlow<Boolean> mAddUsersFromLockScreen =
- StateFlowKt.MutableStateFlow(false);
- private boolean mUserSwitcherEnabled;
- @VisibleForTesting
- boolean mPauseRefreshUsers;
- private int mSecondaryUser = UserHandle.USER_NULL;
- private Intent mSecondaryUserServiceIntent;
- private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
- private final UiEventLogger mUiEventLogger;
- private final IActivityManager mActivityManager;
- private final Executor mBgExecutor;
- private final Executor mUiExecutor;
- private final Executor mLongRunningExecutor;
- private final boolean mGuestUserAutoCreated;
- private final AtomicBoolean mGuestIsResetting;
- private final AtomicBoolean mGuestCreationScheduled;
- private FalsingManager mFalsingManager;
- @Nullable
- private View mView;
- private String mCreateSupervisedUserPackage;
- private GlobalSettings mGlobalSettings;
- private List<UserSwitchCallback> mUserSwitchCallbacks =
- Collections.synchronizedList(new ArrayList<>());
-
- @Inject
- public UserSwitcherControllerOldImpl(
- Context context,
- IActivityManager activityManager,
- UserManager userManager,
- UserTracker userTracker,
- KeyguardStateController keyguardStateController,
- DeviceProvisionedController deviceProvisionedController,
- DevicePolicyManager devicePolicyManager,
- @Main Handler handler,
- ActivityStarter activityStarter,
- BroadcastDispatcher broadcastDispatcher,
- BroadcastSender broadcastSender,
- UiEventLogger uiEventLogger,
- FalsingManager falsingManager,
- TelephonyListenerManager telephonyListenerManager,
- SecureSettings secureSettings,
- GlobalSettings globalSettings,
- @Background Executor bgExecutor,
- @LongRunning Executor longRunningExecutor,
- @Main Executor uiExecutor,
- InteractionJankMonitor interactionJankMonitor,
- LatencyTracker latencyTracker,
- DumpManager dumpManager,
- DialogLaunchAnimator dialogLaunchAnimator,
- GuestResumeSessionReceiver guestResumeSessionReceiver,
- GuestResetOrExitSessionReceiver guestResetOrExitSessionReceiver) {
- mContext = context;
- mActivityManager = activityManager;
- mUserTracker = userTracker;
- mBroadcastDispatcher = broadcastDispatcher;
- mBroadcastSender = broadcastSender;
- mTelephonyListenerManager = telephonyListenerManager;
- mUiEventLogger = uiEventLogger;
- mFalsingManager = falsingManager;
- mInteractionJankMonitor = interactionJankMonitor;
- mLatencyTracker = latencyTracker;
- mGlobalSettings = globalSettings;
- mGuestResumeSessionReceiver = guestResumeSessionReceiver;
- mGuestResetOrExitSessionReceiver = guestResetOrExitSessionReceiver;
- mBgExecutor = bgExecutor;
- mLongRunningExecutor = longRunningExecutor;
- mUiExecutor = uiExecutor;
- mGuestResumeSessionReceiver.register();
- mGuestResetOrExitSessionReceiver.register();
- mGuestUserAutoCreated = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_guestUserAutoCreated);
- mGuestIsResetting = new AtomicBoolean();
- mGuestCreationScheduled = new AtomicBoolean();
- mKeyguardStateController = keyguardStateController;
- mDeviceProvisionedController = deviceProvisionedController;
- mDevicePolicyManager = devicePolicyManager;
- mHandler = handler;
- mActivityStarter = activityStarter;
- mUserManager = userManager;
- mDialogLaunchAnimator = dialogLaunchAnimator;
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_ADDED);
- filter.addAction(Intent.ACTION_USER_REMOVED);
- filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_USER_STOPPED);
- filter.addAction(Intent.ACTION_USER_UNLOCKED);
- mBroadcastDispatcher.registerReceiver(
- mReceiver, filter, null /* executor */,
- UserHandle.SYSTEM, Context.RECEIVER_EXPORTED, null /* permission */);
-
- mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-
- mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class);
-
- filter = new IntentFilter();
- mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter,
- PERMISSION_SELF, null /* scheduler */,
- Context.RECEIVER_EXPORTED_UNAUDITED);
-
- mSettingsObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
- mAddUsersFromLockScreen.setValue(
- mGlobalSettings.getIntForUser(
- Settings.Global.ADD_USERS_WHEN_LOCKED,
- 0,
- UserHandle.USER_SYSTEM) != 0);
- mUserSwitcherEnabled = mGlobalSettings.getIntForUser(
- Settings.Global.USER_SWITCHER_ENABLED, 0, UserHandle.USER_SYSTEM) != 0;
- refreshUsers(UserHandle.USER_NULL);
- };
- };
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.USER_SWITCHER_ENABLED), true,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true,
- mSettingsObserver);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(
- Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED),
- true, mSettingsObserver);
- // Fetch initial values.
- mSettingsObserver.onChange(false);
-
- keyguardStateController.addCallback(mCallback);
- listenForCallState();
-
- mCreateSupervisedUserPackage = mContext.getString(
- com.android.internal.R.string.config_supervisedUserCreationPackage);
-
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
- refreshUsers(UserHandle.USER_NULL);
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void refreshUsers(int forcePictureLoadForId) {
- if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")");
- if (forcePictureLoadForId != UserHandle.USER_NULL) {
- mForcePictureLoadForUserId.put(forcePictureLoadForId, true);
- }
-
- if (mPauseRefreshUsers) {
- return;
- }
-
- boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL);
- SparseArray<Bitmap> bitmaps = new SparseArray<>(mUsers.size());
- final int userCount = mUsers.size();
- for (int i = 0; i < userCount; i++) {
- UserRecord r = mUsers.get(i);
- if (r == null || r.picture == null || r.info == null || forceAllUsers
- || mForcePictureLoadForUserId.get(r.info.id)) {
- continue;
- }
- bitmaps.put(r.info.id, r.picture);
- }
- mForcePictureLoadForUserId.clear();
-
- mBgExecutor.execute(() -> {
- List<UserInfo> infos = mUserManager.getAliveUsers();
- if (infos == null) {
- return;
- }
- ArrayList<UserRecord> records = new ArrayList<>(infos.size());
- int currentId = mUserTracker.getUserId();
- // Check user switchability of the foreground user since SystemUI is running in
- // User 0
- boolean canSwitchUsers = mUserManager.getUserSwitchability(
- UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK;
- UserRecord guestRecord = null;
-
- for (UserInfo info : infos) {
- boolean isCurrent = currentId == info.id;
- if (!mUserSwitcherEnabled && !info.isPrimary()) {
- continue;
- }
-
- if (info.isEnabled()) {
- if (info.isGuest()) {
- // Tapping guest icon triggers remove and a user switch therefore
- // the icon shouldn't be enabled even if the user is current
- guestRecord = LegacyUserDataHelper.createRecord(
- mContext,
- mUserManager,
- null /* picture */,
- info,
- isCurrent,
- canSwitchUsers);
- } else if (info.supportsSwitchToByUser()) {
- records.add(
- LegacyUserDataHelper.createRecord(
- mContext,
- mUserManager,
- bitmaps.get(info.id),
- info,
- isCurrent,
- canSwitchUsers));
- }
- }
- }
-
- if (guestRecord == null) {
- if (mGuestUserAutoCreated) {
- // If mGuestIsResetting=true, the switch should be disabled since
- // we will just use it as an indicator for "Resetting guest...".
- // Otherwise, default to canSwitchUsers.
- boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers;
- guestRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ENTER_GUEST_MODE,
- false /* isRestricted */,
- isSwitchToGuestEnabled);
- records.add(guestRecord);
- } else if (canCreateGuest(guestRecord != null)) {
- guestRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ENTER_GUEST_MODE,
- false /* isRestricted */,
- canSwitchUsers);
- records.add(guestRecord);
- }
- } else {
- records.add(guestRecord);
- }
-
- if (canCreateUser()) {
- final UserRecord userRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ADD_USER,
- createIsRestricted(),
- canSwitchUsers);
- records.add(userRecord);
- }
-
- if (canCreateSupervisedUser()) {
- final UserRecord userRecord = LegacyUserDataHelper.createRecord(
- mContext,
- currentId,
- UserActionModel.ADD_SUPERVISED_USER,
- createIsRestricted(),
- canSwitchUsers);
- records.add(userRecord);
- }
-
- if (canManageUsers()) {
- records.add(LegacyUserDataHelper.createRecord(
- mContext,
- KeyguardUpdateMonitor.getCurrentUser(),
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- /* isRestricted= */ false,
- /* isSwitchToEnabled= */ true
- ));
- }
-
- mUiExecutor.execute(() -> {
- if (records != null) {
- mUsers = records;
- notifyAdapters();
- }
- });
- });
- }
-
- private boolean systemCanCreateUsers() {
- return !mUserManager.hasBaseUserRestriction(
- UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM);
- }
-
- private boolean currentUserCanCreateUsers() {
- UserInfo currentUser = mUserTracker.getUserInfo();
- return currentUser != null
- && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM)
- && systemCanCreateUsers();
- }
-
- private boolean anyoneCanCreateUsers() {
- return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue();
- }
-
- @VisibleForTesting
- boolean canCreateGuest(boolean hasExistingGuest) {
- return mUserSwitcherEnabled
- && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
- && !hasExistingGuest;
- }
-
- @VisibleForTesting
- boolean canCreateUser() {
- return mUserSwitcherEnabled
- && (currentUserCanCreateUsers() || anyoneCanCreateUsers())
- && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY);
- }
-
- @VisibleForTesting
- boolean canManageUsers() {
- UserInfo currentUser = mUserTracker.getUserInfo();
- return mUserSwitcherEnabled
- && ((currentUser != null && currentUser.isAdmin())
- || mAddUsersFromLockScreen.getValue());
- }
-
- private boolean createIsRestricted() {
- return !mAddUsersFromLockScreen.getValue();
- }
-
- @VisibleForTesting
- boolean canCreateSupervisedUser() {
- return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser();
- }
-
- private void pauseRefreshUsers() {
- if (!mPauseRefreshUsers) {
- mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS);
- mPauseRefreshUsers = true;
- }
- }
-
- private void notifyAdapters() {
- for (int i = mAdapters.size() - 1; i >= 0; i--) {
- BaseUserSwitcherAdapter adapter = mAdapters.get(i).get();
- if (adapter != null) {
- adapter.notifyDataSetChanged();
- } else {
- mAdapters.remove(i);
- }
- }
- }
-
- @Override
- public boolean isSimpleUserSwitcher() {
- return mSimpleUserSwitcher;
- }
-
- /**
- * Returns whether the current user is a system user.
- */
- @VisibleForTesting
- boolean isSystemUser() {
- return mUserTracker.getUserId() == UserHandle.USER_SYSTEM;
- }
-
- @Override
- public @Nullable UserRecord getCurrentUserRecord() {
- for (int i = 0; i < mUsers.size(); ++i) {
- UserRecord userRecord = mUsers.get(i);
- if (userRecord.isCurrent) {
- return userRecord;
- }
- }
- return null;
- }
-
- @Override
- public void onUserSelected(int userId, @Nullable DialogShower dialogShower) {
- UserRecord userRecord = mUsers.stream()
- .filter(x -> x.resolveId() == userId)
- .findFirst()
- .orElse(null);
- if (userRecord == null) {
- return;
- }
-
- onUserListItemClicked(userRecord, dialogShower);
- }
-
- @Override
- public Flow<Boolean> isAddUsersFromLockScreenEnabled() {
- return mAddUsersFromLockScreen;
- }
-
- @Override
- public boolean isGuestUserAutoCreated() {
- return mGuestUserAutoCreated;
- }
-
- @Override
- public boolean isGuestUserResetting() {
- return mGuestIsResetting.get();
- }
-
- @Override
- public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
- if (record.isGuest && record.info == null) {
- createAndSwitchToGuestUser(dialogShower);
- } else if (record.isAddUser) {
- showAddUserDialog(dialogShower);
- } else if (record.isAddSupervisedUser) {
- startSupervisedUserActivity();
- } else if (record.isManageUsers) {
- startActivity(new Intent(Settings.ACTION_USER_SETTINGS));
- } else {
- onUserListItemClicked(record.info.id, record, dialogShower);
- }
- }
-
- private void onUserListItemClicked(int id, UserRecord record, DialogShower dialogShower) {
- int currUserId = mUserTracker.getUserId();
- // If switching from guest and guest is ephemeral, then follow the flow
- // of showExitGuestDialog to remove current guest,
- // and switch to selected user
- UserInfo currUserInfo = mUserTracker.getUserInfo();
- if (currUserId == id) {
- if (record.isGuest) {
- showExitGuestDialog(id, currUserInfo.isEphemeral(), dialogShower);
- }
- return;
- }
-
- if (currUserInfo != null && currUserInfo.isGuest()) {
- showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
- record.resolveId(), dialogShower);
- return;
- }
-
- if (dialogShower != null) {
- // If we haven't morphed into another dialog, it means we have just switched users.
- // Then, dismiss the dialog.
- dialogShower.dismiss();
- }
- switchToUserId(id);
- }
-
- private void switchToUserId(int id) {
- try {
- if (mView != null) {
- mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
- .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
- .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
- }
- mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
- pauseRefreshUsers();
- mActivityManager.switchUser(id);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't switch user.", e);
- }
- }
-
- private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) {
- int newId = UserHandle.USER_SYSTEM;
- if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
- UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
- if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
- newId = info.id;
- }
- }
- showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower);
- }
-
- private void showExitGuestDialog(
- int id,
- boolean isGuestEphemeral,
- int targetId,
- DialogShower dialogShower) {
- if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
- mExitGuestDialog.cancel();
- }
- mExitGuestDialog = new ExitGuestDialog(
- mContext,
- id,
- isGuestEphemeral,
- targetId,
- mKeyguardStateController.isShowing(),
- mFalsingManager,
- mDialogLaunchAnimator,
- this::exitGuestUser);
- if (dialogShower != null) {
- dialogShower.showDialog(mExitGuestDialog, new DialogCuj(
- InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
- INTERACTION_JANK_EXIT_GUEST_MODE_TAG));
- } else {
- mExitGuestDialog.show();
- }
- }
-
- @Override
- public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) {
- createGuestAsync(guestId -> {
- // guestId may be USER_NULL if we haven't reloaded the user list yet.
- if (guestId != UserHandle.USER_NULL) {
- mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD);
- onUserListItemClicked(guestId, UserRecord.createForGuest(), dialogShower);
- }
- });
- }
-
- @Override
- public void showAddUserDialog(@Nullable DialogShower dialogShower) {
- if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
- mAddUserDialog.cancel();
- }
- final UserInfo currentUser = mUserTracker.getUserInfo();
- mAddUserDialog = new AddUserDialog(
- mContext,
- currentUser.getUserHandle(),
- mKeyguardStateController.isShowing(),
- /* showEphemeralMessage= */currentUser.isGuest() && currentUser.isEphemeral(),
- mFalsingManager,
- mBroadcastSender,
- mDialogLaunchAnimator);
- if (dialogShower != null) {
- dialogShower.showDialog(mAddUserDialog,
- new DialogCuj(
- InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
- INTERACTION_JANK_ADD_NEW_USER_TAG
- ));
- } else {
- mAddUserDialog.show();
- }
- }
-
- @Override
- public void startSupervisedUserActivity() {
- final Intent intent = new Intent()
- .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
- .setPackage(mCreateSupervisedUserPackage)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- mContext.startActivity(intent);
- }
-
- private void listenForCallState() {
- mTelephonyListenerManager.addCallStateListener(mPhoneStateListener);
- }
-
- private final TelephonyCallback.CallStateListener mPhoneStateListener =
- new TelephonyCallback.CallStateListener() {
- private int mCallState;
-
- @Override
- public void onCallStateChanged(int state) {
- if (mCallState == state) return;
- if (DEBUG) Log.v(TAG, "Call state changed: " + state);
- mCallState = state;
- refreshUsers(UserHandle.USER_NULL);
- }
- };
-
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Log.v(TAG, "Broadcast: a=" + intent.getAction()
- + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
- }
-
- boolean unpauseRefreshUsers = false;
- int forcePictureLoadForId = UserHandle.USER_NULL;
-
- if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
- if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
- mExitGuestDialog.cancel();
- mExitGuestDialog = null;
- }
-
- final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- final UserInfo userInfo = mUserManager.getUserInfo(currentId);
- final int userCount = mUsers.size();
- for (int i = 0; i < userCount; i++) {
- UserRecord record = mUsers.get(i);
- if (record.info == null) continue;
- boolean shouldBeCurrent = record.info.id == currentId;
- if (record.isCurrent != shouldBeCurrent) {
- mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent));
- }
- if (shouldBeCurrent && !record.isGuest) {
- mLastNonGuestUser = record.info.id;
- }
- if ((userInfo == null || !userInfo.isAdmin()) && record.isRestricted) {
- // Immediately remove restricted records in case the AsyncTask is too slow.
- mUsers.remove(i);
- i--;
- }
- }
- notifyUserSwitchCallbacks();
- notifyAdapters();
-
- // Disconnect from the old secondary user's service
- if (mSecondaryUser != UserHandle.USER_NULL) {
- context.stopServiceAsUser(mSecondaryUserServiceIntent,
- UserHandle.of(mSecondaryUser));
- mSecondaryUser = UserHandle.USER_NULL;
- }
- // Connect to the new secondary user's service (purely to ensure that a persistent
- // SystemUI application is created for that user)
- if (userInfo != null && userInfo.id != UserHandle.USER_SYSTEM) {
- context.startServiceAsUser(mSecondaryUserServiceIntent,
- UserHandle.of(userInfo.id));
- mSecondaryUser = userInfo.id;
- }
- unpauseRefreshUsers = true;
- if (mGuestUserAutoCreated) {
- // Guest user must be scheduled for creation AFTER switching to the target user.
- // This avoids lock contention which will produce UX bugs on the keyguard
- // (b/193933686).
- // TODO(b/191067027): Move guest user recreation to system_server
- guaranteeGuestPresent();
- }
- } else if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) {
- forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL);
- } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
- // Unlocking the system user may require a refresh
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId != UserHandle.USER_SYSTEM) {
- return;
- }
- }
- refreshUsers(forcePictureLoadForId);
- if (unpauseRefreshUsers) {
- mUnpauseRefreshUsers.run();
- }
- }
- };
-
- private final Runnable mUnpauseRefreshUsers = new Runnable() {
- @Override
- public void run() {
- mHandler.removeCallbacks(this);
- mPauseRefreshUsers = false;
- refreshUsers(UserHandle.USER_NULL);
- }
- };
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- pw.println("UserSwitcherController state:");
- pw.println(" mLastNonGuestUser=" + mLastNonGuestUser);
- pw.print(" mUsers.size="); pw.println(mUsers.size());
- for (int i = 0; i < mUsers.size(); i++) {
- final UserRecord u = mUsers.get(i);
- pw.print(" "); pw.println(u.toString());
- }
- pw.println("mSimpleUserSwitcher=" + mSimpleUserSwitcher);
- pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated);
- }
-
- @Override
- public String getCurrentUserName() {
- if (mUsers.isEmpty()) return null;
- UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null);
- if (item == null || item.info == null) return null;
- if (item.isGuest) return mContext.getString(com.android.internal.R.string.guest_name);
- return item.info.name;
- }
-
- @Override
- public void onDensityOrFontScaleChanged() {
- refreshUsers(UserHandle.USER_ALL);
- }
-
- @Override
- public void addAdapter(WeakReference<BaseUserSwitcherAdapter> adapter) {
- mAdapters.add(adapter);
- }
-
- @Override
- public ArrayList<UserRecord> getUsers() {
- return mUsers;
- }
-
- @Override
- public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) {
- UserInfo currentUser = mUserTracker.getUserInfo();
- if (currentUser.id != guestUserId) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not current user (" + currentUser.id + ")");
- return;
- }
- if (!currentUser.isGuest()) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not a guest");
- return;
- }
-
- boolean marked = mUserManager.markGuestForDeletion(currentUser.id);
- if (!marked) {
- Log.w(TAG, "Couldn't mark the guest for deletion for user " + guestUserId);
- return;
- }
-
- if (targetUserId == UserHandle.USER_NULL) {
- // Create a new guest in the foreground, and then immediately switch to it
- createGuestAsync(newGuestId -> {
- if (newGuestId == UserHandle.USER_NULL) {
- Log.e(TAG, "Could not create new guest, switching back to system user");
- switchToUserId(UserHandle.USER_SYSTEM);
- mUserManager.removeUser(currentUser.id);
- try {
- WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't remove guest because ActivityManager "
- + "or WindowManager is dead");
- }
- return;
- }
- switchToUserId(newGuestId);
- mUserManager.removeUser(currentUser.id);
- });
- } else {
- if (mGuestUserAutoCreated) {
- mGuestIsResetting.set(true);
- }
- switchToUserId(targetUserId);
- mUserManager.removeUser(currentUser.id);
- }
- }
-
- @Override
- public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId,
- boolean forceRemoveGuestOnExit) {
- UserInfo currentUser = mUserTracker.getUserInfo();
- if (currentUser.id != guestUserId) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not current user (" + currentUser.id + ")");
- return;
- }
- if (!currentUser.isGuest()) {
- Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
- + " is not a guest");
- return;
- }
-
- int newUserId = UserHandle.USER_SYSTEM;
- if (targetUserId == UserHandle.USER_NULL) {
- // when target user is not specified switch to last non guest user
- if (mLastNonGuestUser != UserHandle.USER_SYSTEM) {
- UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
- if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
- newUserId = info.id;
- }
- }
- } else {
- newUserId = targetUserId;
- }
-
- if (currentUser.isEphemeral() || forceRemoveGuestOnExit) {
- mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
- removeGuestUser(currentUser.id, newUserId);
- } else {
- mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
- switchToUserId(newUserId);
- }
- }
-
- private void scheduleGuestCreation() {
- if (!mGuestCreationScheduled.compareAndSet(false, true)) {
- return;
- }
-
- mLongRunningExecutor.execute(() -> {
- int newGuestId = createGuest();
- mGuestCreationScheduled.set(false);
- mGuestIsResetting.set(false);
- if (newGuestId == UserHandle.USER_NULL) {
- Log.w(TAG, "Could not create new guest while exiting existing guest");
- // Refresh users so that we still display "Guest" if
- // config_guestUserAutoCreated=true
- refreshUsers(UserHandle.USER_NULL);
- }
- });
-
- }
-
- @Override
- public void schedulePostBootGuestCreation() {
- if (isDeviceAllowedToAddGuest()) {
- guaranteeGuestPresent();
- } else {
- mDeviceProvisionedController.addCallback(mGuaranteeGuestPresentAfterProvisioned);
- }
- }
-
- private boolean isDeviceAllowedToAddGuest() {
- return mDeviceProvisionedController.isDeviceProvisioned()
- && !mDevicePolicyManager.isDeviceManaged();
- }
-
- /**
- * If there is no guest on the device, schedule creation of a new guest user in the background.
- */
- private void guaranteeGuestPresent() {
- if (isDeviceAllowedToAddGuest() && mUserManager.findCurrentGuestUser() == null) {
- scheduleGuestCreation();
- }
- }
-
- private void createGuestAsync(Consumer<Integer> callback) {
- final Dialog guestCreationProgressDialog =
- new UserCreatingDialog(mContext, /* isGuest= */true);
- guestCreationProgressDialog.show();
-
- // userManager.createGuest will block the thread so post is needed for the dialog to show
- mBgExecutor.execute(() -> {
- final int guestId = createGuest();
- mUiExecutor.execute(() -> {
- guestCreationProgressDialog.dismiss();
- if (guestId == UserHandle.USER_NULL) {
- Toast.makeText(mContext,
- com.android.settingslib.R.string.add_guest_failed,
- Toast.LENGTH_SHORT).show();
- }
- callback.accept(guestId);
- });
- });
- }
-
- /**
- * Creates a guest user and return its multi-user user ID.
- *
- * This method does not check if a guest already exists before it makes a call to
- * {@link UserManager} to create a new one.
- *
- * @return The multi-user user ID of the newly created guest user, or
- * {@link UserHandle#USER_NULL} if the guest couldn't be created.
- */
- private @UserIdInt int createGuest() {
- UserInfo guest;
- try {
- guest = mUserManager.createGuest(mContext);
- } catch (UserManager.UserOperationException e) {
- Log.e(TAG, "Couldn't create guest user", e);
- return UserHandle.USER_NULL;
- }
- if (guest == null) {
- Log.e(TAG, "Couldn't create guest, most likely because there already exists one");
- return UserHandle.USER_NULL;
- }
- return guest.id;
- }
-
- @Override
- public void init(View view) {
- mView = view;
- }
-
- @Override
- public boolean isKeyguardShowing() {
- return mKeyguardStateController.isShowing();
- }
-
- private boolean shouldUseSimpleUserSwitcher() {
- int defaultSimpleUserSwitcher = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0;
- return mGlobalSettings.getIntForUser(SIMPLE_USER_SWITCHER_GLOBAL_SETTING,
- defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0;
- }
-
- @Override
- public void startActivity(Intent intent) {
- mActivityStarter.startActivity(intent, /* dismissShade= */ true);
- }
-
- @Override
- public void addUserSwitchCallback(UserSwitchCallback callback) {
- mUserSwitchCallbacks.add(callback);
- }
-
- @Override
- public void removeUserSwitchCallback(UserSwitchCallback callback) {
- mUserSwitchCallbacks.remove(callback);
- }
-
- /**
- * Notify user switch callbacks that user has switched.
- */
- private void notifyUserSwitchCallbacks() {
- List<UserSwitchCallback> temp;
- synchronized (mUserSwitchCallbacks) {
- temp = new ArrayList<>(mUserSwitchCallbacks);
- }
- for (UserSwitchCallback callback : temp) {
- callback.onUserSwitched();
- }
- }
-
- private final KeyguardStateController.Callback mCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardShowingChanged() {
-
- // When Keyguard is going away, we don't need to update our items immediately
- // which
- // helps making the transition faster.
- if (!mKeyguardStateController.isShowing()) {
- mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters);
- } else {
- notifyAdapters();
- }
- }
- };
-
- private final DeviceProvisionedController.DeviceProvisionedListener
- mGuaranteeGuestPresentAfterProvisioned =
- new DeviceProvisionedController.DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- if (isDeviceAllowedToAddGuest()) {
- mBgExecutor.execute(
- () -> mDeviceProvisionedController.removeCallback(
- mGuaranteeGuestPresentAfterProvisioned));
- guaranteeGuestPresent();
- }
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 9866389..b135d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
@@ -28,6 +27,7 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Global;
@@ -46,7 +46,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.qs.SettingObserver;
-import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.Utils;
import com.android.systemui.util.settings.GlobalSettings;
@@ -58,14 +58,15 @@
/** Platform implementation of the zen mode controller. **/
@SysUISingleton
-public class ZenModeControllerImpl extends CurrentUserTracker
- implements ZenModeController, Dumpable {
+public class ZenModeControllerImpl implements ZenModeController, Dumpable {
private static final String TAG = "ZenModeController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private final Object mCallbacksLock = new Object();
private final Context mContext;
+ private final UserTracker mUserTracker;
+ private final BroadcastDispatcher mBroadcastDispatcher;
private final SettingObserver mModeSetting;
private final SettingObserver mConfigSetting;
private final NotificationManager mNoMan;
@@ -80,23 +81,45 @@
private long mZenUpdateTime;
private NotificationManager.Policy mConsolidatedNotificationPolicy;
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, Context userContext) {
+ mUserId = newUser;
+ if (mRegistered) {
+ mBroadcastDispatcher.unregisterReceiver(mReceiver);
+ }
+ final IntentFilter filter = new IntentFilter(
+ AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+ filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+ mBroadcastDispatcher.registerReceiver(mReceiver, filter, null,
+ UserHandle.of(mUserId));
+ mRegistered = true;
+ mSetupObserver.register();
+ }
+ };
+
@Inject
public ZenModeControllerImpl(
Context context,
@Main Handler handler,
BroadcastDispatcher broadcastDispatcher,
DumpManager dumpManager,
- GlobalSettings globalSettings) {
- super(broadcastDispatcher);
+ GlobalSettings globalSettings,
+ UserTracker userTracker) {
mContext = context;
- mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE) {
+ mBroadcastDispatcher = broadcastDispatcher;
+ mUserTracker = userTracker;
+ mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE,
+ userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
updateZenMode(value);
fireZenChanged(value);
}
};
- mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG) {
+ mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG,
+ userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
updateZenModeConfig();
@@ -112,7 +135,7 @@
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
mUserManager = context.getSystemService(UserManager.class);
- startTracking();
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
@@ -120,7 +143,7 @@
@Override
public boolean isVolumeRestricted() {
return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME,
- new UserHandle(mUserId));
+ UserHandle.of(mUserId));
}
@Override
@@ -183,19 +206,6 @@
}
@Override
- public void onUserSwitched(int userId) {
- mUserId = userId;
- if (mRegistered) {
- mContext.unregisterReceiver(mReceiver);
- }
- final IntentFilter filter = new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
- mContext.registerReceiverAsUser(mReceiver, new UserHandle(mUserId), filter, null, null);
- mRegistered = true;
- mSetupObserver.register();
- }
-
- @Override
public ComponentName getEffectsSuppressor() {
return NotificationManager.from(mContext).getEffectsSuppressor();
}
@@ -208,7 +218,7 @@
@Override
public int getCurrentUser() {
- return ActivityManager.getCurrentUser();
+ return mUserTracker.getUserId();
}
private void fireNextAlarmChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index b1b45b5..1b73539 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -58,8 +58,6 @@
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl;
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.statusbar.policy.WalletControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -198,8 +196,4 @@
static DataSaverController provideDataSaverController(NetworkController networkController) {
return networkController.getDataSaverController();
}
-
- /** Binds {@link UserSwitcherController} to its implementation. */
- @Binds
- UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
index 48df15c..93e78ac 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleController.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import androidx.annotation.VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
index c7f0b7e..f558fee 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/MultiRippleView.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.content.Context
import android.graphics.Canvas
@@ -31,11 +31,21 @@
class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
internal val ripples = ArrayList<RippleAnimation>()
+ private val listeners = ArrayList<RipplesFinishedListener>()
private val ripplePaint = Paint()
private var isWarningLogged = false
companion object {
- const val TAG = "MultiRippleView"
+ private const val TAG = "MultiRippleView"
+
+ interface RipplesFinishedListener {
+ /** Triggered when all the ripples finish running. */
+ fun onRipplesFinish()
+ }
+ }
+
+ fun addRipplesFinishedListener(listener: RipplesFinishedListener) {
+ listeners.add(listener)
}
override fun onDraw(canvas: Canvas?) {
@@ -62,6 +72,10 @@
shouldInvalidate = shouldInvalidate || anim.isPlaying()
}
- if (shouldInvalidate) invalidate()
+ if (shouldInvalidate) {
+ invalidate()
+ } else { // Nothing is playing.
+ listeners.forEach { listener -> listener.onRipplesFinish() }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
index aca9e25..b2f8994 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
index 8812254..773ac55 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.Color
@@ -27,6 +27,6 @@
companion object {
const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt()
- const val RIPPLE_DEFAULT_ALPHA: Int = 45 // full opacity is 255.
+ const val RIPPLE_DEFAULT_ALPHA: Int = 115 // full opacity is 255.
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index d2f3a6a..a950d34 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.PointF
import android.graphics.RuntimeShader
import android.util.MathUtils
+import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
/**
* Shader class that renders an expanding ripple effect. The ripple contains three elements:
@@ -31,7 +33,7 @@
* Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
*/
class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.CIRCLE) :
- RuntimeShader(buildShader(rippleShape)) {
+ RuntimeShader(buildShader(rippleShape)) {
/** Shapes that the [RippleShader] supports. */
enum class RippleShape {
@@ -39,25 +41,30 @@
ROUNDED_BOX,
ELLIPSE
}
- //language=AGSL
+ // language=AGSL
companion object {
- private const val SHADER_UNIFORMS = """uniform vec2 in_center;
- uniform vec2 in_size;
- uniform float in_progress;
- uniform float in_cornerRadius;
- uniform float in_thickness;
- uniform float in_time;
- uniform float in_distort_radial;
- uniform float in_distort_xy;
- uniform float in_fadeSparkle;
- uniform float in_fadeFill;
- uniform float in_fadeRing;
- uniform float in_blur;
- uniform float in_pixelDensity;
- layout(color) uniform vec4 in_color;
- uniform float in_sparkle_strength;"""
+ private const val SHADER_UNIFORMS =
+ """
+ uniform vec2 in_center;
+ uniform vec2 in_size;
+ uniform float in_progress;
+ uniform float in_cornerRadius;
+ uniform float in_thickness;
+ uniform float in_time;
+ uniform float in_distort_radial;
+ uniform float in_distort_xy;
+ uniform float in_fadeSparkle;
+ uniform float in_fadeFill;
+ uniform float in_fadeRing;
+ uniform float in_blur;
+ uniform float in_pixelDensity;
+ layout(color) uniform vec4 in_color;
+ uniform float in_sparkle_strength;
+ """
- private const val SHADER_CIRCLE_MAIN = """vec4 main(vec2 p) {
+ private const val SHADER_CIRCLE_MAIN =
+ """
+ vec4 main(vec2 p) {
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);
@@ -73,7 +80,9 @@
}
"""
- private const val SHADER_ROUNDED_BOX_MAIN = """vec4 main(vec2 p) {
+ private const val SHADER_ROUNDED_BOX_MAIN =
+ """
+ vec4 main(vec2 p) {
float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
in_thickness), in_blur);
float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius),
@@ -89,7 +98,9 @@
}
"""
- private const val SHADER_ELLIPSE_MAIN = """vec4 main(vec2 p) {
+ private const val SHADER_ELLIPSE_MAIN =
+ """
+ vec4 main(vec2 p) {
vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
@@ -105,22 +116,31 @@
}
"""
- private const val CIRCLE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
- SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.CIRCLE_SDF +
+ private const val CIRCLE_SHADER =
+ SHADER_UNIFORMS +
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.CIRCLE_SDF +
SHADER_CIRCLE_MAIN
- private const val ROUNDED_BOX_SHADER = SHADER_UNIFORMS +
- RippleShaderUtilLibrary.SHADER_LIB + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
- SdfShaderLibrary.ROUNDED_BOX_SDF + SHADER_ROUNDED_BOX_MAIN
- private const val ELLIPSE_SHADER = SHADER_UNIFORMS + RippleShaderUtilLibrary.SHADER_LIB +
- SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SdfShaderLibrary.ELLIPSE_SDF +
+ private const val ROUNDED_BOX_SHADER =
+ SHADER_UNIFORMS +
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.ROUNDED_BOX_SDF +
+ SHADER_ROUNDED_BOX_MAIN
+ private const val ELLIPSE_SHADER =
+ SHADER_UNIFORMS +
+ ShaderUtilLibrary.SHADER_LIB +
+ SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
+ SdfShaderLibrary.ELLIPSE_SDF +
SHADER_ELLIPSE_MAIN
private fun buildShader(rippleShape: RippleShape): String =
- when (rippleShape) {
- RippleShape.CIRCLE -> CIRCLE_SHADER
- RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
- RippleShape.ELLIPSE -> ELLIPSE_SHADER
- }
+ when (rippleShape) {
+ RippleShape.CIRCLE -> CIRCLE_SHADER
+ RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
+ RippleShape.ELLIPSE -> ELLIPSE_SHADER
+ }
private fun subProgress(start: Float, end: Float, progress: Float): Float {
val min = Math.min(start, end)
@@ -130,9 +150,7 @@
}
}
- /**
- * Sets the center position of the ripple.
- */
+ /** Sets the center position of the ripple. */
fun setCenter(x: Float, y: Float) {
setFloatUniform("in_center", x, y)
}
@@ -144,21 +162,21 @@
maxSize.y = height
}
- /**
- * Progress of the ripple. Float value between [0, 1].
- */
+ /** Progress of the ripple. Float value between [0, 1]. */
var progress: Float = 0.0f
set(value) {
field = value
setFloatUniform("in_progress", value)
val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
- setFloatUniform("in_size", /* width= */ maxSize.x * curvedProg,
- /* height= */ maxSize.y * curvedProg)
+ setFloatUniform(
+ "in_size",
+ /* width= */ maxSize.x * curvedProg,
+ /* height= */ maxSize.y * curvedProg
+ )
setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
// radius should not exceed width and height values.
- setFloatUniform("in_cornerRadius",
- Math.min(maxSize.x, maxSize.y) * curvedProg)
+ setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
@@ -175,18 +193,14 @@
setFloatUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
}
- /**
- * Play time since the start of the effect.
- */
+ /** Play time since the start of the effect. */
var time: Float = 0.0f
set(value) {
field = value
setFloatUniform("in_time", value)
}
- /**
- * A hex value representing the ripple color, in the format of ARGB
- */
+ /** A hex value representing the ripple color, in the format of ARGB */
var color: Int = 0xffffff
set(value) {
field = value
@@ -194,9 +208,9 @@
}
/**
- * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus
- * with strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1
- * it's opaque white and looks the most grainy.
+ * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus with
+ * strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1 it's
+ * opaque white and looks the most grainy.
*/
var sparkleStrength: Float = 0.0f
set(value) {
@@ -204,9 +218,7 @@
setFloatUniform("in_sparkle_strength", value)
}
- /**
- * Distortion strength of the ripple. Expected value between[0, 1].
- */
+ /** Distortion strength of the ripple. Expected value between[0, 1]. */
var distortionStrength: Float = 0.0f
set(value) {
field = value
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index a6d7930..ae28a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -26,7 +26,7 @@
import android.util.AttributeSet
import android.view.View
import androidx.core.graphics.ColorUtils
-import com.android.systemui.ripple.RippleShader.RippleShape
+import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape
/**
* A generic expanding ripple effect.
@@ -41,7 +41,7 @@
private set
private val ripplePaint = Paint()
- private val animator = ValueAnimator.ofFloat(0f, 1f)
+ protected val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
var duration: Long = 1750
@@ -98,17 +98,20 @@
rippleShader.time = now.toFloat()
invalidate()
}
- animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator?) {
- onAnimationEnd?.run()
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ onAnimationEnd?.run()
+ }
}
- })
+ )
animator.start()
}
- /** Set the color to be used for the ripple.
+ /**
+ * Set the color to be used for the ripple.
*
- * The alpha value of the color will be applied to the ripple. The alpha range is [0-100].
+ * The alpha value of the color will be applied to the ripple. The alpha range is [0-255].
*/
fun setColor(color: Int, alpha: Int = RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA) {
rippleShader.color = ColorUtils.setAlphaComponent(color, alpha)
@@ -123,9 +126,7 @@
rippleShader.rippleFill = rippleFill
}
- /**
- * Set the intensity of the sparkles.
- */
+ /** Set the intensity of the sparkles. */
fun setSparkleStrength(strength: Float) {
rippleShader.sparkleStrength = strength
}
@@ -143,20 +144,30 @@
// active effect area. Values here should be kept in sync with the animation implementation
// in the ripple shader.
if (rippleShape == RippleShape.CIRCLE) {
- val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * maxWidth
+ val maskRadius =
+ (1 -
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxWidth
canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint)
} else {
- val maskWidth = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * maxWidth * 2
- val maskHeight = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * maxHeight * 2
+ val maskWidth =
+ (1 -
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxWidth * 2
+ val maskHeight =
+ (1 -
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress) *
+ (1 - rippleShader.progress)) * maxHeight * 2
canvas.drawRect(
- /* left= */ centerX - maskWidth,
- /* top= */ centerY - maskHeight,
- /* right= */ centerX + maskWidth,
- /* bottom= */ centerY + maskHeight,
- ripplePaint)
+ /* left= */ centerX - maskWidth,
+ /* top= */ centerY - maskHeight,
+ /* right= */ centerX + maskWidth,
+ /* bottom= */ centerY + maskHeight,
+ ripplePaint
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
rename to packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
index 5e256c6..8b2f466 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt
@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.shaderutil
/** Library class that contains 2D signed distance functions. */
class SdfShaderLibrary {
- //language=AGSL
+ // language=AGSL
companion object {
- const val CIRCLE_SDF = """
+ const val CIRCLE_SDF =
+ """
float sdCircle(vec2 p, float r) {
return (length(p)-r) / r;
}
@@ -34,7 +35,8 @@
}
"""
- const val ROUNDED_BOX_SDF = """
+ const val ROUNDED_BOX_SDF =
+ """
float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) {
size *= 0.5;
cornerRadius *= 0.5;
@@ -58,7 +60,8 @@
// Used non-trigonometry parametrization and Halley's method (iterative) for root finding.
// This is more expensive than the regular circle SDF, recommend to use the circle SDF if
// possible.
- const val ELLIPSE_SDF = """float sdEllipse(vec2 p, vec2 wh) {
+ const val ELLIPSE_SDF =
+ """float sdEllipse(vec2 p, vec2 wh) {
wh *= 0.5;
// symmetry
@@ -98,7 +101,8 @@
}
"""
- const val SHADER_SDF_OPERATION_LIB = """
+ const val SHADER_SDF_OPERATION_LIB =
+ """
float soften(float d, float blur) {
float blurHalf = blur * 0.5;
return smoothstep(-blurHalf, blurHalf, d);
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
new file mode 100644
index 0000000..d78e0c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.shaderutil
+
+/** A common utility functions that are used for computing shaders. */
+class ShaderUtilLibrary {
+ // language=AGSL
+ companion object {
+ const val SHADER_LIB =
+ """
+ float triangleNoise(vec2 n) {
+ n = fract(n * vec2(5.3987, 5.4421));
+ n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
+ float xy = n.x * n.y;
+ // compute in [0..2[ and remap to [-1.0..1.0[
+ return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
+ }
+
+ const float PI = 3.1415926535897932384626;
+
+ float sparkles(vec2 uv, float t) {
+ float n = triangleNoise(uv);
+ float s = 0.0;
+ for (float i = 0; i < 4; i += 1) {
+ float l = i * 0.01;
+ float h = l + 0.1;
+ float o = smoothstep(n - l, h, n);
+ o *= abs(sin(PI * o * (t + 0.55 * i)));
+ s += o;
+ }
+ return s;
+ }
+
+ vec2 distort(vec2 p, float time, float distort_amount_radial,
+ float distort_amount_xy) {
+ float angle = atan(p.y, p.x);
+ return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
+ cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
+ + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
+ cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
+ }
+
+ // Return range [-1, 1].
+ vec3 hash(vec3 p) {
+ p = fract(p * vec3(.3456, .1234, .9876));
+ p += dot(p, p.yxz + 43.21);
+ p = (p.xxy + p.yxx) * p.zyx;
+ return (fract(sin(p) * 4567.1234567) - .5) * 2.;
+ }
+
+ // Skew factors (non-uniform).
+ const float SKEW = 0.3333333; // 1/3
+ const float UNSKEW = 0.1666667; // 1/6
+
+ // Return range roughly [-1,1].
+ // It's because the hash function (that returns a random gradient vector) returns
+ // different magnitude of vectors. Noise doesn't have to be in the precise range thus
+ // skipped normalize.
+ float simplex3d(vec3 p) {
+ // Skew the input coordinate, so that we get squashed cubical grid
+ vec3 s = floor(p + (p.x + p.y + p.z) * SKEW);
+
+ // Unskew back
+ vec3 u = s - (s.x + s.y + s.z) * UNSKEW;
+
+ // Unskewed coordinate that is relative to p, to compute the noise contribution
+ // based on the distance.
+ vec3 c0 = p - u;
+
+ // We have six simplices (in this case tetrahedron, since we are in 3D) that we
+ // could possibly in.
+ // Here, we are finding the correct tetrahedron (simplex shape), and traverse its
+ // four vertices (c0..3) when computing noise contribution.
+ // The way we find them is by comparing c0's x,y,z values.
+ // For example in 2D, we can find the triangle (simplex shape in 2D) that we are in
+ // by comparing x and y values. i.e. x>y lower, x<y, upper triangle.
+ // Same applies in 3D.
+ //
+ // Below indicates the offsets (or offset directions) when c0=(x0,y0,z0)
+ // x0>y0>z0: (1,0,0), (1,1,0), (1,1,1)
+ // x0>z0>y0: (1,0,0), (1,0,1), (1,1,1)
+ // z0>x0>y0: (0,0,1), (1,0,1), (1,1,1)
+ // z0>y0>x0: (0,0,1), (0,1,1), (1,1,1)
+ // y0>z0>x0: (0,1,0), (0,1,1), (1,1,1)
+ // y0>x0>z0: (0,1,0), (1,1,0), (1,1,1)
+ //
+ // The rule is:
+ // * For offset1, set 1 at the max component, otherwise 0.
+ // * For offset2, set 0 at the min component, otherwise 1.
+ // * For offset3, set 1 for all.
+ //
+ // Encode x0-y0, y0-z0, z0-x0 in a vec3
+ vec3 en = c0 - c0.yzx;
+ // Each represents whether x0>y0, y0>z0, z0>x0
+ en = step(vec3(0.), en);
+ // en.zxy encodes z0>x0, x0>y0, y0>x0
+ vec3 offset1 = en * (1. - en.zxy); // find max
+ vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min)
+ vec3 offset3 = vec3(1.);
+
+ vec3 c1 = c0 - offset1 + UNSKEW;
+ vec3 c2 = c0 - offset2 + UNSKEW * 2.;
+ vec3 c3 = c0 - offset3 + UNSKEW * 3.;
+
+ // Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution)
+ //
+ // First compute d^2, squared distance to the point.
+ vec4 w; // w = max(0, r^2 - d^2))
+ w.x = dot(c0, c0);
+ w.y = dot(c1, c1);
+ w.z = dot(c2, c2);
+ w.w = dot(c3, c3);
+
+ // Noise contribution should decay to zero before they cross the simplex boundary.
+ // Usually r^2 is 0.5 or 0.6;
+ // 0.5 ensures continuity but 0.6 increases the visual quality for the application
+ // where discontinuity isn't noticeable.
+ w = max(0.6 - w, 0.);
+
+ // Noise contribution from each point.
+ vec4 nc;
+ nc.x = dot(hash(s), c0);
+ nc.y = dot(hash(s + offset1), c1);
+ nc.z = dot(hash(s + offset2), c2);
+ nc.w = dot(hash(s + offset3), c3);
+
+ nc *= w*w*w*w;
+
+ // Add all the noise contributions.
+ // Should multiply by the possible max contribution to adjust the range in [-1,1].
+ return dot(vec4(32.), nc);
+ }
+ """
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
new file mode 100644
index 0000000..5ac3aad7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.graphics.BlendMode
+import android.graphics.Color
+
+/** Turbulence noise animation configuration. */
+data class TurbulenceNoiseAnimationConfig(
+ /** The number of grids that is used to generate noise. */
+ val gridCount: Float = DEFAULT_NOISE_GRID_COUNT,
+
+ /** Multiplier for the noise luma matte. Increase this for brighter effects. */
+ val luminosityMultiplier: Float = DEFAULT_LUMINOSITY_MULTIPLIER,
+
+ /**
+ * Noise move speed variables.
+ *
+ * Its sign determines the direction; magnitude determines the speed. <ul>
+ * ```
+ * <li> [noiseMoveSpeedX] positive: right to left; negative: left to right.
+ * <li> [noiseMoveSpeedY] positive: bottom to top; negative: top to bottom.
+ * <li> [noiseMoveSpeedZ] its sign doesn't matter much, as it moves in Z direction. Use it
+ * to add turbulence in place.
+ * ```
+ * </ul>
+ */
+ val noiseMoveSpeedX: Float = 0f,
+ val noiseMoveSpeedY: Float = 0f,
+ val noiseMoveSpeedZ: Float = DEFAULT_NOISE_SPEED_Z,
+
+ /** Color of the effect. */
+ var color: Int = DEFAULT_COLOR,
+ /** Background color of the effect. */
+ val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
+ val opacity: Int = DEFAULT_OPACITY,
+ val width: Float = 0f,
+ val height: Float = 0f,
+ val duration: Float = DEFAULT_NOISE_DURATION_IN_MILLIS,
+ val pixelDensity: Float = 1f,
+ val blendMode: BlendMode = DEFAULT_BLEND_MODE,
+ val onAnimationEnd: Runnable? = null
+) {
+ companion object {
+ const val DEFAULT_NOISE_DURATION_IN_MILLIS = 7500F
+ const val DEFAULT_LUMINOSITY_MULTIPLIER = 1f
+ const val DEFAULT_NOISE_GRID_COUNT = 1.2f
+ const val DEFAULT_NOISE_SPEED_Z = 0.3f
+ const val DEFAULT_OPACITY = 150 // full opacity is 255.
+ const val DEFAULT_COLOR = Color.WHITE
+ const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
+ val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
new file mode 100644
index 0000000..4c7e5f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseController.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+/** A controller that plays [TurbulenceNoiseView]. */
+class TurbulenceNoiseController(private val turbulenceNoiseView: TurbulenceNoiseView) {
+ /** Updates the color of the noise. */
+ fun updateNoiseColor(color: Int) {
+ turbulenceNoiseView.updateColor(color)
+ }
+
+ // TODO: add cancel and/ or pause once design requirements become clear.
+ /** Plays [TurbulenceNoiseView] with the given config. */
+ fun play(turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig) {
+ turbulenceNoiseView.play(turbulenceNoiseAnimationConfig)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
new file mode 100644
index 0000000..19c114d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.graphics.RuntimeShader
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+import java.lang.Float.max
+
+/** Shader that renders turbulence simplex noise, with no octave. */
+class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
+ // language=AGSL
+ companion object {
+ private const val UNIFORMS =
+ """
+ uniform float in_gridNum;
+ uniform vec3 in_noiseMove;
+ uniform vec2 in_size;
+ uniform float in_aspectRatio;
+ uniform float in_opacity;
+ uniform float in_pixelDensity;
+ layout(color) uniform vec4 in_color;
+ layout(color) uniform vec4 in_backgroundColor;
+ """
+
+ private const val SHADER_LIB =
+ """
+ float getLuminosity(vec3 c) {
+ return 0.3*c.r + 0.59*c.g + 0.11*c.b;
+ }
+
+ vec3 maskLuminosity(vec3 dest, float lum) {
+ dest.rgb *= vec3(lum);
+ // Clip back into the legal range
+ dest = clamp(dest, vec3(0.), vec3(1.0));
+ return dest;
+ }
+ """
+
+ private const val MAIN_SHADER =
+ """
+ vec4 main(vec2 p) {
+ vec2 uv = p / in_size.xy;
+ uv.x *= in_aspectRatio;
+
+ vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
+ float luma = simplex3d(noiseP) * in_opacity;
+ vec3 mask = maskLuminosity(in_color.rgb, luma);
+ vec3 color = in_backgroundColor.rgb + mask * 0.6;
+
+ // Add dither with triangle distribution to avoid color banding. Ok to dither in the
+ // shader here as we are in gamma space.
+ float dither = triangleNoise(p * in_pixelDensity) / 255.;
+
+ // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
+ // multiply rgb with a to get the correct result.
+ color = (color + dither.rrr) * in_color.a;
+ return vec4(color, in_color.a);
+ }
+ """
+
+ private const val TURBULENCE_NOISE_SHADER =
+ ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SHADER_LIB + MAIN_SHADER
+ }
+
+ /** Sets the number of grid for generating noise. */
+ fun setGridCount(gridNumber: Float = 1.0f) {
+ setFloatUniform("in_gridNum", gridNumber)
+ }
+
+ /**
+ * Sets the pixel density of the screen.
+ *
+ * Used it for noise dithering.
+ */
+ fun setPixelDensity(pixelDensity: Float) {
+ setFloatUniform("in_pixelDensity", pixelDensity)
+ }
+
+ /** Sets the noise color of the effect. */
+ fun setColor(color: Int) {
+ setColorUniform("in_color", color)
+ }
+
+ /** Sets the background color of the effect. */
+ fun setBackgroundColor(color: Int) {
+ setColorUniform("in_backgroundColor", color)
+ }
+
+ /**
+ * Sets the opacity to achieve fade in/ out of the animation.
+ *
+ * Expected value range is [1, 0].
+ */
+ fun setOpacity(opacity: Float) {
+ setFloatUniform("in_opacity", opacity)
+ }
+
+ /** Sets the size of the shader. */
+ fun setSize(width: Float, height: Float) {
+ setFloatUniform("in_size", width, height)
+ setFloatUniform("in_aspectRatio", width / max(height, 0.001f))
+ }
+
+ /** Sets noise move speed in x, y, and z direction. */
+ fun setNoiseMove(x: Float, y: Float, z: Float) {
+ setFloatUniform("in_noiseMove", x, y, z)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
new file mode 100644
index 0000000..8649d59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.core.graphics.ColorUtils
+import java.util.Random
+import kotlin.math.sin
+
+/** View that renders turbulence noise effect. */
+class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+
+ companion object {
+ private const val MS_TO_SEC = 0.001f
+ private const val TWO_PI = Math.PI.toFloat() * 2f
+ }
+
+ @VisibleForTesting val turbulenceNoiseShader = TurbulenceNoiseShader()
+ private val paint = Paint().apply { this.shader = turbulenceNoiseShader }
+ private val random = Random()
+ private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+ private var config: TurbulenceNoiseAnimationConfig? = null
+
+ val isPlaying: Boolean
+ get() = animator.isRunning
+
+ init {
+ // Only visible during the animation.
+ visibility = INVISIBLE
+ }
+
+ /** Updates the color during the animation. No-op if there's no animation playing. */
+ fun updateColor(color: Int) {
+ config?.let {
+ it.color = color
+ applyConfig(it)
+ }
+ }
+
+ override fun onDraw(canvas: Canvas?) {
+ if (canvas == null || !canvas.isHardwareAccelerated) {
+ // Drawing with the turbulence noise shader requires hardware acceleration, so skip
+ // if it's unsupported.
+ return
+ }
+
+ canvas.drawPaint(paint)
+ }
+
+ internal fun play(config: TurbulenceNoiseAnimationConfig) {
+ if (isPlaying) {
+ return // Ignore if the animation is playing.
+ }
+ visibility = VISIBLE
+ applyConfig(config)
+
+ // Add random offset to avoid same patterned noise.
+ val offsetX = random.nextFloat()
+ val offsetY = random.nextFloat()
+
+ animator.duration = config.duration.toLong()
+ animator.addUpdateListener { updateListener ->
+ val timeInSec = updateListener.currentPlayTime * MS_TO_SEC
+ // Remap [0,1] to [0, 2*PI]
+ val progress = TWO_PI * updateListener.animatedValue as Float
+
+ turbulenceNoiseShader.setNoiseMove(
+ offsetX + timeInSec * config.noiseMoveSpeedX,
+ offsetY + timeInSec * config.noiseMoveSpeedY,
+ timeInSec * config.noiseMoveSpeedZ
+ )
+
+ // Fade in and out the noise as the animation progress.
+ // TODO: replace it with a better curve
+ turbulenceNoiseShader.setOpacity(sin(TWO_PI - progress) * config.luminosityMultiplier)
+
+ invalidate()
+ }
+
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ visibility = INVISIBLE
+ config.onAnimationEnd?.run()
+ }
+ }
+ )
+ animator.start()
+ }
+
+ private fun applyConfig(config: TurbulenceNoiseAnimationConfig) {
+ this.config = config
+ with(turbulenceNoiseShader) {
+ setGridCount(config.gridCount)
+ setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
+ setBackgroundColor(config.backgroundColor)
+ setSize(config.width, config.height)
+ setPixelDensity(config.pixelDensity)
+ }
+ paint.blendMode = config.blendMode
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 8270336..a9d05d1 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -94,6 +94,13 @@
private var wakeReasonAcquired: String? = null
/**
+ * A stack of pairs of device id and temporary view info. This is used when there may be
+ * multiple devices in range, and we want to always display the chip for the most recently
+ * active device.
+ */
+ internal val activeViews: ArrayDeque<Pair<String, T>> = ArrayDeque()
+
+ /**
* Displays the view with the provided [newInfo].
*
* This method handles inflating and attaching the view, then delegates to [updateView] to
@@ -102,6 +109,12 @@
fun displayView(newInfo: T) {
val currentDisplayInfo = displayInfo
+ // Update our list of active devices by removing it if necessary, then adding back at the
+ // front of the list
+ val id = newInfo.id
+ val position = findAndRemoveFromActiveViewsList(id)
+ activeViews.addFirst(Pair(id, newInfo))
+
if (currentDisplayInfo != null &&
currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
// We're already displaying information in the correctly-titled window, so we just need
@@ -113,7 +126,10 @@
// We're already displaying information but that information is under a different
// window title. So, we need to remove the old window with the old title and add a
// new window with the new title.
- removeView(removalReason = "New info has new window title: ${newInfo.windowTitle}")
+ removeView(
+ id,
+ removalReason = "New info has new window title: ${newInfo.windowTitle}"
+ )
}
// At this point, we're guaranteed to no longer be displaying a view.
@@ -140,7 +156,7 @@
}
wakeLock?.acquire(newInfo.wakeReason)
wakeReasonAcquired = newInfo.wakeReason
- logger.logViewAddition(newInfo.windowTitle)
+ logger.logViewAddition(id, newInfo.windowTitle)
inflateAndUpdateView(newInfo)
}
@@ -151,9 +167,13 @@
// include it just to be safe.
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
)
- cancelViewTimeout?.run()
+
+ // Only cancel timeout of the most recent view displayed, as it will be reset.
+ if (position == 0) {
+ cancelViewTimeout?.run()
+ }
cancelViewTimeout = mainExecutor.executeDelayed(
- { removeView(REMOVAL_REASON_TIMEOUT) },
+ { removeView(id, REMOVAL_REASON_TIMEOUT) },
timeout.toLong()
)
}
@@ -196,28 +216,67 @@
}
/**
- * Hides the view.
+ * Hides the view given its [id].
*
+ * @param id the id of the device responsible of displaying the temp view.
* @param removalReason a short string describing why the view was removed (timeout, state
* change, etc.)
*/
- fun removeView(removalReason: String) {
+ fun removeView(id: String, removalReason: String) {
val currentDisplayInfo = displayInfo ?: return
+ val removalPosition = findAndRemoveFromActiveViewsList(id)
+ if (removalPosition == null) {
+ logger.logViewRemovalIgnored(id, "view not found in the list")
+ return
+ }
+ if (removalPosition != 0) {
+ logger.logViewRemovalIgnored(id, "most recent view is being displayed.")
+ return
+ }
+ logger.logViewRemoval(id, removalReason)
+
+ val newViewToDisplay = if (activeViews.isEmpty()) {
+ null
+ } else {
+ activeViews[0].second
+ }
+
val currentView = currentDisplayInfo.view
animateViewOut(currentView) {
windowManager.removeView(currentView)
wakeLock?.release(wakeReasonAcquired)
}
- logger.logViewRemoval(removalReason)
configurationController.removeCallback(displayScaleListener)
// Re-set to null immediately (instead as part of the animation end runnable) so
- // that if a new view event comes in while this view is animating out, we still display the
- // new view appropriately.
+ // that if a new view event comes in while this view is animating out, we still display
+ // the new view appropriately.
displayInfo = null
// No need to time the view out since it's already gone
cancelViewTimeout?.run()
+
+ if (newViewToDisplay != null) {
+ mainExecutor.executeDelayed({ displayView(newViewToDisplay)}, DISPLAY_VIEW_DELAY)
+ }
+ }
+
+ /**
+ * Finds and removes the active view with the given [id] from the stack, or null if there is no
+ * active view with that ID
+ *
+ * @param id that temporary view belonged to.
+ *
+ * @return index of the view in the stack , otherwise null.
+ */
+ private fun findAndRemoveFromActiveViewsList(id: String): Int? {
+ for (i in 0 until activeViews.size) {
+ if (activeViews[i].first == id) {
+ activeViews.removeAt(i)
+ return i
+ }
+ }
+ return null
}
/**
@@ -258,6 +317,7 @@
}
private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
+const val DISPLAY_VIEW_DELAY = 50L
private data class IconInfo(
val iconName: String,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
index cbb5002..df83960 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
@@ -37,6 +37,11 @@
* disappears.
*/
open val timeoutMs: Int = DEFAULT_TIMEOUT_MILLIS
+
+ /**
+ * The id of the temporary view.
+ */
+ abstract val id: String
}
const val DEFAULT_TIMEOUT_MILLIS = 10000
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index 428a104..133a384 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -24,13 +24,42 @@
internal val buffer: LogBuffer,
internal val tag: String,
) {
- /** Logs that we added the view in a window titled [windowTitle]. */
- fun logViewAddition(windowTitle: String) {
- buffer.log(tag, LogLevel.DEBUG, { str1 = windowTitle }, { "View added. window=$str1" })
+ /** Logs that we added the view with the given [id] in a window titled [windowTitle]. */
+ fun logViewAddition(id: String, windowTitle: String) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = windowTitle
+ str2 = id
+ },
+ { "View added. window=$str1 id=$str2" }
+ )
}
- /** Logs that we removed the chip for the given [reason]. */
- fun logViewRemoval(reason: String) {
- buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "View removed due to: $str1" })
+ /** Logs that we removed the view with the given [id] for the given [reason]. */
+ fun logViewRemoval(id: String, reason: String) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = reason
+ str2 = id
+ },
+ { "View with id=$str2 is removed due to: $str1" }
+ )
+ }
+
+ /** Logs that we ignored removal of the view with the given [id]. */
+ fun logViewRemovalIgnored(id: String, reason: String) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = reason
+ str2 = id
+ },
+ { "Removal of view with id=$str2 is ignored because $str1" }
+ )
}
}
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 44e5ce8..fb17b69 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -174,7 +174,7 @@
chipInnerView,
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_DECELERATE,
- duration = ANIMATION_DURATION,
+ duration = ANIMATION_IN_DURATION,
includeMargins = true,
includeFadeIn = true,
// We can only request focus once the animation finishes.
@@ -187,7 +187,7 @@
view.requireViewById<ViewGroup>(R.id.chipbar_inner),
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_ACCELERATE,
- ANIMATION_DURATION,
+ ANIMATION_OUT_DURATION,
includeMargins = true,
onAnimationEnd,
)
@@ -208,4 +208,5 @@
}
}
-private const val ANIMATION_DURATION = 500L
+private const val ANIMATION_IN_DURATION = 500L
+private const val ANIMATION_OUT_DURATION = 250L
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 6237365..b92e0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -40,6 +40,7 @@
override val windowTitle: String,
override val wakeReason: String,
override val timeoutMs: Int,
+ override val id: String,
) : TemporaryViewInfo()
/** The possible items to display at the end of the chipbar. */
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index 26cc6ba3..f3b9cc1 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -25,8 +25,8 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.ToastPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index fe183fc..4999515 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -38,9 +38,9 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.PluginEnablerImpl;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginActionManager;
import com.android.systemui.shared.plugins.PluginEnabler;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginPrefs;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
index 0f06144..6216acd 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
@@ -92,6 +92,7 @@
deviceStateManager.registerCallback(executor, FoldListener())
wakefulnessLifecycle.addObserver(this)
+ // TODO(b/254878364): remove this call to NPVC.getView()
centralSurfaces.notificationPanelViewController.view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) { listenForDozing(this) }
}
@@ -157,6 +158,7 @@
// We don't need to wait for the scrim as it is already displayed
// but we should wait for the initial animation preparations to be drawn
// (setting initial alpha/translation)
+ // TODO(b/254878364): remove this call to NPVC.getView()
OneShotPreDrawListener.add(
centralSurfaces.notificationPanelViewController.view,
onReady
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 6ed3a09..ae30ca0 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -15,7 +15,7 @@
*/
package com.android.systemui.unfold
-import android.animation.ValueAnimator
+import android.content.ContentResolver
import android.content.Context
import android.graphics.PixelFormat
import android.hardware.devicestate.DeviceStateManager
@@ -39,6 +39,7 @@
import com.android.systemui.statusbar.LinearLightRevealEffect
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
import com.android.systemui.util.traceSection
import com.android.wm.shell.displayareahelper.DisplayAreaHelper
import java.util.Optional
@@ -52,6 +53,7 @@
constructor(
private val context: Context,
private val deviceStateManager: DeviceStateManager,
+ private val contentResolver: ContentResolver,
private val displayManager: DisplayManager,
private val unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
private val displayAreaHelper: Optional<DisplayAreaHelper>,
@@ -92,7 +94,7 @@
overlayContainer = builder.build()
SurfaceControl.Transaction()
- .setLayer(overlayContainer, Integer.MAX_VALUE)
+ .setLayer(overlayContainer, UNFOLD_OVERLAY_LAYER_Z_INDEX)
.show(overlayContainer)
.apply()
@@ -117,7 +119,7 @@
Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
try {
// Add the view only if we are unfolding and this is the first screen on
- if (!isFolded && !isUnfoldHandled && ValueAnimator.areAnimatorsEnabled()) {
+ if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
addView(onOverlayReady)
isUnfoldHandled = true
} else {
@@ -162,11 +164,10 @@
// blocker (turn on the brightness) only when the content is actually visible as it
// might be presented only in the next frame.
// See b/197538198
- transaction
- .setFrameTimelineVsync(vsyncId)
- .apply()
+ transaction.setFrameTimelineVsync(vsyncId).apply()
- transaction.setFrameTimelineVsync(vsyncId + 1)
+ transaction
+ .setFrameTimelineVsync(vsyncId + 1)
.addTransactionCommittedListener(backgroundExecutor) {
Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
callback.run()
@@ -218,8 +219,7 @@
}
private fun getUnfoldedDisplayInfo(): DisplayInfo =
- displayManager
- .displays
+ displayManager.displays
.asSequence()
.map { DisplayInfo().apply { it.getDisplayInfo(this) } }
.filter { it.type == Display.TYPE_INTERNAL }
@@ -266,5 +266,14 @@
isUnfoldHandled = false
}
this.isFolded = isFolded
- })
+ }
+ )
+
+ private companion object {
+ private const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
+
+ // Put the unfold overlay below the rotation animation screenshot to hide the moment
+ // when it is rotated but the rotation of the other windows hasn't happen yet
+ private const val UNFOLD_OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index bf70673..e8a22ec 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -46,6 +46,7 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.CoreStartable;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.util.NotificationChannels;
@@ -61,15 +62,24 @@
private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME";
private static final String ACTION_FINISH_WIZARD = "com.android.systemui.action.FINISH_WIZARD";
private final Context mContext;
+ private final BroadcastDispatcher mBroadcastDispatcher;
// TODO: delay some notifications to avoid bumpy fast operations
- private NotificationManager mNotificationManager;
- private StorageManager mStorageManager;
+ private final NotificationManager mNotificationManager;
+ private final StorageManager mStorageManager;
@Inject
- public StorageNotification(Context context) {
+ public StorageNotification(
+ Context context,
+ BroadcastDispatcher broadcastDispatcher,
+ NotificationManager notificationManager,
+ StorageManager storageManager
+ ) {
mContext = context;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mNotificationManager = notificationManager;
+ mStorageManager = storageManager;
}
private static class MoveInfo {
@@ -168,17 +178,22 @@
@Override
public void start() {
- mNotificationManager = mContext.getSystemService(NotificationManager.class);
-
- mStorageManager = mContext.getSystemService(StorageManager.class);
mStorageManager.registerListener(mListener);
- mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME),
- android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null,
- Context.RECEIVER_EXPORTED_UNAUDITED);
- mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD),
- android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null,
- Context.RECEIVER_EXPORTED_UNAUDITED);
+ mBroadcastDispatcher.registerReceiver(
+ mSnoozeReceiver,
+ new IntentFilter(ACTION_SNOOZE_VOLUME),
+ null,
+ null,
+ Context.RECEIVER_EXPORTED_UNAUDITED,
+ android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+ mBroadcastDispatcher.registerReceiver(
+ mFinishReceiver,
+ new IntentFilter(ACTION_FINISH_WIZARD),
+ null,
+ null,
+ Context.RECEIVER_EXPORTED_UNAUDITED,
+ android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
// Kick current state into place
final List<DiskInfo> disks = mStorageManager.getDisks();
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 0c72b78..2b29885 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -17,6 +17,7 @@
package com.android.systemui.user;
import android.app.Activity;
+import android.os.UserHandle;
import com.android.settingslib.users.EditUserInfoController;
import com.android.systemui.user.data.repository.UserRepositoryModule;
@@ -51,4 +52,22 @@
@IntoMap
@ClassKey(UserSwitcherActivity.class)
public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity);
+
+ /**
+ * Provides the {@link UserHandle} for the user associated with this System UI process.
+ *
+ * <p>Note that this is static and unchanging for the life-time of the process we are running
+ * in. It can be <i>different</i> from the user that is the currently-selected user, which may
+ * be associated with a different System UI process.
+ *
+ * <p>For example, the System UI process which creates all the windows and renders UI is always
+ * the one associated with the primary user on the device. However, if the user is switched to
+ * another, non-primary user (for example user "X"), then a secondary System UI process will be
+ * spawned. While the original primary user process continues to be the only one rendering UI,
+ * the new system UI process may be used for things like file or content access.
+ */
+ @Provides
+ public static UserHandle provideUserHandle() {
+ return new UserHandle(UserHandle.myUserId());
+ }
}
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 ffaf524..c0f0390 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
@@ -19,31 +19,19 @@
import android.content.Context
import android.content.pm.UserInfo
-import android.graphics.drawable.BitmapDrawable
-import android.graphics.drawable.Drawable
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.annotation.VisibleForTesting
-import androidx.appcompat.content.res.AppCompatResources
-import com.android.internal.util.UserIcons
import com.android.systemui.R
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.shared.model.Text
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
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.util.concurrent.atomic.AtomicBoolean
@@ -55,7 +43,6 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -72,15 +59,6 @@
* upstream changes.
*/
interface UserRepository {
- /** List of all users on the device. */
- val users: Flow<List<UserModel>>
-
- /** The currently-selected user. */
- val selectedUser: Flow<UserModel>
-
- /** List of available user-related actions. */
- val actions: Flow<List<UserActionModel>>
-
/** User switcher related settings. */
val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
@@ -93,9 +71,6 @@
/** User ID of the last non-guest selected user. */
val lastSelectedNonGuestUserId: Int
- /** Whether actions are available even when locked. */
- val isActionableWhenLocked: Flow<Boolean>
-
/** Whether the device is configured to always have a guest user available. */
val isGuestUserAutoCreated: Boolean
@@ -105,6 +80,9 @@
/** Whether we've scheduled the creation of a guest user. */
val isGuestUserCreationScheduled: AtomicBoolean
+ /** Whether to enable the status bar user chip (which launches the user switcher) */
+ val isStatusBarUserChipEnabled: Boolean
+
/** The user of the secondary service. */
var secondaryUserId: Int
@@ -125,18 +103,13 @@
constructor(
@Application private val appContext: Context,
private val manager: UserManager,
- private val controller: UserSwitcherController,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val globalSettings: GlobalSettings,
private val tracker: UserTracker,
- private val featureFlags: FeatureFlags,
) : UserRepository {
- private val isNewImpl: Boolean
- get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
_userSwitcherSettings.asStateFlow().filterNotNull()
@@ -150,70 +123,24 @@
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
private set
- private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
- fun send() {
- trySendWithFailureLogging(
- controller.users,
- TAG,
- )
- }
-
- val callback = UserSwitcherController.UserSwitchCallback { send() }
-
- controller.addUserSwitchCallback(callback)
- send()
-
- awaitClose { controller.removeUserSwitchCallback(callback) }
- }
-
- override val users: Flow<List<UserModel>> =
- userRecords.map { records -> records.filter { it.isUser() }.map { it.toUserModel() } }
-
- override val selectedUser: Flow<UserModel> =
- users.map { users -> users.first { user -> user.isSelected } }
-
- override val actions: Flow<List<UserActionModel>> =
- userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
-
- override val isActionableWhenLocked: Flow<Boolean> =
- if (isNewImpl) {
- emptyFlow()
- } else {
- controller.isAddUsersFromLockScreenEnabled
- }
-
override val isGuestUserAutoCreated: Boolean =
- if (isNewImpl) {
- appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
- } else {
- controller.isGuestUserAutoCreated
- }
+ appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
private var _isGuestUserResetting: Boolean = false
- override var isGuestUserResetting: Boolean =
- if (isNewImpl) {
- _isGuestUserResetting
- } else {
- controller.isGuestUserResetting
- }
- set(value) =
- if (isNewImpl) {
- _isGuestUserResetting = value
- } else {
- error("Not supported in the old implementation!")
- }
+ override var isGuestUserResetting: Boolean = _isGuestUserResetting
override val isGuestUserCreationScheduled = AtomicBoolean()
+ override val isStatusBarUserChipEnabled: Boolean =
+ appContext.resources.getBoolean(R.bool.flag_user_switcher_chip)
+
override var secondaryUserId: Int = UserHandle.USER_NULL
override var isRefreshUsersPaused: Boolean = false
init {
- if (isNewImpl) {
- observeSelectedUser()
- observeUserSettings()
- }
+ observeSelectedUser()
+ observeUserSettings()
}
override fun refreshUsers() {
@@ -315,7 +242,15 @@
val isUserSwitcherEnabled =
globalSettings.getIntForUser(
Settings.Global.USER_SWITCHER_ENABLED,
- 0,
+ if (
+ appContext.resources.getBoolean(
+ com.android.internal.R.bool.config_showUserSwitcherByDefault
+ )
+ ) {
+ 1
+ } else {
+ 0
+ },
UserHandle.USER_SYSTEM,
) != 0
@@ -327,64 +262,6 @@
}
}
- private fun UserRecord.isUser(): Boolean {
- return when {
- isAddUser -> false
- isAddSupervisedUser -> false
- isManageUsers -> false
- isGuest -> info != null
- else -> true
- }
- }
-
- private fun UserRecord.isNotUser(): Boolean {
- return !isUser()
- }
-
- private fun UserRecord.toUserModel(): UserModel {
- return UserModel(
- id = resolveId(),
- name = getUserName(this),
- image = getUserImage(this),
- isSelected = isCurrent,
- isSelectable = isSwitchToEnabled || isGuest,
- isGuest = isGuest,
- )
- }
-
- private fun UserRecord.toActionModel(): UserActionModel {
- return when {
- isAddUser -> UserActionModel.ADD_USER
- isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
- isGuest -> UserActionModel.ENTER_GUEST_MODE
- isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
- else -> error("Don't know how to convert to UserActionModel: $this")
- }
- }
-
- private fun getUserName(record: UserRecord): Text {
- val resourceId: Int? = LegacyUserUiHelper.getGuestUserRecordNameResourceId(record)
- return if (resourceId != null) {
- Text.Resource(resourceId)
- } else {
- Text.Loaded(checkNotNull(record.info).name)
- }
- }
-
- private fun getUserImage(record: UserRecord): Drawable {
- if (record.isGuest) {
- return checkNotNull(
- AppCompatResources.getDrawable(appContext, R.drawable.ic_account_circle)
- )
- }
-
- val userId = checkNotNull(record.info?.id)
- return manager.getUserIcon(userId)?.let { userSelectedIcon ->
- BitmapDrawable(userSelectedIcon)
- }
- ?: UserIcons.getDefaultUserIcon(appContext.resources, userId, /* light= */ false)
- }
-
companion object {
private const val TAG = "UserRepository"
@VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
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 dda78aa..d7b0971 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
@@ -34,6 +34,7 @@
import com.android.internal.util.UserIcons
import com.android.systemui.R
import com.android.systemui.SystemUISecondaryUserService
+import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
@@ -44,8 +45,9 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.domain.model.ShowDialogRequestModel
@@ -64,8 +66,6 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -82,7 +82,6 @@
constructor(
@Application private val applicationContext: Context,
private val repository: UserRepository,
- private val controller: UserSwitcherController,
private val activityStarter: ActivityStarter,
private val keyguardInteractor: KeyguardInteractor,
private val featureFlags: FeatureFlags,
@@ -107,9 +106,6 @@
fun onUserStateChanged()
}
- private val isNewImpl: Boolean
- get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
private val supervisedUserPackageName: String?
get() =
applicationContext.getString(
@@ -118,185 +114,161 @@
private val callbackMutex = Mutex()
private val callbacks = mutableSetOf<UserCallback>()
+ private val userInfos: Flow<List<UserInfo>> =
+ repository.userInfos.map { userInfos ->
+ userInfos.filter { it.isFull }
+ }
/** List of current on-device users to select from. */
val users: Flow<List<UserModel>>
get() =
- if (isNewImpl) {
- combine(
- repository.userInfos,
- repository.selectedUserInfo,
- repository.userSwitcherSettings,
- ) { userInfos, selectedUserInfo, settings ->
- toUserModels(
- userInfos = userInfos,
- selectedUserId = selectedUserInfo.id,
- isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
- )
- }
- } else {
- repository.users
+ combine(
+ userInfos,
+ repository.selectedUserInfo,
+ repository.userSwitcherSettings,
+ ) { userInfos, selectedUserInfo, settings ->
+ toUserModels(
+ userInfos = userInfos,
+ selectedUserId = selectedUserInfo.id,
+ isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
+ )
}
/** The currently-selected user. */
val selectedUser: Flow<UserModel>
get() =
- if (isNewImpl) {
- combine(
- repository.selectedUserInfo,
- repository.userSwitcherSettings,
- ) { selectedUserInfo, settings ->
- val selectedUserId = selectedUserInfo.id
- checkNotNull(
- toUserModel(
- userInfo = selectedUserInfo,
- selectedUserId = selectedUserId,
- canSwitchUsers = canSwitchUsers(selectedUserId),
- isUserSwitcherEnabled = settings.isUserSwitcherEnabled,
- )
- )
- }
- } else {
- repository.selectedUser
+ repository.selectedUserInfo.map { selectedUserInfo ->
+ val selectedUserId = selectedUserInfo.id
+ toUserModel(
+ userInfo = selectedUserInfo,
+ selectedUserId = selectedUserId,
+ canSwitchUsers = canSwitchUsers(selectedUserId)
+ )
}
/** List of user-switcher related actions that are available. */
val actions: Flow<List<UserActionModel>>
get() =
- if (isNewImpl) {
- combine(
- repository.selectedUserInfo,
- repository.userInfos,
- repository.userSwitcherSettings,
- keyguardInteractor.isKeyguardShowing,
- ) { _, userInfos, settings, isDeviceLocked ->
- buildList {
- val hasGuestUser = userInfos.any { it.isGuest }
- if (
- !hasGuestUser &&
- (guestUserInteractor.isGuestUserAutoCreated ||
- UserActionsUtil.canCreateGuest(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- ))
- ) {
- add(UserActionModel.ENTER_GUEST_MODE)
- }
+ combine(
+ repository.selectedUserInfo,
+ userInfos,
+ repository.userSwitcherSettings,
+ keyguardInteractor.isKeyguardShowing,
+ ) { _, userInfos, settings, isDeviceLocked ->
+ buildList {
+ if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
+ // The device is locked and our setting to allow actions that add users
+ // from the lock-screen is not enabled. We can finish building the list
+ // here.
+ val isFullScreen = featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)
- if (!isDeviceLocked || settings.isAddUsersFromLockscreen) {
- // The device is locked and our setting to allow actions that add users
- // from the lock-screen is not enabled. The guest action from above is
- // always allowed, even when the device is locked, but the various "add
- // user" actions below are not. We can finish building the list here.
-
- val canCreateUsers =
- UserActionsUtil.canCreateUser(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
+ val actionList: List<UserActionModel> =
+ if (isFullScreen) {
+ listOf(
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.ENTER_GUEST_MODE,
)
-
- if (canCreateUsers) {
- add(UserActionModel.ADD_USER)
+ } else {
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ )
}
+ actionList.map {
+ when (it) {
+ UserActionModel.ENTER_GUEST_MODE -> {
+ val hasGuestUser = userInfos.any { it.isGuest }
+ if (!hasGuestUser && canCreateGuestUser(settings)) {
+ add(UserActionModel.ENTER_GUEST_MODE)
+ }
+ }
+ UserActionModel.ADD_USER -> {
+ val canCreateUsers =
+ UserActionsUtil.canCreateUser(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ )
- if (
- UserActionsUtil.canCreateSupervisedUser(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- supervisedUserPackageName,
- )
- ) {
- add(UserActionModel.ADD_SUPERVISED_USER)
+ if (canCreateUsers) {
+ add(UserActionModel.ADD_USER)
+ }
+ }
+ UserActionModel.ADD_SUPERVISED_USER -> {
+ if (
+ UserActionsUtil.canCreateSupervisedUser(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ supervisedUserPackageName,
+ )
+ ) {
+ add(UserActionModel.ADD_SUPERVISED_USER)
+ }
+ }
+ else -> Unit
}
}
-
- if (
- UserActionsUtil.canManageUsers(
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- )
- ) {
- add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
- }
+ }
+ if (
+ UserActionsUtil.canManageUsers(
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ )
+ ) {
+ add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
}
}
- } else {
- combine(
- repository.isActionableWhenLocked,
- keyguardInteractor.isKeyguardShowing,
- ) { isActionableWhenLocked, isLocked ->
- isActionableWhenLocked || !isLocked
- }
- .flatMapLatest { isActionable ->
- if (isActionable) {
- repository.actions
- } else {
- // If not actionable it means that we're not allowed to show actions
- // when
- // locked and we are locked. Therefore, we should show no actions.
- flowOf(emptyList())
- }
- }
}
val userRecords: StateFlow<ArrayList<UserRecord>> =
- if (isNewImpl) {
- combine(
- repository.userInfos,
- repository.selectedUserInfo,
- actions,
- repository.userSwitcherSettings,
- ) { userInfos, selectedUserInfo, actionModels, settings ->
- ArrayList(
- userInfos.map {
+ combine(
+ userInfos,
+ repository.selectedUserInfo,
+ actions,
+ repository.userSwitcherSettings,
+ ) { userInfos, selectedUserInfo, actionModels, settings ->
+ ArrayList(
+ userInfos.map {
+ toRecord(
+ userInfo = it,
+ selectedUserId = selectedUserInfo.id,
+ )
+ } +
+ actionModels.map {
toRecord(
- userInfo = it,
+ action = it,
selectedUserId = selectedUserInfo.id,
+ isRestricted =
+ it != UserActionModel.ENTER_GUEST_MODE &&
+ it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
+ !settings.isAddUsersFromLockscreen,
)
- } +
- actionModels.map {
- toRecord(
- action = it,
- selectedUserId = selectedUserInfo.id,
- isRestricted =
- it != UserActionModel.ENTER_GUEST_MODE &&
- it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
- !settings.isAddUsersFromLockscreen,
- )
- }
- )
- }
- .onEach { notifyCallbacks() }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = ArrayList(),
+ }
)
- } else {
- MutableStateFlow(ArrayList())
- }
+ }
+ .onEach { notifyCallbacks() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = ArrayList(),
+ )
val selectedUserRecord: StateFlow<UserRecord?> =
- if (isNewImpl) {
- repository.selectedUserInfo
- .map { selectedUserInfo ->
- toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = null,
- )
- } else {
- MutableStateFlow(null)
- }
+ repository.selectedUserInfo
+ .map { selectedUserInfo ->
+ toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
/** Whether the device is configured to always have a guest user available. */
val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated
@@ -304,6 +276,9 @@
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
+ /** Whether to enable the user chip in the status bar */
+ val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled
+
private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
@@ -311,44 +286,37 @@
val dialogDismissRequests: Flow<Unit?> = _dialogDismissRequests.asStateFlow()
val isSimpleUserSwitcher: Boolean
- get() =
- if (isNewImpl) {
- repository.isSimpleUserSwitcher()
- } else {
- error("Not supported in the old implementation!")
- }
+ get() = repository.isSimpleUserSwitcher()
init {
- if (isNewImpl) {
- refreshUsersScheduler.refreshIfNotPaused()
- telephonyInteractor.callState
- .distinctUntilChanged()
- .onEach { refreshUsersScheduler.refreshIfNotPaused() }
- .launchIn(applicationScope)
+ refreshUsersScheduler.refreshIfNotPaused()
+ telephonyInteractor.callState
+ .distinctUntilChanged()
+ .onEach { refreshUsersScheduler.refreshIfNotPaused() }
+ .launchIn(applicationScope)
- combine(
- broadcastDispatcher.broadcastFlow(
- filter =
- IntentFilter().apply {
- addAction(Intent.ACTION_USER_ADDED)
- addAction(Intent.ACTION_USER_REMOVED)
- addAction(Intent.ACTION_USER_INFO_CHANGED)
- addAction(Intent.ACTION_USER_SWITCHED)
- addAction(Intent.ACTION_USER_STOPPED)
- addAction(Intent.ACTION_USER_UNLOCKED)
- },
- user = UserHandle.SYSTEM,
- map = { intent, _ -> intent },
- ),
- repository.selectedUserInfo.pairwise(null),
- ) { intent, selectedUserChange ->
- Pair(intent, selectedUserChange.previousValue)
- }
- .onEach { (intent, previousSelectedUser) ->
- onBroadcastReceived(intent, previousSelectedUser)
- }
- .launchIn(applicationScope)
- }
+ combine(
+ broadcastDispatcher.broadcastFlow(
+ filter =
+ IntentFilter().apply {
+ addAction(Intent.ACTION_USER_ADDED)
+ addAction(Intent.ACTION_USER_REMOVED)
+ addAction(Intent.ACTION_USER_INFO_CHANGED)
+ addAction(Intent.ACTION_USER_SWITCHED)
+ addAction(Intent.ACTION_USER_STOPPED)
+ addAction(Intent.ACTION_USER_UNLOCKED)
+ },
+ user = UserHandle.SYSTEM,
+ map = { intent, _ -> intent },
+ ),
+ repository.selectedUserInfo.pairwise(null),
+ ) { intent, selectedUserChange ->
+ Pair(intent, selectedUserChange.previousValue)
+ }
+ .onEach { (intent, previousSelectedUser) ->
+ onBroadcastReceived(intent, previousSelectedUser)
+ }
+ .launchIn(applicationScope)
}
fun addCallback(callback: UserCallback) {
@@ -414,48 +382,43 @@
newlySelectedUserId: Int,
dialogShower: UserSwitchDialogController.DialogShower? = null,
) {
- if (isNewImpl) {
- val currentlySelectedUserInfo = repository.getSelectedUserInfo()
- if (
- newlySelectedUserId == currentlySelectedUserInfo.id &&
- currentlySelectedUserInfo.isGuest
- ) {
- // Here when clicking on the currently-selected guest user to leave guest mode
- // and return to the previously-selected non-guest user.
- showDialog(
- ShowDialogRequestModel.ShowExitGuestDialog(
- guestUserId = currentlySelectedUserInfo.id,
- targetUserId = repository.lastSelectedNonGuestUserId,
- isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
- isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
- onExitGuestUser = this::exitGuestUser,
- dialogShower = dialogShower,
- )
+ val currentlySelectedUserInfo = repository.getSelectedUserInfo()
+ if (
+ newlySelectedUserId == currentlySelectedUserInfo.id && currentlySelectedUserInfo.isGuest
+ ) {
+ // Here when clicking on the currently-selected guest user to leave guest mode
+ // and return to the previously-selected non-guest user.
+ showDialog(
+ ShowDialogRequestModel.ShowExitGuestDialog(
+ guestUserId = currentlySelectedUserInfo.id,
+ targetUserId = repository.lastSelectedNonGuestUserId,
+ isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ onExitGuestUser = this::exitGuestUser,
+ dialogShower = dialogShower,
)
- return
- }
-
- if (currentlySelectedUserInfo.isGuest) {
- // Here when switching from guest to a non-guest user.
- showDialog(
- ShowDialogRequestModel.ShowExitGuestDialog(
- guestUserId = currentlySelectedUserInfo.id,
- targetUserId = newlySelectedUserId,
- isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
- isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
- onExitGuestUser = this::exitGuestUser,
- dialogShower = dialogShower,
- )
- )
- return
- }
-
- dialogShower?.dismiss()
-
- switchUser(newlySelectedUserId)
- } else {
- controller.onUserSelected(newlySelectedUserId, dialogShower)
+ )
+ return
}
+
+ if (currentlySelectedUserInfo.isGuest) {
+ // Here when switching from guest to a non-guest user.
+ showDialog(
+ ShowDialogRequestModel.ShowExitGuestDialog(
+ guestUserId = currentlySelectedUserInfo.id,
+ targetUserId = newlySelectedUserId,
+ isGuestEphemeral = currentlySelectedUserInfo.isEphemeral,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ onExitGuestUser = this::exitGuestUser,
+ dialogShower = dialogShower,
+ )
+ )
+ return
+ }
+
+ dialogShower?.dismiss()
+
+ switchUser(newlySelectedUserId)
}
/** Executes the given action. */
@@ -463,51 +426,38 @@
action: UserActionModel,
dialogShower: UserSwitchDialogController.DialogShower? = null,
) {
- if (isNewImpl) {
- when (action) {
- UserActionModel.ENTER_GUEST_MODE ->
- guestUserInteractor.createAndSwitchTo(
- this::showDialog,
- this::dismissDialog,
- ) { userId ->
- selectUser(userId, dialogShower)
- }
- UserActionModel.ADD_USER -> {
- val currentUser = repository.getSelectedUserInfo()
- showDialog(
- ShowDialogRequestModel.ShowAddUserDialog(
- userHandle = currentUser.userHandle,
- isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
- showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
- dialogShower = dialogShower,
- )
- )
+ when (action) {
+ UserActionModel.ENTER_GUEST_MODE ->
+ guestUserInteractor.createAndSwitchTo(
+ this::showDialog,
+ this::dismissDialog,
+ ) { userId ->
+ selectUser(userId, dialogShower)
}
- UserActionModel.ADD_SUPERVISED_USER ->
- activityStarter.startActivity(
- Intent()
- .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
- .setPackage(supervisedUserPackageName)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
- /* dismissShade= */ true,
+ UserActionModel.ADD_USER -> {
+ val currentUser = repository.getSelectedUserInfo()
+ showDialog(
+ ShowDialogRequestModel.ShowAddUserDialog(
+ userHandle = currentUser.userHandle,
+ isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
+ showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
+ dialogShower = dialogShower,
)
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
- activityStarter.startActivity(
- Intent(Settings.ACTION_USER_SETTINGS),
- /* dismissShade= */ true,
- )
+ )
}
- } else {
- when (action) {
- UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null)
- UserActionModel.ADD_USER -> controller.showAddUserDialog(null)
- UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity()
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
- activityStarter.startActivity(
- Intent(Settings.ACTION_USER_SETTINGS),
- /* dismissShade= */ false,
- )
- }
+ UserActionModel.ADD_SUPERVISED_USER ->
+ activityStarter.startActivity(
+ Intent()
+ .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
+ .setPackage(supervisedUserPackageName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+ /* dismissShade= */ true,
+ )
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
+ activityStarter.startActivity(
+ Intent(Settings.ACTION_USER_SETTINGS),
+ /* dismissShade= */ true,
+ )
}
}
@@ -541,6 +491,26 @@
}
}
+ fun showUserSwitcher(context: Context, expandable: Expandable) {
+ if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog)
+ return
+ }
+
+ val intent =
+ Intent(context, UserSwitcherActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ expandable.activityLaunchController(),
+ true /* showOverlockscreenwhenlocked */,
+ UserHandle.SYSTEM,
+ )
+ }
+
private fun showDialog(request: ShowDialogRequestModel) {
_dialogShowRequests.value = request
}
@@ -674,7 +644,7 @@
// The guest user should go in the last position.
.sortedBy { it.isGuest }
.mapNotNull { userInfo ->
- toUserModel(
+ filterAndMapToUserModel(
userInfo = userInfo,
selectedUserId = selectedUserId,
canSwitchUsers = canSwitchUsers,
@@ -683,51 +653,65 @@
}
}
- private suspend fun toUserModel(
+ /**
+ * Maps UserInfo to UserModel based on some parameters and return null under certain conditions
+ * to be filtered out.
+ */
+ private suspend fun filterAndMapToUserModel(
userInfo: UserInfo,
selectedUserId: Int,
canSwitchUsers: Boolean,
isUserSwitcherEnabled: Boolean,
): UserModel? {
- val userId = userInfo.id
- val isSelected = userId == selectedUserId
-
return when {
// When the user switcher is not enabled in settings, we only show the primary user.
!isUserSwitcherEnabled && !userInfo.isPrimary -> null
-
// We avoid showing disabled users.
!userInfo.isEnabled -> null
- userInfo.isGuest ->
- UserModel(
- id = userId,
- name = Text.Loaded(userInfo.name),
- image =
- getUserImage(
- isGuest = true,
- userId = userId,
- ),
- isSelected = isSelected,
- isSelectable = canSwitchUsers,
- isGuest = true,
- )
- userInfo.supportsSwitchToByUser() ->
- UserModel(
- id = userId,
- name = Text.Loaded(userInfo.name),
- image =
- getUserImage(
- isGuest = false,
- userId = userId,
- ),
- isSelected = isSelected,
- isSelectable = canSwitchUsers || isSelected,
- isGuest = false,
- )
+ // We meet the conditions to return the UserModel.
+ userInfo.isGuest || userInfo.supportsSwitchToByUser() ->
+ toUserModel(userInfo, selectedUserId, canSwitchUsers)
else -> null
}
}
+ /** Maps UserInfo to UserModel based on some parameters. */
+ private suspend fun toUserModel(
+ userInfo: UserInfo,
+ selectedUserId: Int,
+ canSwitchUsers: Boolean
+ ): UserModel {
+ val userId = userInfo.id
+ val isSelected = userId == selectedUserId
+ return if (userInfo.isGuest) {
+ UserModel(
+ id = userId,
+ name = Text.Loaded(userInfo.name),
+ image =
+ getUserImage(
+ isGuest = true,
+ userId = userId,
+ ),
+ isSelected = isSelected,
+ isSelectable = canSwitchUsers,
+ isGuest = true,
+ )
+ } else {
+ UserModel(
+ id = userId,
+ name = Text.Loaded(userInfo.name),
+ image =
+ getUserImage(
+ isGuest = false,
+ userId = userId,
+ ),
+ isSelected = isSelected,
+ isSelectable = canSwitchUsers || isSelected,
+ isGuest = false,
+ )
+ }
+ }
+
private suspend fun canSwitchUsers(selectedUserId: Int): Boolean {
return withContext(backgroundDispatcher) {
manager.getUserSwitchability(UserHandle.of(selectedUserId))
@@ -757,6 +741,16 @@
)
}
+ private fun canCreateGuestUser(settings: UserSwitcherSettingsModel): Boolean {
+ return guestUserInteractor.isGuestUserAutoCreated ||
+ UserActionsUtil.canCreateGuest(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ )
+ }
+
companion object {
private const val TAG = "UserInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 177356e..85c2964 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -43,4 +43,7 @@
val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
override val dialogShower: UserSwitchDialogController.DialogShower?,
) : ShowDialogRequestModel(dialogShower)
+
+ /** Show the user switcher dialog */
+ object ShowUserSwitcherDialog : ShowDialogRequestModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
new file mode 100644
index 0000000..8e40f68
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.ui.binder
+
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@OptIn(InternalCoroutinesApi::class)
+object StatusBarUserChipViewBinder {
+ /** Binds the status bar user chip view model to the given view */
+ @JvmStatic
+ fun bind(
+ view: StatusBarUserSwitcherContainer,
+ viewModel: StatusBarUserChipViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.isChipVisible.collect { isVisible -> view.isVisible = isVisible }
+ }
+
+ launch {
+ viewModel.userName.collect { name -> TextViewBinder.bind(view.text, name) }
+ }
+
+ launch {
+ viewModel.userAvatar.collect { avatar -> view.avatar.setImageDrawable(avatar) }
+ }
+
+ bindButton(view, viewModel)
+ }
+ }
+ }
+
+ private fun bindButton(
+ view: StatusBarUserSwitcherContainer,
+ viewModel: StatusBarUserChipViewModel,
+ ) {
+ view.setOnClickListener { viewModel.onClick(Expandable.fromView(view)) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index ad09ee3..e137107 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -133,7 +133,9 @@
launch {
viewModel.users.collect { users ->
val viewPool =
- view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList()
+ gridContainerView.children
+ .filter { it.tag == USER_VIEW_TAG }
+ .toMutableList()
viewPool.forEach {
gridContainerView.removeView(it)
flowWidget.removeView(it)
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
new file mode 100644
index 0000000..ed25898
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
@@ -0,0 +1,68 @@
+package com.android.systemui.user.ui.dialog
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.view.LayoutInflater
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/**
+ * Extracted from the old UserSwitchDialogController. This is the dialog version of the full-screen
+ * user switcher. See config_enableFullscreenUserSwitcher
+ */
+class UserSwitchDialog(
+ context: Context,
+ adapter: UserDetailView.Adapter,
+ uiEventLogger: UiEventLogger,
+ falsingManager: FalsingManager,
+ activityStarter: ActivityStarter,
+ dialogLaunchAnimator: DialogLaunchAnimator,
+) : SystemUIDialog(context) {
+ init {
+ setShowForAllUsers(true)
+ setCanceledOnTouchOutside(true)
+ setTitle(R.string.qs_user_switch_dialog_title)
+ setPositiveButton(R.string.quick_settings_done) { _, _ ->
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE)
+ }
+ setNeutralButton(
+ R.string.quick_settings_more_user_settings,
+ { _, _ ->
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
+ val controller =
+ dialogLaunchAnimator.createActivityLaunchController(
+ getButton(BUTTON_NEUTRAL)
+ )
+
+ if (controller == null) {
+ dismiss()
+ }
+
+ activityStarter.postStartActivityDismissingKeyguard(
+ USER_SETTINGS_INTENT,
+ 0,
+ controller
+ )
+ }
+ },
+ false /* dismissOnClick */
+ )
+ val gridFrame =
+ LayoutInflater.from(this.context).inflate(R.layout.qs_user_dialog_content, null)
+ setView(gridFrame)
+
+ adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
+ }
+
+ companion object {
+ private val USER_SETTINGS_INTENT = Intent(Settings.ACTION_USER_SETTINGS)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index e921720..4141054 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -20,6 +20,7 @@
import android.app.Dialog
import android.content.Context
import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.users.UserCreatingDialog
import com.android.systemui.CoreStartable
import com.android.systemui.animation.DialogCuj
@@ -27,15 +28,15 @@
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.UserDetailView
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
import dagger.Lazy
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
@@ -50,16 +51,14 @@
private val broadcastSender: Lazy<BroadcastSender>,
private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
private val interactor: Lazy<UserInteractor>,
- private val featureFlags: Lazy<FeatureFlags>,
+ private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
+ private val eventLogger: Lazy<UiEventLogger>,
+ private val activityStarter: Lazy<ActivityStarter>,
) : CoreStartable {
private var currentDialog: Dialog? = null
override fun start() {
- if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
- return
- }
-
startHandlingDialogShowRequests()
startHandlingDialogDismissRequests()
}
@@ -116,6 +115,21 @@
INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
),
)
+ is ShowDialogRequestModel.ShowUserSwitcherDialog ->
+ Pair(
+ UserSwitchDialog(
+ context = context.get(),
+ adapter = userDetailAdapterProvider.get(),
+ uiEventLogger = eventLogger.get(),
+ falsingManager = falsingManager.get(),
+ activityStarter = activityStarter.get(),
+ dialogLaunchAnimator = dialogLaunchAnimator.get(),
+ ),
+ DialogCuj(
+ InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+ INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
+ ),
+ )
}
currentDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
new file mode 100644
index 0000000..3300e8e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.ui.viewmodel
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.domain.interactor.UserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class StatusBarUserChipViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ interactor: UserInteractor,
+) {
+ /** Whether the status bar chip ui should be available */
+ val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
+
+ /** Whether or not the chip should be showing, based on the number of users */
+ val isChipVisible: Flow<Boolean> =
+ if (!chipEnabled) {
+ flowOf(false)
+ } else {
+ interactor.users.mapLatest { users -> users.size > 1 }
+ }
+
+ /** The display name of the current user */
+ val userName: Flow<Text> = interactor.selectedUser.mapLatest { userModel -> userModel.name }
+
+ /** Avatar for the current user */
+ val userAvatar: Flow<Drawable> =
+ interactor.selectedUser.mapLatest { userModel -> userModel.image }
+
+ /** Action to execute on click. Should launch the user switcher */
+ val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(context, it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index d857e85..0910ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -20,8 +20,6 @@
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.android.systemui.common.ui.drawable.CircularDrawable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.UserInteractor
@@ -41,12 +39,8 @@
private val userInteractor: UserInteractor,
private val guestUserInteractor: GuestUserInteractor,
private val powerInteractor: PowerInteractor,
- private val featureFlags: FeatureFlags,
) : ViewModel() {
- private val isNewImpl: Boolean
- get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
-
/** On-device users. */
val users: Flow<List<UserViewModel>> =
userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
@@ -216,7 +210,6 @@
private val userInteractor: UserInteractor,
private val guestUserInteractor: GuestUserInteractor,
private val powerInteractor: PowerInteractor,
- private val featureFlags: FeatureFlags,
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
@@ -224,7 +217,6 @@
userInteractor = userInteractor,
guestUserInteractor = guestUserInteractor,
powerInteractor = powerInteractor,
- featureFlags = featureFlags,
)
as T
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt b/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt
new file mode 100644
index 0000000..da81d54
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.condition
+
+/**
+ * A higher order [Condition] which combines multiple conditions with a specified
+ * [Evaluator.ConditionOperand].
+ */
+internal class CombinedCondition
+constructor(
+ private val conditions: Collection<Condition>,
+ @Evaluator.ConditionOperand private val operand: Int
+) : Condition(null, false), Condition.Callback {
+
+ override fun start() {
+ onConditionChanged(this)
+ conditions.forEach { it.addCallback(this) }
+ }
+
+ override fun onConditionChanged(condition: Condition) {
+ Evaluator.evaluate(conditions, operand)?.also { value -> updateCondition(value) }
+ ?: clearCondition()
+ }
+
+ override fun stop() {
+ conditions.forEach { it.removeCallback(this) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
index 2c317dd..b39adef 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
@@ -24,7 +24,10 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
/**
* Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform
@@ -181,6 +184,42 @@
}
/**
+ * Creates a new condition which will only be true when both this condition and all the provided
+ * conditions are true.
+ */
+ public Condition and(Collection<Condition> others) {
+ final List<Condition> conditions = new ArrayList<>(others);
+ conditions.add(this);
+ return new CombinedCondition(conditions, Evaluator.OP_AND);
+ }
+
+ /**
+ * Creates a new condition which will only be true when both this condition and the provided
+ * condition is true.
+ */
+ public Condition and(Condition other) {
+ return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND);
+ }
+
+ /**
+ * Creates a new condition which will only be true when either this condition or any of the
+ * provided conditions are true.
+ */
+ public Condition or(Collection<Condition> others) {
+ final List<Condition> conditions = new ArrayList<>(others);
+ conditions.add(this);
+ return new CombinedCondition(conditions, Evaluator.OP_OR);
+ }
+
+ /**
+ * Creates a new condition which will only be true when either this condition or the provided
+ * condition is true.
+ */
+ public Condition or(Condition other) {
+ return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR);
+ }
+
+ /**
* Callback that receives updates about whether the condition has been fulfilled.
*/
public interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt b/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt
new file mode 100644
index 0000000..cf44e84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt
@@ -0,0 +1,92 @@
+package com.android.systemui.util.condition
+
+import android.annotation.IntDef
+
+/**
+ * Helper for evaluating a collection of [Condition] objects with a given
+ * [Evaluator.ConditionOperand]
+ */
+internal object Evaluator {
+ /** Operands for combining multiple conditions together */
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(value = [OP_AND, OP_OR])
+ annotation class ConditionOperand
+
+ /**
+ * 3-valued logical AND operand, with handling for unknown values (represented as null)
+ *
+ * ```
+ * +-----+----+---+---+
+ * | AND | T | F | U |
+ * +-----+----+---+---+
+ * | T | T | F | U |
+ * | F | F | F | F |
+ * | U | U | F | U |
+ * +-----+----+---+---+
+ * ```
+ */
+ const val OP_AND = 0
+
+ /**
+ * 3-valued logical OR operand, with handling for unknown values (represented as null)
+ *
+ * ```
+ * +-----+----+---+---+
+ * | OR | T | F | U |
+ * +-----+----+---+---+
+ * | T | T | T | T |
+ * | F | T | F | U |
+ * | U | T | U | U |
+ * +-----+----+---+---+
+ * ```
+ */
+ const val OP_OR = 1
+
+ /**
+ * Evaluates a set of conditions with a given operand
+ *
+ * If overriding conditions are present, they take precedence over normal conditions if set.
+ *
+ * @param conditions The collection of conditions to evaluate. If empty, null is returned.
+ * @param operand The operand to use when evaluating.
+ * @return Either true or false if the value is known, or null if value is unknown
+ */
+ fun evaluate(conditions: Collection<Condition>, @ConditionOperand operand: Int): Boolean? {
+ if (conditions.isEmpty()) return null
+ // If there are overriding conditions with values set, they take precedence.
+ val targetConditions =
+ conditions
+ .filter { it.isConditionSet && it.isOverridingCondition }
+ .ifEmpty { conditions }
+ return when (operand) {
+ OP_AND ->
+ threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = false)
+ OP_OR ->
+ threeValuedAndOrOr(conditions = targetConditions, returnValueIfAnyMatches = true)
+ else -> null
+ }
+ }
+
+ /**
+ * Helper for evaluating 3-valued logical AND/OR.
+ *
+ * @param returnValueIfAnyMatches AND returns false if any value is false. OR returns true if
+ * any value is true.
+ */
+ private fun threeValuedAndOrOr(
+ conditions: Collection<Condition>,
+ returnValueIfAnyMatches: Boolean
+ ): Boolean? {
+ var hasUnknown = false
+ for (condition in conditions) {
+ if (!condition.isConditionSet) {
+ hasUnknown = true
+ continue
+ }
+ if (condition.isConditionMet == returnValueIfAnyMatches) {
+ return returnValueIfAnyMatches
+ }
+ }
+ return if (hasUnknown) null else !returnValueIfAnyMatches
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index cb430ba..24bc907 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -24,12 +24,10 @@
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.Executor;
-import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -57,21 +55,10 @@
}
public void update() {
- // Only consider set conditions.
- final Collection<Condition> setConditions = mSubscription.mConditions.stream()
- .filter(Condition::isConditionSet).collect(Collectors.toSet());
-
- // Overriding conditions do not override each other
- final Collection<Condition> overridingConditions = setConditions.stream()
- .filter(Condition::isOverridingCondition).collect(Collectors.toSet());
-
- final Collection<Condition> targetCollection = overridingConditions.isEmpty()
- ? setConditions : overridingConditions;
-
- final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection
- .stream()
- .map(Condition::isConditionMet)
- .allMatch(conditionMet -> conditionMet);
+ final Boolean result = Evaluator.INSTANCE.evaluate(mSubscription.mConditions,
+ Evaluator.OP_AND);
+ // Consider unknown (null) as true
+ final boolean newAllConditionsMet = result == null || result;
if (mAllConditionsMet != null && newAllConditionsMet == mAllConditionsMet) {
return;
@@ -109,6 +96,7 @@
/**
* Registers a callback and the set of conditions to trigger it.
+ *
* @param subscription A {@link Subscription} detailing the desired conditions and callback.
* @return A {@link Subscription.Token} that can be used to remove the subscription.
*/
@@ -139,6 +127,7 @@
/**
* Removes a subscription from participating in future callbacks.
+ *
* @param token The {@link Subscription.Token} returned when the {@link Subscription} was
* originally added.
*/
@@ -179,7 +168,9 @@
private final Set<Condition> mConditions;
private final Callback mCallback;
- /** */
+ /**
+ *
+ */
public Subscription(Set<Condition> conditions, Callback callback) {
this.mConditions = Collections.unmodifiableSet(conditions);
this.mCallback = callback;
@@ -209,7 +200,6 @@
/**
* Default constructor specifying the {@link Callback} for the {@link Subscription}.
- * @param callback
*/
public Builder(Callback callback) {
mCallback = callback;
@@ -218,7 +208,7 @@
/**
* Adds a {@link Condition} to be associated with the {@link Subscription}.
- * @param condition
+ *
* @return The updated {@link Builder}.
*/
public Builder addCondition(Condition condition) {
@@ -228,7 +218,7 @@
/**
* Adds a set of {@link Condition} to be associated with the {@link Subscription}.
- * @param condition
+ *
* @return The updated {@link Builder}.
*/
public Builder addConditions(Set<Condition> condition) {
@@ -238,6 +228,7 @@
/**
* Builds the {@link Subscription}.
+ *
* @return The resulting {@link Subscription}.
*/
public Subscription build() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index f71d596..b61b2e6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -20,7 +20,6 @@
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onStart
@@ -58,6 +57,22 @@
onStart { emit(initialValue) }.pairwiseBy(transform)
/**
+ * Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
+ *
+ *
+ * The output of [getInitialValue] will be used as the "old" value for the first emission. As
+ * opposed to the initial value in the above [pairwiseBy], [getInitialValue] can do some work before
+ * returning the initial value.
+ *
+ * Useful for code that needs to compare the current value to the previous value.
+ */
+fun <T, R> Flow<T>.pairwiseBy(
+ getInitialValue: suspend () -> T,
+ transform: suspend (previousValue: T, newValue: T) -> R,
+): Flow<R> =
+ onStart { emit(getInitialValue()) }.pairwiseBy(transform)
+
+/**
* Returns a new [Flow] that produces the two most recent emissions from [this]. Note that the new
* Flow will not start emitting until it has received two emissions from the upstream Flow.
*
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index 4875982..9b06a37 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -31,8 +31,8 @@
import com.android.internal.util.Preconditions;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.concurrency.ThreadFactory;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java b/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
index d7c4e93..3c57081 100644
--- a/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/util/time/DateFormatUtil.java
@@ -16,10 +16,11 @@
package com.android.systemui.util.time;
-import android.app.ActivityManager;
import android.content.Context;
import android.text.format.DateFormat;
+import com.android.systemui.settings.UserTracker;
+
import javax.inject.Inject;
/**
@@ -27,14 +28,16 @@
*/
public class DateFormatUtil {
private final Context mContext;
+ private final UserTracker mUserTracker;
@Inject
- public DateFormatUtil(Context context) {
+ public DateFormatUtil(Context context, UserTracker userTracker) {
mContext = context;
+ mUserTracker = userTracker;
}
/** Returns true if the phone is in 24 hour format. */
public boolean is24HourFormat() {
- return DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser());
+ return DateFormat.is24HourFormat(mContext, mUserTracker.getUserId());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0d96272..101dd45 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -72,6 +72,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.text.InputFilter;
@@ -108,6 +109,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.view.RotationPolicy;
@@ -125,11 +128,15 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.AlphaTintDrawableWrapper;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.RoundedCornerProgressDrawable;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -186,6 +193,9 @@
private ViewGroup mDialogRowsView;
private ViewGroup mRinger;
+ private DeviceConfigProxy mDeviceConfigProxy;
+ private Executor mExecutor;
+
/**
* Container for the top part of the dialog, which contains the ringer, the ringer drawer, the
* volume rows, and the ellipsis button. This does not include the live caption button.
@@ -274,6 +284,13 @@
private BackgroundBlurDrawable mDialogRowsViewBackground;
private final InteractionJankMonitor mInteractionJankMonitor;
+ private boolean mSeparateNotification;
+
+ @VisibleForTesting
+ int mVolumeRingerIconDrawableId;
+ @VisibleForTesting
+ int mVolumeRingerMuteIconDrawableId;
+
public VolumeDialogImpl(
Context context,
VolumeDialogController volumeDialogController,
@@ -283,7 +300,9 @@
MediaOutputDialogFactory mediaOutputDialogFactory,
VolumePanelFactory volumePanelFactory,
ActivityStarter activityStarter,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ DeviceConfigProxy deviceConfigProxy,
+ Executor executor) {
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
mController = volumeDialogController;
@@ -323,6 +342,50 @@
}
initDimens();
+
+ mDeviceConfigProxy = deviceConfigProxy;
+ mExecutor = executor;
+ mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+ updateRingerModeIconSet();
+ }
+
+ /**
+ * If ringer and notification are the same stream (T and earlier), use notification-like bell
+ * icon set.
+ * If ringer and notification are separated, then use generic speaker icons.
+ */
+ private void updateRingerModeIconSet() {
+ if (mSeparateNotification) {
+ mVolumeRingerIconDrawableId = R.drawable.ic_speaker_on;
+ mVolumeRingerMuteIconDrawableId = R.drawable.ic_speaker_mute;
+ } else {
+ mVolumeRingerIconDrawableId = R.drawable.ic_volume_ringer;
+ mVolumeRingerMuteIconDrawableId = R.drawable.ic_volume_ringer_mute;
+ }
+
+ if (mRingerDrawerMuteIcon != null) {
+ mRingerDrawerMuteIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+ }
+ if (mRingerDrawerNormalIcon != null) {
+ mRingerDrawerNormalIcon.setImageResource(mVolumeRingerIconDrawableId);
+ }
+ }
+
+ /**
+ * Change icon for ring stream (not ringer mode icon)
+ */
+ private void updateRingRowIcon() {
+ Optional<VolumeRow> volumeRow = mRows.stream().filter(row -> row.stream == STREAM_RING)
+ .findFirst();
+ if (volumeRow.isPresent()) {
+ VolumeRow volRow = volumeRow.get();
+ volRow.iconRes = mSeparateNotification ? R.drawable.ic_ring_volume
+ : R.drawable.ic_volume_ringer;
+ volRow.iconMuteRes = mSeparateNotification ? R.drawable.ic_ring_volume_off
+ : R.drawable.ic_volume_ringer_mute;
+ volRow.setIcon(volRow.iconRes, mContext.getTheme());
+ }
}
@Override
@@ -339,6 +402,9 @@
mController.getState();
mConfigurationController.addCallback(this);
+
+ mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mExecutor, this::onDeviceConfigChange);
}
@Override
@@ -346,6 +412,24 @@
mController.removeCallback(mControllerCallbackH);
mHandler.removeCallbacksAndMessages(null);
mConfigurationController.removeCallback(this);
+ mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
+ }
+
+ /**
+ * Update ringer mode icon based on the config
+ */
+ private void onDeviceConfigChange(DeviceConfig.Properties properties) {
+ Set<String> changeSet = properties.getKeyset();
+ if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+ boolean newVal = properties.getBoolean(
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+ if (newVal != mSeparateNotification) {
+ mSeparateNotification = newVal;
+ updateRingerModeIconSet();
+ updateRingRowIcon();
+
+ }
+ }
}
@Override
@@ -552,6 +636,8 @@
mRingerDrawerNormalIcon = mDialog.findViewById(R.id.volume_drawer_normal_icon);
mRingerDrawerNewSelectionBg = mDialog.findViewById(R.id.volume_drawer_selection_background);
+ updateRingerModeIconSet();
+
setupRingerDrawer();
mODICaptionsView = mDialog.findViewById(R.id.odi_captions);
@@ -575,8 +661,14 @@
addRow(AudioManager.STREAM_MUSIC,
R.drawable.ic_volume_media, R.drawable.ic_volume_media_mute, true, true);
if (!AudioSystem.isSingleVolume(mContext)) {
- addRow(AudioManager.STREAM_RING,
- R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true, false);
+ if (mSeparateNotification) {
+ addRow(AudioManager.STREAM_RING, R.drawable.ic_ring_volume,
+ R.drawable.ic_ring_volume_off, true, false);
+ } else {
+ addRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer,
+ R.drawable.ic_volume_ringer, true, false);
+ }
+
addRow(STREAM_ALARM,
R.drawable.ic_alarm, R.drawable.ic_volume_alarm_mute, true, false);
addRow(AudioManager.STREAM_VOICE_CALL,
@@ -1309,7 +1401,7 @@
private void showH(int reason, boolean keyguardLocked, int lockTaskModeState) {
Trace.beginSection("VolumeDialogImpl#showH");
- if (D.BUG) Log.d(TAG, "showH r=" + Events.SHOW_REASONS[reason]);
+ Log.i(TAG, "showH r=" + Events.SHOW_REASONS[reason]);
mHandler.removeMessages(H.SHOW);
mHandler.removeMessages(H.DISMISS);
rescheduleTimeoutH();
@@ -1337,7 +1429,7 @@
final int timeout = computeTimeoutH();
mHandler.sendMessageDelayed(mHandler
.obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
- if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
+ Log.i(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
mController.userActivity();
}
@@ -1364,10 +1456,10 @@
protected void dismissH(int reason) {
Trace.beginSection("VolumeDialogImpl#dismissH");
- if (D.BUG) {
- Log.d(TAG, "mDialog.dismiss() reason: " + Events.DISMISS_REASONS[reason]
- + " from: " + Debug.getCaller());
- }
+
+ Log.i(TAG, "mDialog.dismiss() reason: " + Events.DISMISS_REASONS[reason]
+ + " from: " + Debug.getCaller());
+
mHandler.removeMessages(H.DISMISS);
mHandler.removeMessages(H.SHOW);
if (mIsAnimatingDismiss) {
@@ -1532,8 +1624,8 @@
mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
break;
case AudioManager.RINGER_MODE_SILENT:
- mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
- mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+ mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+ mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT,
mContext.getString(R.string.volume_ringer_hint_unmute));
@@ -1542,14 +1634,14 @@
default:
boolean muted = (mAutomute && ss.level == 0) || ss.muted;
if (!isZenMuted && muted) {
- mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
- mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
+ mRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
+ mSelectedRingerIcon.setImageResource(mVolumeRingerMuteIconDrawableId);
addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
mContext.getString(R.string.volume_ringer_hint_unmute));
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
} else {
- mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
- mSelectedRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
+ mRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
+ mSelectedRingerIcon.setImageResource(mVolumeRingerIconDrawableId);
if (mController.hasVibrator()) {
addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
mContext.getString(R.string.volume_ringer_hint_vibrate));
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index c5792b9..8f10fa6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -20,6 +20,7 @@
import android.media.AudioManager;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
@@ -27,11 +28,14 @@
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
import com.android.systemui.volume.VolumePanelFactory;
+import java.util.concurrent.Executor;
+
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -55,7 +59,9 @@
MediaOutputDialogFactory mediaOutputDialogFactory,
VolumePanelFactory volumePanelFactory,
ActivityStarter activityStarter,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ DeviceConfigProxy deviceConfigProxy,
+ @Main Executor executor) {
VolumeDialogImpl impl = new VolumeDialogImpl(
context,
volumeDialogController,
@@ -65,7 +71,9 @@
mediaOutputDialogFactory,
volumePanelFactory,
activityStarter,
- interactionJankMonitor);
+ interactionJankMonitor,
+ deviceConfigProxy,
+ executor);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
impl.setSilentMode(false);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index a4384d5..7033ccd 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -549,7 +549,7 @@
} catch (RemoteException e) {
Log.e(TAG, e.getMessage());
}
- mShadeController.collapsePanel(true);
+ mShadeController.collapseShade(true);
if (entry.getRow() != null) {
entry.getRow().updateBubbleButton();
}
@@ -597,7 +597,7 @@
}
if (shouldBubble) {
- mShadeController.collapsePanel(true);
+ mShadeController.collapseShade(true);
if (entry.getRow() != null) {
entry.getRow().updateBubbleButton();
}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index ea0b03d..d8331ab 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -127,6 +127,12 @@
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true" />
+ <activity android:name=".sensorprivacy.SensorUseStartedActivityTest$SensorUseStartedActivityTestable"
+ android:exported="false"
+ android:theme="@style/Theme.SystemUI.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true" />
+
<provider
android:name="androidx.startup.InitializationProvider"
tools:replace="android:authorities"
@@ -140,6 +146,12 @@
tools:replace="android:authorities"
tools:node="remove" />
+ <provider android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
+ android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.android.systemui.test.fileprovider"
diff --git a/packages/SystemUI/tests/robolectric/config/robolectric.properties b/packages/SystemUI/tests/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..2a75bd9
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/config/robolectric.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
new file mode 100644
index 0000000..188dff2
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.robotests;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import static com.google.common.truth.Truth.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SysuiResourceLoadingTest extends SysuiRoboBase {
+ @Test
+ public void testResources() {
+ assertThat(getContext().getString(com.android.systemui.R.string.app_label))
+ .isEqualTo("System UI");
+ assertThat(getContext().getString(com.android.systemui.tests.R.string.test_content))
+ .isNotEmpty();
+ }
+}
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
new file mode 100644
index 0000000..d9686bb
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.robotests;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+
+public class SysuiRoboBase {
+ public Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
index 0a9c745..ffedb30 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
@@ -46,7 +46,7 @@
@Test
public void testShowsTextField() {
mKeyguardMessageArea.setVisibility(View.INVISIBLE);
- mKeyguardMessageArea.setMessage("oobleck");
+ mKeyguardMessageArea.setMessage("oobleck", /* animate= */ true);
assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
}
@@ -55,7 +55,7 @@
public void testHiddenWhenBouncerHidden() {
mKeyguardMessageArea.setIsVisible(false);
mKeyguardMessageArea.setVisibility(View.INVISIBLE);
- mKeyguardMessageArea.setMessage("oobleck");
+ mKeyguardMessageArea.setMessage("oobleck", /* animate= */ true);
assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
}
@@ -63,7 +63,7 @@
@Test
public void testClearsTextField() {
mKeyguardMessageArea.setVisibility(View.VISIBLE);
- mKeyguardMessageArea.setMessage("");
+ mKeyguardMessageArea.setMessage("", /* animate= */ true);
assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
assertThat(mKeyguardMessageArea.getText()).isEqualTo("");
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
index 7b9b39f..ba46a87 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
@@ -49,30 +49,30 @@
@Test
fun testSetSameMessage() {
val underTestSpy = spy(underTest)
- underTestSpy.setMessage("abc")
- underTestSpy.setMessage("abc")
+ underTestSpy.setMessage("abc", animate = true)
+ underTestSpy.setMessage("abc", animate = true)
verify(underTestSpy, times(1)).text = "abc"
}
@Test
fun testSetDifferentMessage() {
- underTest.setMessage("abc")
- underTest.setMessage("def")
+ underTest.setMessage("abc", animate = true)
+ underTest.setMessage("def", animate = true)
assertThat(underTest.text).isEqualTo("def")
}
@Test
fun testSetNullMessage() {
- underTest.setMessage(null)
+ underTest.setMessage(null, animate = true)
assertThat(underTest.text).isEqualTo("")
}
@Test
fun testSetNullClearsPreviousMessage() {
- underTest.setMessage("something not null")
+ underTest.setMessage("something not null", animate = true)
assertThat(underTest.text).isEqualTo("something not null")
- underTest.setMessage(null)
+ underTest.setMessage(null, animate = true)
assertThat(underTest.text).isEqualTo("")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 52b6b38..e8f8e25 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -155,7 +155,8 @@
verify(configurationController).addCallback(capture(captor))
captor.value.onDensityOrFontScaleChanged()
- verify(events).onFontSettingChanged()
+ verify(smallClockEvents, times(2)).onFontSettingChanged(anyFloat())
+ verify(largeClockEvents, times(2)).onFontSettingChanged(anyFloat())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 131cf7d..afd582a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -63,7 +63,6 @@
credentialAttempted = false,
deviceInteractive = false,
dreaming = false,
- encryptedOrLockdown = false,
fingerprintDisabled = false,
fingerprintLockedOut = false,
goingToSleep = false,
@@ -74,6 +73,7 @@
primaryUser = false,
shouldListenSfpsState = false,
shouldListenForFingerprintAssistant = false,
+ strongerAuthRequired = false,
switchingUser = false,
udfps = false,
userDoesNotHaveTrust = false
@@ -84,10 +84,9 @@
userId = user,
listening = false,
authInterruptActive = false,
- becauseCannotSkipBouncer = false,
biometricSettingEnabledForUser = false,
bouncerFullyShown = false,
- faceAuthenticated = false,
+ faceAndFpNotAuthenticated = false,
faceDisabled = false,
faceLockedOut = false,
fpLockedOut = false,
@@ -101,4 +100,6 @@
secureCameraLaunched = false,
switchingUser = false,
udfpsBouncerShowing = false,
+ udfpsFingerDown = false,
+ userNotTrustedOrDetectionIsNeeded = false
)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 8290084..0e837d2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -85,7 +85,7 @@
@Test
public void testClearsTextField() {
mMessageAreaController.setMessage("");
- verify(mKeyguardMessageArea).setMessage("");
+ verify(mKeyguardMessageArea).setMessage("", /* animate= */ true);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index aa4469f..84f6d91 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -379,9 +379,9 @@
}
@Test
- public void onBouncerVisibilityChanged_needsStrongAuth_sideFpsHintHidden() {
+ public void onBouncerVisibilityChanged_unlockingWithFingerprintNotAllowed_sideFpsHintHidden() {
setupConditionsToEnableSideFpsHint();
- setNeedsStrongAuth(true);
+ setUnlockingWithFingerprintAllowed(false);
reset(mSideFpsController);
mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
@@ -548,6 +548,51 @@
verify(mKeyguardPasswordViewControllerMock, never()).showMessage(null, null);
}
+ @Test
+ public void onDensityorFontScaleChanged() {
+ ArgumentCaptor<ConfigurationController.ConfigurationListener>
+ configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
+ ConfigurationController.ConfigurationListener.class);
+ mKeyguardSecurityContainerController.onViewAttached();
+ verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
+ configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged();
+
+ verify(mView).onDensityOrFontScaleChanged();
+ verify(mKeyguardSecurityViewFlipperController).clearViews();
+ verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
+ any(KeyguardSecurityCallback.class));
+ }
+
+ @Test
+ public void onThemeChanged() {
+ ArgumentCaptor<ConfigurationController.ConfigurationListener>
+ configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
+ ConfigurationController.ConfigurationListener.class);
+ mKeyguardSecurityContainerController.onViewAttached();
+ verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
+ configurationListenerArgumentCaptor.getValue().onThemeChanged();
+
+ verify(mView).reloadColors();
+ verify(mKeyguardSecurityViewFlipperController).clearViews();
+ verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
+ any(KeyguardSecurityCallback.class));
+ }
+
+ @Test
+ public void onUiModeChanged() {
+ ArgumentCaptor<ConfigurationController.ConfigurationListener>
+ configurationListenerArgumentCaptor = ArgumentCaptor.forClass(
+ ConfigurationController.ConfigurationListener.class);
+ mKeyguardSecurityContainerController.onViewAttached();
+ verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
+ configurationListenerArgumentCaptor.getValue().onUiModeChanged();
+
+ verify(mView).reloadColors();
+ verify(mKeyguardSecurityViewFlipperController).clearViews();
+ verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class),
+ any(KeyguardSecurityCallback.class));
+ }
+
private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
mKeyguardSecurityContainerController.onViewAttached();
verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
@@ -558,7 +603,7 @@
attachView();
setSideFpsHintEnabledFromResources(true);
setFingerprintDetectionRunning(true);
- setNeedsStrongAuth(false);
+ setUnlockingWithFingerprintAllowed(true);
}
private void attachView() {
@@ -577,9 +622,8 @@
enabled);
}
- private void setNeedsStrongAuth(boolean needed) {
- when(mKeyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(needed);
- mKeyguardUpdateMonitorCallback.getValue().onStrongAuthStateChanged(/* userId= */ 0);
+ private void setUnlockingWithFingerprintAllowed(boolean allowed) {
+ when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(allowed);
}
private void setupGetSecurityView() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 1d2b09c..36ed669 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -311,6 +311,17 @@
}
@Test
+ public void testOnDensityOrFontScaleChanged() {
+ setupUserSwitcher();
+ View oldUserSwitcher = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ mKeyguardSecurityContainer.onDensityOrFontScaleChanged();
+ View newUserSwitcher = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ assertThat(oldUserSwitcher).isNotEqualTo(newUserSwitcher);
+ }
+
+ @Test
public void testTouchesAreRecognizedAsBeingOnTheOtherSideOfSecurity() {
setupUserSwitcher();
setViewWidth(VIEW_WIDTH);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 9296d3d..1614b57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -106,4 +106,10 @@
}
}
}
+
+ @Test
+ public void onDensityOrFontScaleChanged() {
+ mKeyguardSecurityViewFlipperController.clearViews();
+ verify(mView).removeAllViews();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c6200da..c87a664 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -27,7 +27,9 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
+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.getCurrentUser;
import static com.google.common.truth.Truth.assertThat;
@@ -35,6 +37,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
@@ -88,6 +91,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.service.dreams.IDreamManager;
+import android.service.trust.TrustAgentService;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -112,6 +116,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -156,6 +161,8 @@
private static final int FINGERPRINT_SENSOR_ID = 1;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private DumpManager mDumpManager;
@Mock
private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker;
@@ -275,7 +282,6 @@
componentInfo, FaceSensorProperties.TYPE_UNKNOWN,
false /* supportsFaceDetection */, true /* supportsSelfIllumination */,
false /* resetLockoutRequiresChallenge */));
-
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of(
@@ -306,8 +312,7 @@
ExtendedMockito.doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
.when(SubscriptionManager::getDefaultSubscriptionId);
KeyguardUpdateMonitor.setCurrentUser(mCurrentUserId);
- ExtendedMockito.doReturn(KeyguardUpdateMonitor.getCurrentUser())
- .when(ActivityManager::getCurrentUser);
+ when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig(
@@ -351,7 +356,9 @@
@After
public void tearDown() {
- mMockitoSession.finishMocking();
+ if (mMockitoSession != null) {
+ mMockitoSession.finishMocking();
+ }
cleanupKeyguardUpdateMonitor();
}
@@ -587,30 +594,13 @@
}
@Test
- public void testFingerprintDoesNotAuth_whenEncrypted() {
- testFingerprintWhenStrongAuth(
- STRONG_AUTH_REQUIRED_AFTER_BOOT);
- }
-
- @Test
- public void testFingerprintDoesNotAuth_whenDpmLocked() {
- testFingerprintWhenStrongAuth(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW);
- }
-
- @Test
- public void testFingerprintDoesNotAuth_whenUserLockdown() {
- testFingerprintWhenStrongAuth(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
- }
-
- private void testFingerprintWhenStrongAuth(int strongAuth) {
+ public void testOnlyDetectFingerprint_whenFingerprintUnlockNotAllowed() {
// Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
// will trigger updateBiometricListeningState();
clearInvocations(mFingerprintManager);
mKeyguardUpdateMonitor.resetBiometricListeningState();
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
mTestableLooper.processAllMessages();
@@ -619,6 +609,64 @@
}
@Test
+ public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() {
+ // GIVEN unlocking with biometric is allowed
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+
+ // THEN unlocking with face and fp is allowed
+ Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE));
+ Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
+ public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() {
+ // GIVEN unlocking with biometric is not allowed
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+
+ // THEN unlocking with face is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE));
+ }
+
+ @Test
+ public void testUnlockingWithFaceAllowed_fingerprintLockout() {
+ // GIVEN unlocking with biometric is allowed
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+
+ // WHEN fingerprint is locked out
+ fingerprintErrorLockedOut();
+
+ // THEN unlocking with face is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE));
+ }
+
+ @Test
+ public void testUnlockingWithFpAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() {
+ // GIVEN unlocking with biometric is not allowed
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+
+ // THEN unlocking with fingerprint is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
+ public void testUnlockingWithFpAllowed_fingerprintLockout() {
+ // GIVEN unlocking with biometric is allowed
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+
+ // WHEN fingerprint is locked out
+ fingerprintErrorLockedOut();
+
+ // THEN unlocking with fingeprint is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
public void testTriesToAuthenticate_whenBouncer() {
setKeyguardBouncerVisibility(true);
@@ -921,10 +969,6 @@
faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
final boolean fpLocked =
fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
- when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
- .thenReturn(fingerprintLockoutMode);
- when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
- .thenReturn(faceLockoutMode);
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
@@ -933,7 +977,13 @@
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
anyInt());
+// resetFaceManager();
+// resetFingerprintManager();
+ when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser)))
+ .thenReturn(fingerprintLockoutMode);
+ when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
+ .thenReturn(faceLockoutMode);
final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
@@ -944,14 +994,22 @@
mKeyguardUpdateMonitor.handleUserSwitchComplete(newUser);
mTestableLooper.processAllMessages();
- verify(faceCancel, faceLocked ? times(1) : never()).cancel();
- verify(fpCancel, fpLocked ? times(1) : never()).cancel();
- verify(callback, faceLocked ? times(1) : never()).onBiometricRunningStateChanged(
+ // THEN face and fingerprint listening are always cancelled immediately
+ verify(faceCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
eq(false), eq(BiometricSourceType.FACE));
- verify(callback, fpLocked ? times(1) : never()).onBiometricRunningStateChanged(
+ verify(fpCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
eq(false), eq(BiometricSourceType.FINGERPRINT));
+
+ // THEN locked out states are updated
assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
+
+ // Fingerprint should be restarted once its cancelled bc on lockout, the device
+ // can still detectFingerprint (and if it's not locked out, fingerprint can listen)
+ assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
+ .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING);
}
@Test
@@ -1032,6 +1090,7 @@
@Test
public void testSecondaryLockscreenRequirement() {
KeyguardUpdateMonitor.setCurrentUser(UserHandle.myUserId());
+ when(mUserTracker.getUserId()).thenReturn(UserHandle.myUserId());
int user = KeyguardUpdateMonitor.getCurrentUser();
String packageName = "fake.test.package";
String cls = "FakeService";
@@ -1136,9 +1195,8 @@
// GIVEN status bar state is on the keyguard
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
- // WHEN user hasn't authenticated since last boot
- when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
- .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ // WHEN user hasn't authenticated since last boot, cannot unlock with FP
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
// THEN we shouldn't listen for udfps
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1251,8 +1309,7 @@
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
// WHEN device in lock down
- when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
- KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
// THEN we shouldn't listen for udfps
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
@@ -1286,6 +1343,24 @@
}
@Test
+ public void testRequestFaceAuthFromOccludingApp_whenInvoked_startsFaceAuth() {
+ mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
+
+ assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue();
+ }
+
+ @Test
+ public void testRequestFaceAuthFromOccludingApp_whenInvoked_stopsFaceAuth() {
+ mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
+
+ assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isTrue();
+
+ mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
+
+ assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isFalse();
+ }
+
+ @Test
public void testRequireUnlockForNfc_Broadcast() {
KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
mKeyguardUpdateMonitor.registerCallback(callback);
@@ -1314,7 +1389,10 @@
Arrays.asList("Unlocked by wearable"));
// THEN the showTrustGrantedMessage should be called with the first message
- verify(mTestCallback).showTrustGrantedMessage("Unlocked by wearable");
+ verify(mTestCallback).onTrustGrantedForCurrentUser(
+ anyBoolean(),
+ eq(new TrustGrantFlags(0)),
+ eq("Unlocked by wearable"));
}
@Test
@@ -1371,6 +1449,29 @@
}
@Test
+ public void testShouldListenForFace_whenFpIsAlreadyAuthenticated_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ bouncerFullyVisibleAndNotGoingToSleep();
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ strongAuthNotRequired();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ successfulFingerprintAuth();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException {
cleanupKeyguardUpdateMonitor();
// This disables face auth
@@ -1724,6 +1825,155 @@
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_deviceInteractive() {
+ // GIVEN device is interactive
+ deviceIsInteractive();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+ eq(null) /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_doesNotDismiss() {
+ // GIVEN device is NOT interactive
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback).onTrustGrantedForCurrentUser(
+ eq(false) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+ eq(null) /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_temporaryAndRenewable() {
+ // GIVEN device is interactive
+ deviceIsInteractive();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged for a different user
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ 546 /* userId, not the current userId */,
+ 0 /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback, never()).onTrustGrantedForCurrentUser(
+ anyBoolean() /* dismissKeyguard */,
+ anyObject() /* flags */,
+ anyString() /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGranted_differentUser_noCallback() {
+ // GIVEN device is interactive
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD AND TRUST_TEMPORARY_AND_RENEWABLE
+ // flags (temporary & rewable is active unlock)
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)),
+ eq(null) /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_bouncerShowing_initiatedByUser() {
+ // GIVEN device is interactive & bouncer is showing
+ deviceIsInteractive();
+ bouncerFullyVisible();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with INITIATED_BY_USER flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId, not the current userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback, never()).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER)),
+ anyString() /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_bouncerShowing_temporaryRenewable() {
+ // GIVEN device is NOT interactive & bouncer is showing
+ bouncerFullyVisible();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with INITIATED_BY_USER flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId, not the current userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback, never()).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)),
+ anyString() /* message */
+ );
+ }
+
private void cleanupKeyguardUpdateMonitor() {
if (mKeyguardUpdateMonitor != null) {
mKeyguardUpdateMonitor.removeCallback(mTestCallback);
@@ -1775,6 +2025,15 @@
.onAuthenticationAcquired(FINGERPRINT_ACQUIRED_START);
}
+ private void successfulFingerprintAuth() {
+ mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
+ .onAuthenticationSucceeded(
+ new FingerprintManager.AuthenticationResult(null,
+ null,
+ mCurrentUserId,
+ true));
+ }
+
private void triggerSuccessfulFaceAuth() {
mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
verify(mFaceManager).authenticate(any(),
@@ -1884,7 +2143,7 @@
AtomicBoolean mSimStateChanged = new AtomicBoolean(false);
protected TestableKeyguardUpdateMonitor(Context context) {
- super(context,
+ super(context, mUserTracker,
TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
mBroadcastDispatcher, mSecureSettings, mDumpManager,
mBackgroundExecutor, mMainExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index ff4412e9..7a5b772 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -30,15 +31,15 @@
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
-import androidx.lifecycle.MutableLiveData;
-
import com.android.systemui.SysuiTestCase;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.settings.CurrentUserObservable;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
@@ -52,8 +53,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-// Need to run tests on main looper because LiveData operations such as setData, observe,
-// removeObserver cannot be invoked on a background thread.
+// Need to run tests on main looper to allow for onClockChanged operation to happen synchronously.
@RunWithLooper(setAsMainLooper = true)
public final class ClockManagerTest extends SysuiTestCase {
@@ -63,14 +63,16 @@
private static final int SECONDARY_USER_ID = 11;
private static final Uri SETTINGS_URI = null;
+ private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
private ClockManager mClockManager;
private ContentObserver mContentObserver;
private DockManagerFake mFakeDockManager;
- private MutableLiveData<Integer> mCurrentUser;
+ private ArgumentCaptor<UserTracker.Callback> mUserTrackerCallbackCaptor;
@Mock PluginManager mMockPluginManager;
@Mock SysuiColorExtractor mMockColorExtractor;
@Mock ContentResolver mMockContentResolver;
- @Mock CurrentUserObservable mMockCurrentUserObserable;
+ @Mock UserTracker mUserTracker;
@Mock SettingsWrapper mMockSettingsWrapper;
@Mock ClockManager.ClockChangedListener mMockListener1;
@Mock ClockManager.ClockChangedListener mMockListener2;
@@ -83,18 +85,18 @@
mFakeDockManager = new DockManagerFake();
- mCurrentUser = new MutableLiveData<>();
- mCurrentUser.setValue(MAIN_USER_ID);
- when(mMockCurrentUserObserable.getCurrentUser()).thenReturn(mCurrentUser);
+ when(mUserTracker.getUserId()).thenReturn(MAIN_USER_ID);
+ mUserTrackerCallbackCaptor = ArgumentCaptor.forClass(UserTracker.Callback.class);
mClockManager = new ClockManager(getContext(), inflater,
mMockPluginManager, mMockColorExtractor, mMockContentResolver,
- mMockCurrentUserObserable, mMockSettingsWrapper, mFakeDockManager);
+ mUserTracker, mMainExecutor, mMockSettingsWrapper, mFakeDockManager);
mClockManager.addBuiltinClock(() -> new BubbleClockController(
getContext().getResources(), inflater, mMockColorExtractor));
mClockManager.addOnClockChangedListener(mMockListener1);
mClockManager.addOnClockChangedListener(mMockListener2);
+ verify(mUserTracker).addCallback(mUserTrackerCallbackCaptor.capture(), any());
reset(mMockListener1, mMockListener2);
mContentObserver = mClockManager.getContentObserver();
@@ -221,7 +223,7 @@
@Test
public void onUserChanged_defaultClock() {
// WHEN the user changes
- mCurrentUser.setValue(SECONDARY_USER_ID);
+ switchUser(SECONDARY_USER_ID);
// THEN the plugin is null for the default clock face
assertThat(mClockManager.getCurrentClock()).isNull();
}
@@ -232,7 +234,7 @@
when(mMockSettingsWrapper.getLockScreenCustomClockFace(SECONDARY_USER_ID)).thenReturn(
BUBBLE_CLOCK);
// WHEN the user changes
- mCurrentUser.setValue(SECONDARY_USER_ID);
+ switchUser(SECONDARY_USER_ID);
// THEN the plugin is the bubble clock face.
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
@@ -244,8 +246,13 @@
// AND the second user as selected the bubble clock for the dock
when(mMockSettingsWrapper.getDockedClockFace(SECONDARY_USER_ID)).thenReturn(BUBBLE_CLOCK);
// WHEN the user changes
- mCurrentUser.setValue(SECONDARY_USER_ID);
+ switchUser(SECONDARY_USER_ID);
// THEN the plugin is the bubble clock face.
assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
}
+
+ private void switchUser(int newUser) {
+ when(mUserTracker.getUserId()).thenReturn(newUser);
+ mUserTrackerCallbackCaptor.getValue().onUserChanged(newUser, mContext);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 181839a..0627fc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -77,7 +77,6 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.decor.CornerDecorProvider;
import com.android.systemui.decor.CutoutDecorProviderFactory;
import com.android.systemui.decor.CutoutDecorProviderImpl;
@@ -132,8 +131,6 @@
@Mock
private TunerService mTunerService;
@Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
private UserTracker mUserTracker;
@Mock
private PrivacyDotViewController mDotViewController;
@@ -223,8 +220,8 @@
mExecutor));
mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
- mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,
- mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) {
+ mTunerService, mUserTracker, mDotViewController, mThreadFactory,
+ mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory) {
@Override
public void start() {
super.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
new file mode 100644
index 0000000..982f033
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/AccessorizedBatteryDrawableTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class AccessorizedBatteryDrawableTest : SysuiTestCase() {
+ @Test
+ fun intrinsicSize_shieldFalse_isBatterySize() {
+ val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+ drawable.displayShield = false
+
+ val density = context.resources.displayMetrics.density
+ assertThat(drawable.intrinsicHeight).isEqualTo((BATTERY_HEIGHT * density).toInt())
+ assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH * density).toInt())
+ }
+
+ @Test
+ fun intrinsicSize_shieldTrue_isBatteryPlusShieldSize() {
+ val drawable = AccessorizedBatteryDrawable(context, frameColor = 0)
+ drawable.displayShield = true
+
+ val density = context.resources.displayMetrics.density
+ assertThat(drawable.intrinsicHeight)
+ .isEqualTo((BATTERY_HEIGHT_WITH_SHIELD * density).toInt())
+ assertThat(drawable.intrinsicWidth).isEqualTo((BATTERY_WIDTH_WITH_SHIELD * density).toInt())
+ }
+
+ // TODO(b/255625888): Screenshot tests for this drawable would be amazing!
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 1d038a4..1482f29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -34,7 +34,9 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
@@ -50,15 +52,16 @@
private BatteryMeterView mBatteryMeterView;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private ConfigurationController mConfigurationController;
@Mock
private TunerService mTunerService;
@Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
private Handler mHandler;
@Mock
private ContentResolver mContentResolver;
+ private FakeFeatureFlags mFeatureFlags;
@Mock
private BatteryController mBatteryController;
@@ -71,19 +74,13 @@
when(mBatteryMeterView.getContext()).thenReturn(mContext);
when(mBatteryMeterView.getResources()).thenReturn(mContext.getResources());
- mController = new BatteryMeterViewController(
- mBatteryMeterView,
- mConfigurationController,
- mTunerService,
- mBroadcastDispatcher,
- mHandler,
- mContentResolver,
- mBatteryController
- );
+ mFeatureFlags = new FakeFeatureFlags();
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
}
@Test
public void onViewAttached_callbacksRegistered() {
+ initController();
mController.onViewAttached();
verify(mConfigurationController).addCallback(any());
@@ -101,6 +98,7 @@
@Test
public void onViewDetached_callbacksUnregistered() {
+ initController();
// Set everything up first.
mController.onViewAttached();
@@ -114,6 +112,7 @@
@Test
public void ignoreTunerUpdates_afterOnViewAttached_callbackUnregistered() {
+ initController();
// Start out receiving tuner updates
mController.onViewAttached();
@@ -124,10 +123,43 @@
@Test
public void ignoreTunerUpdates_beforeOnViewAttached_callbackNeverRegistered() {
+ initController();
+
mController.ignoreTunerUpdates();
mController.onViewAttached();
verify(mTunerService, never()).addTunable(any(), any());
}
+
+ @Test
+ public void shieldFlagDisabled_viewNotified() {
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, false);
+
+ initController();
+
+ verify(mBatteryMeterView).setDisplayShieldEnabled(false);
+ }
+
+ @Test
+ public void shieldFlagEnabled_viewNotified() {
+ mFeatureFlags.set(Flags.BATTERY_SHIELD_ICON, true);
+
+ initController();
+
+ verify(mBatteryMeterView).setDisplayShieldEnabled(true);
+ }
+
+ private void initController() {
+ mController = new BatteryMeterViewController(
+ mBatteryMeterView,
+ mUserTracker,
+ mConfigurationController,
+ mTunerService,
+ mHandler,
+ mContentResolver,
+ mFeatureFlags,
+ mBatteryController
+ );
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index b4ff2a5..eb7d9c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -17,7 +17,9 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.widget.ImageView
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
import com.android.systemui.statusbar.policy.BatteryController.EstimateFetchCompletion
@@ -58,6 +60,182 @@
// No assert needed
}
+ @Test
+ fun contentDescription_unknown() {
+ mBatteryMeterView.onBatteryUnknownStateChanged(true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_unknown)
+ )
+ }
+
+ @Test
+ fun contentDescription_estimate() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+ )
+ )
+ }
+
+ @Test
+ fun contentDescription_estimateAndOverheated() {
+ mBatteryMeterView.onBatteryLevelChanged(17, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_charging_paused_with_estimate,
+ 17,
+ ESTIMATE,
+ )
+ )
+ }
+
+ @Test
+ fun contentDescription_overheated() {
+ mBatteryMeterView.onBatteryLevelChanged(90, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+ )
+ }
+
+ @Test
+ fun contentDescription_charging() {
+ mBatteryMeterView.onBatteryLevelChanged(45, true)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging, 45)
+ )
+ }
+
+ @Test
+ fun contentDescription_notCharging() {
+ mBatteryMeterView.onBatteryLevelChanged(45, false)
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 45)
+ )
+ }
+
+ @Test
+ fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+ )
+ )
+
+ // Update the show mode from estimate to percent
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+
+ assertThat(mBatteryMeterView.batteryPercentViewText).isEqualTo("15%")
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 15)
+ )
+ }
+
+ @Test
+ fun contentDescription_manyUpdates_alwaysUpdated() {
+ // Overheated
+ mBatteryMeterView.onBatteryLevelChanged(90, false)
+ mBatteryMeterView.onIsOverheatedChanged(true)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging_paused, 90)
+ )
+
+ // Overheated & estimate
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+ mBatteryMeterView.updatePercentText()
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_charging_paused_with_estimate,
+ 90,
+ ESTIMATE,
+ )
+ )
+
+ // Just estimate
+ mBatteryMeterView.onIsOverheatedChanged(false)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate,
+ 90,
+ ESTIMATE,
+ )
+ )
+
+ // Just percent
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 90)
+ )
+
+ // Charging
+ mBatteryMeterView.onBatteryLevelChanged(90, true)
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level_charging, 90)
+ )
+ }
+
+ @Test
+ fun isOverheatedChanged_true_drawableGetsTrue() {
+ mBatteryMeterView.setDisplayShieldEnabled(true)
+ val drawable = getBatteryDrawable()
+
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(drawable.displayShield).isTrue()
+ }
+
+ @Test
+ fun isOverheatedChanged_false_drawableGetsFalse() {
+ mBatteryMeterView.setDisplayShieldEnabled(true)
+ val drawable = getBatteryDrawable()
+
+ // Start as true
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ // Update to false
+ mBatteryMeterView.onIsOverheatedChanged(false)
+
+ assertThat(drawable.displayShield).isFalse()
+ }
+
+ @Test
+ fun isOverheatedChanged_true_featureflagOff_drawableGetsFalse() {
+ mBatteryMeterView.setDisplayShieldEnabled(false)
+ val drawable = getBatteryDrawable()
+
+ mBatteryMeterView.onIsOverheatedChanged(true)
+
+ assertThat(drawable.displayShield).isFalse()
+ }
+
+ private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
+ return (mBatteryMeterView.getChildAt(0) as ImageView)
+ .drawable as AccessorizedBatteryDrawable
+ }
+
private class Fetcher : BatteryEstimateFetcher {
override fun fetchBatteryTimeRemainingEstimate(
completion: EstimateFetchCompletion) {
@@ -68,4 +246,4 @@
private companion object {
const val ESTIMATE = "2 hours 2 minutes"
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
new file mode 100644
index 0000000..39cb047
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatterySpecsTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.battery
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT
+import com.android.systemui.battery.BatterySpecs.BATTERY_HEIGHT_WITH_SHIELD
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH
+import com.android.systemui.battery.BatterySpecs.BATTERY_WIDTH_WITH_SHIELD
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class BatterySpecsTest : SysuiTestCase() {
+ @Test
+ fun getFullBatteryHeight_shieldFalse_returnsMainHeight() {
+ val fullHeight = BatterySpecs.getFullBatteryHeight(56f, displayShield = false)
+
+ assertThat(fullHeight).isEqualTo(56f)
+ }
+
+ @Test
+ fun getFullBatteryHeight_shieldTrue_returnsMainHeightPlusShield() {
+ val mainHeight = BATTERY_HEIGHT * 5
+ val fullHeight = BatterySpecs.getFullBatteryHeight(mainHeight, displayShield = true)
+
+ // Since the main battery was scaled 5x, the output height should also be scaled 5x
+ val expectedFullHeight = BATTERY_HEIGHT_WITH_SHIELD * 5
+
+ assertThat(fullHeight).isWithin(.0001f).of(expectedFullHeight)
+ }
+
+ @Test
+ fun getFullBatteryWidth_shieldFalse_returnsMainWidth() {
+ val fullWidth = BatterySpecs.getFullBatteryWidth(33f, displayShield = false)
+
+ assertThat(fullWidth).isEqualTo(33f)
+ }
+
+ @Test
+ fun getFullBatteryWidth_shieldTrue_returnsMainWidthPlusShield() {
+ val mainWidth = BATTERY_WIDTH * 3.3f
+
+ val fullWidth = BatterySpecs.getFullBatteryWidth(mainWidth, displayShield = true)
+
+ // Since the main battery was scaled 3.3x, the output width should also be scaled 5x
+ val expectedFullWidth = BATTERY_WIDTH_WITH_SHIELD * 3.3f
+ assertThat(fullWidth).isWithin(.0001f).of(expectedFullWidth)
+ }
+
+ @Test
+ fun getMainBatteryHeight_shieldFalse_returnsFullHeight() {
+ val mainHeight = BatterySpecs.getMainBatteryHeight(89f, displayShield = false)
+
+ assertThat(mainHeight).isEqualTo(89f)
+ }
+
+ @Test
+ fun getMainBatteryHeight_shieldTrue_returnsNotFullHeight() {
+ val fullHeight = BATTERY_HEIGHT_WITH_SHIELD * 7.7f
+
+ val mainHeight = BatterySpecs.getMainBatteryHeight(fullHeight, displayShield = true)
+
+ // Since the full height was scaled 7.7x, the main height should also be scaled 7.7x.
+ val expectedHeight = BATTERY_HEIGHT * 7.7f
+ assertThat(mainHeight).isWithin(.0001f).of(expectedHeight)
+ }
+
+ @Test
+ fun getMainBatteryWidth_shieldFalse_returnsFullWidth() {
+ val mainWidth = BatterySpecs.getMainBatteryWidth(2345f, displayShield = false)
+
+ assertThat(mainWidth).isEqualTo(2345f)
+ }
+
+ @Test
+ fun getMainBatteryWidth_shieldTrue_returnsNotFullWidth() {
+ val fullWidth = BATTERY_WIDTH_WITH_SHIELD * 0.6f
+
+ val mainWidth = BatterySpecs.getMainBatteryWidth(fullWidth, displayShield = true)
+
+ // Since the full width was scaled 0.6x, the main height should also be scaled 0.6x.
+ val expectedWidth = BATTERY_WIDTH * 0.6f
+ assertThat(mainWidth).isWithin(.0001f).of(expectedWidth)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index eaef159..5e6acd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -111,6 +111,21 @@
}
@Test
+ fun testCredentialPasswordDismissesOnBack() {
+ val container = initializeCredentialPasswordContainer(addToView = true)
+ assertThat(container.parent).isNotNull()
+ val root = container.rootView
+
+ // Simulate back invocation
+ container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK))
+ container.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK))
+ waitForIdleSync()
+
+ assertThat(container.parent).isNull()
+ assertThat(root.isAttachedToWindow).isFalse()
+ }
+
+ @Test
fun testIgnoresAnimatedInWhenDismissed() {
val container = initializeFingerprintContainer(addToView = false)
container.dismissFromSystemServer()
@@ -355,20 +370,7 @@
@Test
fun testCredentialUI_disablesClickingOnBackground() {
- whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
- whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- )
-
- // In the credential view, clicking on the background (to cancel authentication) is not
- // valid. Thus, the listener should be null, and it should not be in the accessibility
- // hierarchy.
- val container = initializeFingerprintContainer(
- authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
- )
- waitForIdleSync()
-
- assertThat(container.hasCredentialPasswordView()).isTrue()
+ val container = initializeCredentialPasswordContainer()
assertThat(container.hasBiometricPrompt()).isFalse()
assertThat(
container.findViewById<View>(R.id.background)?.isImportantForAccessibility
@@ -428,6 +430,27 @@
verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
}
+ private fun initializeCredentialPasswordContainer(
+ addToView: Boolean = true,
+ ): TestAuthContainerView {
+ whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
+ whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
+ )
+
+ // In the credential view, clicking on the background (to cancel authentication) is not
+ // valid. Thus, the listener should be null, and it should not be in the accessibility
+ // hierarchy.
+ val container = initializeFingerprintContainer(
+ authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
+ addToView = addToView,
+ )
+ waitForIdleSync()
+
+ assertThat(container.hasCredentialPasswordView()).isTrue()
+ return container
+ }
+
private fun initializeFingerprintContainer(
authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
addToView: Boolean = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 0b528a5..eb8c823 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -37,7 +37,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.leak.RotationUtils
-import javax.inject.Provider
+import com.android.systemui.util.mockito.any
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -46,15 +46,16 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
-import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
+import javax.inject.Provider
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -118,12 +119,13 @@
@Test
fun testFingerprintTrigger_KeyguardShowing_Ripple() {
- // GIVEN fp exists, keyguard is showing, user doesn't need strong auth
+ // GIVEN fp exists, keyguard is showing, unlocking with fp allowed
val fpsLocation = Point(5, 5)
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
// WHEN fingerprint authenticated
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
@@ -140,11 +142,12 @@
@Test
fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() {
- // GIVEN fp exists & user doesn't need strong auth
+ // GIVEN fp exists & unlocking with fp allowed
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FINGERPRINT))).thenReturn(true)
// WHEN keyguard is NOT showing & fingerprint authenticated
`when`(keyguardStateController.isShowing).thenReturn(false)
@@ -160,15 +163,16 @@
}
@Test
- fun testFingerprintTrigger_StrongAuthRequired_NoRipple() {
+ fun testFingerprintTrigger_biometricUnlockNotAllowed_NoRipple() {
// GIVEN fp exists & keyguard is showing
val fpsLocation = Point(5, 5)
`when`(authController.udfpsLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
- // WHEN user needs strong auth & fingerprint authenticated
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(true)
+ // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FINGERPRINT))).thenReturn(false)
val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
captor.value.onBiometricAuthenticated(
@@ -182,13 +186,14 @@
@Test
fun testFaceTriggerBypassEnabled_Ripple() {
- // GIVEN face auth sensor exists, keyguard is showing & strong auth isn't required
+ // GIVEN face auth sensor exists, keyguard is showing & unlocking with face is allowed
val faceLocation = Point(5, 5)
`when`(authController.faceSensorLocation).thenReturn(faceLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
- `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE)).thenReturn(true)
// WHEN bypass is enabled & face authenticated
`when`(bypassController.canBypass()).thenReturn(true)
@@ -275,6 +280,8 @@
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT)).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
controller.showUnlockRipple(BiometricSourceType.FINGERPRINT)
@@ -295,6 +302,8 @@
`when`(keyguardStateController.isShowing).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
`when`(authController.isUdfpsFingerDown).thenReturn(true)
+ `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ eq(BiometricSourceType.FACE))).thenReturn(true)
controller.showUnlockRipple(BiometricSourceType.FACE)
assertTrue("reveal didn't start on keyguardFadingAway",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index acdafe3..b267a5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -70,8 +70,13 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.udfps.InteractionEvent;
+import com.android.systemui.biometrics.udfps.NormalizedTouchData;
+import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
+import com.android.systemui.biometrics.udfps.TouchProcessorResult;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
@@ -190,6 +195,8 @@
private AlternateUdfpsTouchProvider mAlternateTouchProvider;
@Mock
private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+ @Mock
+ private SinglePointerTouchProcessor mSinglePointerTouchProcessor;
// Capture listeners so that they can be used to send events
@Captor
@@ -275,7 +282,7 @@
mDisplayManager, mHandler, mConfigurationController, mSystemClock,
mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
mActivityLaunchAnimator, alternateTouchProvider, mBiometricsExecutor,
- mPrimaryBouncerInteractor);
+ mPrimaryBouncerInteractor, mSinglePointerTouchProcessor);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -1086,4 +1093,100 @@
anyString(),
any());
}
+
+ @Test
+ public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath()
+ throws RemoteException {
+ // Disable new touch detection.
+ when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(false);
+
+ // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+ initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+ // Configure UdfpsView to accept the ACTION_DOWN event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+ // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+ // WHEN ACTION_DOWN is received
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricsExecutor.runAllReady();
+ downEvent.recycle();
+
+ // AND ACTION_MOVE is received
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+ mBiometricsExecutor.runAllReady();
+ moveEvent.recycle();
+
+ // AND ACTION_UP is received
+ MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
+ mBiometricsExecutor.runAllReady();
+ upEvent.recycle();
+
+ // THEN the old FingerprintManager path is invoked.
+ verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
+ anyFloat(), anyFloat());
+ verify(mFingerprintManager).onPointerUp(anyLong(), anyInt());
+ }
+
+ @Test
+ public void onTouch_withNewTouchDetection_shouldCallOldFingerprintManagerPath()
+ throws RemoteException {
+ final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+ 0L);
+ final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch(
+ InteractionEvent.DOWN, 1 /* pointerId */, touchData);
+ final TouchProcessorResult processorResultUp = new TouchProcessorResult.ProcessedTouch(
+ InteractionEvent.UP, 1 /* pointerId */, touchData);
+
+ // Enable new touch detection.
+ when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+ // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+ initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+ // Configure UdfpsView to accept the ACTION_DOWN event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+ // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+ // WHEN ACTION_DOWN is received
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultDown);
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricsExecutor.runAllReady();
+ downEvent.recycle();
+
+ // AND ACTION_UP is received
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultUp);
+ MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
+ mBiometricsExecutor.runAllReady();
+ upEvent.recycle();
+
+ // THEN the new FingerprintManager path is invoked.
+ verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(),
+ anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+ verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(),
+ anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java
new file mode 100644
index 0000000..60a0258
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsEnrollViewTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class UdfpsEnrollViewTest extends SysuiTestCase {
+
+ private static String ENROLL_PROGRESS_COLOR_LIGHT = "#699FF3";
+ private static String ENROLL_PROGRESS_COLOR_DARK = "#7DA7F1";
+
+ @Test
+ public void fingerprintUdfpsEnroll_usesCorrectThemeCheckmarkFillColor() {
+ final Configuration config = mContext.getResources().getConfiguration();
+ final boolean isDarkThemeOn = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+ final int currentColor = mContext.getColor(R.color.udfps_enroll_progress);
+
+ assertThat(currentColor).isEqualTo(Color.parseColor(isDarkThemeOn
+ ? ENROLL_PROGRESS_COLOR_DARK : ENROLL_PROGRESS_COLOR_LIGHT));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index 75629f4..3c61382 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -94,6 +94,11 @@
mKeyguardStateControllerCallbackCaptor;
protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+ private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.KeyguardViewManagerCallback>
+ mKeyguardViewManagerCallbackArgumentCaptor;
+ protected StatusBarKeyguardViewManager.KeyguardViewManagerCallback mKeyguardViewManagerCallback;
+
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -143,15 +148,22 @@
}
public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
- return createUdfpsKeyguardViewController(false);
+ return createUdfpsKeyguardViewController(false, false);
+ }
+
+ public void captureKeyGuardViewManagerCallback() {
+ verify(mStatusBarKeyguardViewManager).addCallback(
+ mKeyguardViewManagerCallbackArgumentCaptor.capture());
+ mKeyguardViewManagerCallback = mKeyguardViewManagerCallbackArgumentCaptor.getValue();
}
protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
- boolean useModernBouncer) {
+ boolean useModernBouncer, boolean useExpandedOverlay) {
mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
+ mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(
useModernBouncer ? null : mBouncer);
- return new UdfpsKeyguardViewController(
+ UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController(
mView,
mStatusBarStateController,
mShadeExpansionStateManager,
@@ -168,5 +180,6 @@
mActivityLaunchAnimator,
mFeatureFlags,
mPrimaryBouncerInteractor);
+ return controller;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 16728b6..babe533 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
@@ -30,6 +31,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -52,7 +54,8 @@
@Override
public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
- return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
+ return createUdfpsKeyguardViewController(/* useModernBouncer */ false,
+ /* useExpandedOverlay */ false);
}
@Test
@@ -422,4 +425,37 @@
verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
}
+
+ @Test
+ // TODO(b/259264861): Tracking Bug
+ public void testUdfpsExpandedOverlayOn() {
+ // GIVEN view is attached and useExpandedOverlay is true
+ mController = createUdfpsKeyguardViewController(false, true);
+ mController.onViewAttached();
+ captureKeyGuardViewManagerCallback();
+
+ // WHEN a touch is received
+ mKeyguardViewManagerCallback.onTouch(
+ MotionEvent.obtain(0, 0, 0, 0, 0, 0));
+
+ // THEN udfpsController onTouch is not called
+ assertTrue(mView.mUseExpandedOverlay);
+ verify(mUdfpsController, never()).onTouch(any());
+ }
+
+ @Test
+ // TODO(b/259264861): Tracking Bug
+ public void testUdfpsExpandedOverlayOff() {
+ // GIVEN view is attached and useExpandedOverlay is false
+ mController.onViewAttached();
+ captureKeyGuardViewManagerCallback();
+
+ // WHEN a touch is received
+ mKeyguardViewManagerCallback.onTouch(
+ MotionEvent.obtain(0, 0, 0, 0, 0, 0));
+
+ // THEN udfpsController onTouch is called
+ assertFalse(mView.mUseExpandedOverlay);
+ verify(mUdfpsController).onTouch(any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 68e744e..517e27a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -72,7 +72,10 @@
mock(KeyguardBypassController::class.java),
mKeyguardUpdateMonitor
)
- return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
+ return createUdfpsKeyguardViewController(
+ /* useModernBouncer */ true, /* useExpandedOverlay */
+ false
+ )
}
/** After migration, replaces LockIconViewControllerTest version */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
new file mode 100644
index 0000000..4f89b69
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.graphics.Rect
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@SmallTest
+@RunWith(Parameterized::class)
+class BoundingBoxOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
+ val underTest = BoundingBoxOverlapDetector()
+
+ @Test
+ fun isGoodOverlap() {
+ val touchData = TOUCH_DATA.copy(x = testCase.x.toFloat(), y = testCase.y.toFloat())
+ val actual = underTest.isGoodOverlap(touchData, SENSOR)
+
+ assertThat(actual).isEqualTo(testCase.expected)
+ }
+
+ data class TestCase(val x: Int, val y: Int, val expected: Boolean)
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun data(): List<TestCase> =
+ listOf(
+ genPositiveTestCases(
+ validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
+ validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
+ ),
+ genNegativeTestCases(
+ invalidXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
+ invalidYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1),
+ validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
+ validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
+ )
+ )
+ .flatten()
+ }
+}
+
+/* Placeholder touch parameters. */
+private const val POINTER_ID = 42
+private const val NATIVE_MINOR = 2.71828f
+private const val NATIVE_MAJOR = 3.14f
+private const val ORIENTATION = 1.23f
+private const val TIME = 12345699L
+private const val GESTURE_START = 12345600L
+
+/* Template [NormalizedTouchData]. */
+private val TOUCH_DATA =
+ NormalizedTouchData(
+ POINTER_ID,
+ x = 0f,
+ y = 0f,
+ NATIVE_MINOR,
+ NATIVE_MAJOR,
+ ORIENTATION,
+ TIME,
+ GESTURE_START
+ )
+
+private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */)
+
+private fun genTestCases(
+ xs: List<Int>,
+ ys: List<Int>,
+ expected: Boolean
+): List<BoundingBoxOverlapDetectorTest.TestCase> {
+ return xs.flatMap { x ->
+ ys.map { y -> BoundingBoxOverlapDetectorTest.TestCase(x, y, expected) }
+ }
+}
+
+private fun genPositiveTestCases(
+ validXs: List<Int>,
+ validYs: List<Int>,
+) = genTestCases(validXs, validYs, expected = true)
+
+private fun genNegativeTestCases(
+ invalidXs: List<Int>,
+ invalidYs: List<Int>,
+ validXs: List<Int>,
+ validYs: List<Int>,
+): List<BoundingBoxOverlapDetectorTest.TestCase> {
+ return genTestCases(invalidXs, validYs, expected = false) +
+ genTestCases(validXs, invalidYs, expected = false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
new file mode 100644
index 0000000..834d0a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/NormalizedTouchDataTest.kt
@@ -0,0 +1,90 @@
+package com.android.systemui.biometrics.udfps
+
+import android.graphics.Rect
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@SmallTest
+@RunWith(Parameterized::class)
+class NormalizedTouchDataTest(val testCase: TestCase) : SysuiTestCase() {
+
+ @Test
+ fun isWithinSensor() {
+ val touchData = TOUCH_DATA.copy(x = testCase.x.toFloat(), y = testCase.y.toFloat())
+ val actual = touchData.isWithinSensor(SENSOR)
+
+ assertThat(actual).isEqualTo(testCase.expected)
+ }
+
+ data class TestCase(val x: Int, val y: Int, val expected: Boolean)
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun data(): List<TestCase> =
+ listOf(
+ genPositiveTestCases(
+ validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
+ validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
+ ),
+ genNegativeTestCases(
+ invalidXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
+ invalidYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1),
+ validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
+ validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
+ )
+ )
+ .flatten()
+ }
+}
+
+/* Placeholder touch parameters. */
+private const val POINTER_ID = 42
+private const val NATIVE_MINOR = 2.71828f
+private const val NATIVE_MAJOR = 3.14f
+private const val ORIENTATION = 1.23f
+private const val TIME = 12345699L
+private const val GESTURE_START = 12345600L
+
+/* Template [NormalizedTouchData]. */
+private val TOUCH_DATA =
+ NormalizedTouchData(
+ POINTER_ID,
+ x = 0f,
+ y = 0f,
+ NATIVE_MINOR,
+ NATIVE_MAJOR,
+ ORIENTATION,
+ TIME,
+ GESTURE_START
+ )
+
+private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */)
+
+private fun genTestCases(
+ xs: List<Int>,
+ ys: List<Int>,
+ expected: Boolean
+): List<NormalizedTouchDataTest.TestCase> {
+ return xs.flatMap { x -> ys.map { y -> NormalizedTouchDataTest.TestCase(x, y, expected) } }
+}
+
+private fun genPositiveTestCases(
+ validXs: List<Int>,
+ validYs: List<Int>,
+) = genTestCases(validXs, validYs, expected = true)
+
+private fun genNegativeTestCases(
+ invalidXs: List<Int>,
+ invalidYs: List<Int>,
+ validXs: List<Int>,
+ validYs: List<Int>,
+): List<NormalizedTouchDataTest.TestCase> {
+ return genTestCases(invalidXs, validYs, expected = false) +
+ genTestCases(validXs, invalidYs, expected = false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
new file mode 100644
index 0000000..95c53b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.graphics.Rect
+import android.view.MotionEvent
+import android.view.MotionEvent.INVALID_POINTER_ID
+import android.view.MotionEvent.PointerProperties
+import android.view.Surface
+import android.view.Surface.Rotation
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.UdfpsOverlayParams
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@SmallTest
+@RunWith(Parameterized::class)
+class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() {
+ private val overlapDetector = FakeOverlapDetector()
+ private val underTest = SinglePointerTouchProcessor(overlapDetector)
+
+ @Test
+ fun processTouch() {
+ overlapDetector.shouldReturn = testCase.isGoodOverlap
+
+ val actual =
+ underTest.processTouch(
+ testCase.event,
+ testCase.previousPointerOnSensorId,
+ testCase.overlayParams,
+ )
+
+ assertThat(actual).isInstanceOf(testCase.expected.javaClass)
+ if (actual is TouchProcessorResult.ProcessedTouch) {
+ assertThat(actual).isEqualTo(testCase.expected)
+ }
+ }
+
+ data class TestCase(
+ val event: MotionEvent,
+ val isGoodOverlap: Boolean,
+ val previousPointerOnSensorId: Int,
+ val overlayParams: UdfpsOverlayParams,
+ val expected: TouchProcessorResult,
+ ) {
+ override fun toString(): String {
+ val expectedOutput =
+ if (expected is TouchProcessorResult.ProcessedTouch) {
+ expected.event.toString() +
+ ", (x: ${expected.touchData.x}, y: ${expected.touchData.y})" +
+ ", pointerOnSensorId: ${expected.pointerOnSensorId}" +
+ ", ..."
+ } else {
+ TouchProcessorResult.Failure().toString()
+ }
+ return "{" +
+ MotionEvent.actionToString(event.action) +
+ ", (x: ${event.x}, y: ${event.y})" +
+ ", scale: ${overlayParams.scaleFactor}" +
+ ", rotation: " +
+ Surface.rotationToString(overlayParams.rotation) +
+ ", previousPointerOnSensorId: $previousPointerOnSensorId" +
+ ", ...} expected: {$expectedOutput}"
+ }
+ }
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun data(): List<TestCase> =
+ listOf(
+ // MotionEvent.ACTION_DOWN
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_DOWN,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ isGoodOverlap = true,
+ expectedInteractionEvent = InteractionEvent.DOWN,
+ expectedPointerOnSensorId = POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_DOWN,
+ previousPointerOnSensorId = POINTER_ID,
+ isGoodOverlap = true,
+ expectedInteractionEvent = InteractionEvent.DOWN,
+ expectedPointerOnSensorId = POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_DOWN,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ isGoodOverlap = false,
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_DOWN,
+ previousPointerOnSensorId = POINTER_ID,
+ isGoodOverlap = false,
+ expectedInteractionEvent = InteractionEvent.UP,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ // MotionEvent.ACTION_MOVE
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_MOVE,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ isGoodOverlap = true,
+ expectedInteractionEvent = InteractionEvent.DOWN,
+ expectedPointerOnSensorId = POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_MOVE,
+ previousPointerOnSensorId = POINTER_ID,
+ isGoodOverlap = true,
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_MOVE,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ isGoodOverlap = false,
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_MOVE,
+ previousPointerOnSensorId = POINTER_ID,
+ isGoodOverlap = false,
+ expectedInteractionEvent = InteractionEvent.UP,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ // MotionEvent.ACTION_UP
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_UP,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ isGoodOverlap = true,
+ expectedInteractionEvent = InteractionEvent.UP,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_UP,
+ previousPointerOnSensorId = POINTER_ID,
+ isGoodOverlap = true,
+ expectedInteractionEvent = InteractionEvent.UP,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_UP,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ isGoodOverlap = false,
+ expectedInteractionEvent = InteractionEvent.UNCHANGED,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_UP,
+ previousPointerOnSensorId = POINTER_ID,
+ isGoodOverlap = false,
+ expectedInteractionEvent = InteractionEvent.UP,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ // MotionEvent.ACTION_CANCEL
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_CANCEL,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ isGoodOverlap = true,
+ expectedInteractionEvent = InteractionEvent.CANCEL,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_CANCEL,
+ previousPointerOnSensorId = POINTER_ID,
+ isGoodOverlap = true,
+ expectedInteractionEvent = InteractionEvent.CANCEL,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_CANCEL,
+ previousPointerOnSensorId = INVALID_POINTER_ID,
+ isGoodOverlap = false,
+ expectedInteractionEvent = InteractionEvent.CANCEL,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ genPositiveTestCases(
+ motionEventAction = MotionEvent.ACTION_CANCEL,
+ previousPointerOnSensorId = POINTER_ID,
+ isGoodOverlap = false,
+ expectedInteractionEvent = InteractionEvent.CANCEL,
+ expectedPointerOnSensorId = INVALID_POINTER_ID,
+ ),
+ )
+ .flatten() +
+ listOf(
+ // Unsupported MotionEvent actions.
+ genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_DOWN),
+ genTestCasesForUnsupportedAction(MotionEvent.ACTION_POINTER_UP),
+ genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_ENTER),
+ genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_MOVE),
+ genTestCasesForUnsupportedAction(MotionEvent.ACTION_HOVER_EXIT),
+ )
+ .flatten()
+ }
+}
+
+/* Display dimensions in native resolution and natural orientation. */
+private const val ROTATION_0_NATIVE_DISPLAY_WIDTH = 400
+private const val ROTATION_0_NATIVE_DISPLAY_HEIGHT = 600
+
+/*
+ * ROTATION_0 map:
+ * _ _ _ _
+ * _ _ O _
+ * _ _ _ _
+ * _ S _ _
+ * _ S _ _
+ * _ _ _ _
+ *
+ * (_) empty space
+ * (S) sensor
+ * (O) touch outside of the sensor
+ */
+private val ROTATION_0_NATIVE_SENSOR_BOUNDS =
+ Rect(
+ 100, /* left */
+ 300, /* top */
+ 200, /* right */
+ 500, /* bottom */
+ )
+private val ROTATION_0_INPUTS =
+ OrientationBasedInputs(
+ rotation = Surface.ROTATION_0,
+ nativeXWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterX(),
+ nativeYWithinSensor = ROTATION_0_NATIVE_SENSOR_BOUNDS.exactCenterY(),
+ nativeXOutsideSensor = 250f,
+ nativeYOutsideSensor = 150f,
+ )
+
+/*
+ * ROTATION_90 map:
+ * _ _ _ _ _ _
+ * _ O _ _ _ _
+ * _ _ _ S S _
+ * _ _ _ _ _ _
+ *
+ * (_) empty space
+ * (S) sensor
+ * (O) touch outside of the sensor
+ */
+private val ROTATION_90_NATIVE_SENSOR_BOUNDS =
+ Rect(
+ 300, /* left */
+ 200, /* top */
+ 500, /* right */
+ 300, /* bottom */
+ )
+private val ROTATION_90_INPUTS =
+ OrientationBasedInputs(
+ rotation = Surface.ROTATION_90,
+ nativeXWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterX(),
+ nativeYWithinSensor = ROTATION_90_NATIVE_SENSOR_BOUNDS.exactCenterY(),
+ nativeXOutsideSensor = 150f,
+ nativeYOutsideSensor = 150f,
+ )
+
+/* ROTATION_180 is not supported. It's treated the same as ROTATION_0. */
+private val ROTATION_180_INPUTS =
+ ROTATION_0_INPUTS.copy(
+ rotation = Surface.ROTATION_180,
+ )
+
+/*
+ * ROTATION_270 map:
+ * _ _ _ _ _ _
+ * _ S S _ _ _
+ * _ _ _ _ O _
+ * _ _ _ _ _ _
+ *
+ * (_) empty space
+ * (S) sensor
+ * (O) touch outside of the sensor
+ */
+private val ROTATION_270_NATIVE_SENSOR_BOUNDS =
+ Rect(
+ 100, /* left */
+ 100, /* top */
+ 300, /* right */
+ 200, /* bottom */
+ )
+private val ROTATION_270_INPUTS =
+ OrientationBasedInputs(
+ rotation = Surface.ROTATION_270,
+ nativeXWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterX(),
+ nativeYWithinSensor = ROTATION_270_NATIVE_SENSOR_BOUNDS.exactCenterY(),
+ nativeXOutsideSensor = 450f,
+ nativeYOutsideSensor = 250f,
+ )
+
+/* Placeholder touch parameters. */
+private const val POINTER_ID = 42
+private const val NATIVE_MINOR = 2.71828f
+private const val NATIVE_MAJOR = 3.14f
+private const val ORIENTATION = 1.23f
+private const val TIME = 12345699L
+private const val GESTURE_START = 12345600L
+
+/* Template [MotionEvent]. */
+private val MOTION_EVENT =
+ obtainMotionEvent(
+ action = 0,
+ pointerId = POINTER_ID,
+ x = 0f,
+ y = 0f,
+ minor = 0f,
+ major = 0f,
+ orientation = ORIENTATION,
+ time = TIME,
+ gestureStart = GESTURE_START,
+ )
+
+/* Template [NormalizedTouchData]. */
+private val NORMALIZED_TOUCH_DATA =
+ NormalizedTouchData(
+ POINTER_ID,
+ x = 0f,
+ y = 0f,
+ NATIVE_MINOR,
+ NATIVE_MAJOR,
+ ORIENTATION,
+ TIME,
+ GESTURE_START
+ )
+
+/*
+ * Contains test inputs that are tied to a particular device orientation.
+ *
+ * "native" means in native resolution (not scaled).
+ */
+private data class OrientationBasedInputs(
+ @Rotation val rotation: Int,
+ val nativeXWithinSensor: Float,
+ val nativeYWithinSensor: Float,
+ val nativeXOutsideSensor: Float,
+ val nativeYOutsideSensor: Float,
+) {
+
+ fun toOverlayParams(scaleFactor: Float): UdfpsOverlayParams =
+ UdfpsOverlayParams(
+ sensorBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
+ overlayBounds = ROTATION_0_NATIVE_SENSOR_BOUNDS.scaled(scaleFactor),
+ naturalDisplayHeight = (ROTATION_0_NATIVE_DISPLAY_HEIGHT * scaleFactor).toInt(),
+ naturalDisplayWidth = (ROTATION_0_NATIVE_DISPLAY_WIDTH * scaleFactor).toInt(),
+ scaleFactor = scaleFactor,
+ rotation = rotation
+ )
+
+ fun getNativeX(isWithinSensor: Boolean): Float {
+ return if (isWithinSensor) nativeXWithinSensor else nativeXOutsideSensor
+ }
+
+ fun getNativeY(isWithinSensor: Boolean): Float {
+ return if (isWithinSensor) nativeYWithinSensor else nativeYOutsideSensor
+ }
+}
+
+private fun genPositiveTestCases(
+ motionEventAction: Int,
+ previousPointerOnSensorId: Int,
+ isGoodOverlap: Boolean,
+ expectedInteractionEvent: InteractionEvent,
+ expectedPointerOnSensorId: Int
+): List<SinglePointerTouchProcessorTest.TestCase> {
+ val scaleFactors = listOf(0.75f, 1f, 1.5f)
+ val orientations =
+ listOf(
+ ROTATION_0_INPUTS,
+ ROTATION_90_INPUTS,
+ ROTATION_180_INPUTS,
+ ROTATION_270_INPUTS,
+ )
+ return scaleFactors.flatMap { scaleFactor ->
+ orientations.map { orientation ->
+ val overlayParams = orientation.toOverlayParams(scaleFactor)
+ val nativeX = orientation.getNativeX(isGoodOverlap)
+ val nativeY = orientation.getNativeY(isGoodOverlap)
+ val event =
+ MOTION_EVENT.copy(
+ action = motionEventAction,
+ x = nativeX * scaleFactor,
+ y = nativeY * scaleFactor,
+ minor = NATIVE_MINOR * scaleFactor,
+ major = NATIVE_MAJOR * scaleFactor,
+ )
+ val expectedTouchData =
+ NORMALIZED_TOUCH_DATA.copy(
+ x = ROTATION_0_INPUTS.getNativeX(isGoodOverlap),
+ y = ROTATION_0_INPUTS.getNativeY(isGoodOverlap),
+ )
+ val expected =
+ TouchProcessorResult.ProcessedTouch(
+ event = expectedInteractionEvent,
+ pointerOnSensorId = expectedPointerOnSensorId,
+ touchData = expectedTouchData,
+ )
+ SinglePointerTouchProcessorTest.TestCase(
+ event = event,
+ isGoodOverlap = isGoodOverlap,
+ previousPointerOnSensorId = previousPointerOnSensorId,
+ overlayParams = overlayParams,
+ expected = expected,
+ )
+ }
+ }
+}
+
+private fun genTestCasesForUnsupportedAction(
+ motionEventAction: Int
+): List<SinglePointerTouchProcessorTest.TestCase> {
+ val isGoodOverlap = true
+ val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID)
+ return previousPointerOnSensorIds.map { previousPointerOnSensorId ->
+ val overlayParams = ROTATION_0_INPUTS.toOverlayParams(scaleFactor = 1f)
+ val nativeX = ROTATION_0_INPUTS.getNativeX(isGoodOverlap)
+ val nativeY = ROTATION_0_INPUTS.getNativeY(isGoodOverlap)
+ val event =
+ MOTION_EVENT.copy(
+ action = motionEventAction,
+ x = nativeX,
+ y = nativeY,
+ minor = NATIVE_MINOR,
+ major = NATIVE_MAJOR,
+ )
+ SinglePointerTouchProcessorTest.TestCase(
+ event = event,
+ isGoodOverlap = isGoodOverlap,
+ previousPointerOnSensorId = previousPointerOnSensorId,
+ overlayParams = overlayParams,
+ expected = TouchProcessorResult.Failure(),
+ )
+ }
+}
+
+private fun obtainMotionEvent(
+ action: Int,
+ pointerId: Int,
+ x: Float,
+ y: Float,
+ minor: Float,
+ major: Float,
+ orientation: Float,
+ time: Long,
+ gestureStart: Long,
+): MotionEvent {
+ val pp = PointerProperties()
+ pp.id = pointerId
+ val pc = MotionEvent.PointerCoords()
+ pc.x = x
+ pc.y = y
+ pc.touchMinor = minor
+ pc.touchMajor = major
+ pc.orientation = orientation
+ return MotionEvent.obtain(
+ gestureStart /* downTime */,
+ time /* eventTime */,
+ action /* action */,
+ 1 /* pointerCount */,
+ arrayOf(pp) /* pointerProperties */,
+ arrayOf(pc) /* pointerCoords */,
+ 0 /* metaState */,
+ 0 /* buttonState */,
+ 1f /* xPrecision */,
+ 1f /* yPrecision */,
+ 0 /* deviceId */,
+ 0 /* edgeFlags */,
+ 0 /* source */,
+ 0 /* flags */
+ )
+}
+
+private fun MotionEvent.copy(
+ action: Int = this.action,
+ pointerId: Int = this.getPointerId(0),
+ x: Float = this.rawX,
+ y: Float = this.rawY,
+ minor: Float = this.touchMinor,
+ major: Float = this.touchMajor,
+ orientation: Float = this.orientation,
+ time: Long = this.eventTime,
+ gestureStart: Long = this.downTime,
+) = obtainMotionEvent(action, pointerId, x, y, minor, major, orientation, time, gestureStart)
+
+private fun Rect.scaled(scaleFactor: Float) = Rect(this).apply { scale(scaleFactor) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index 2af0557..d159714 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -24,7 +24,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.ripple.RippleView
+import com.android.systemui.surfaceeffects.ripple.RippleView
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index f8579ff..0fadc13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -120,6 +120,7 @@
mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
+ mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true);
}
@Test
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 a872e4b..d6e621f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.animation.Animator;
@@ -171,7 +172,7 @@
mCallbacks.onShareButtonTapped();
- verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED);
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
verify(mClipboardOverlayView, times(1)).getExitAnimation();
}
@@ -181,7 +182,7 @@
mCallbacks.onDismissButtonTapped();
- verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "");
verify(mClipboardOverlayView, times(1)).getExitAnimation();
}
@@ -192,7 +193,7 @@
mCallbacks.onSwipeDismissInitiated(mAnimator);
mCallbacks.onDismissButtonTapped();
- verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
+ verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null);
verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
}
@@ -224,4 +225,16 @@
verify(mTimeoutHandler).resetTimeout();
}
+
+ @Test
+ public void test_logsUseLastClipSource() {
+ mOverlayController.setClipData(mSampleClipData, "first.package");
+ mCallbacks.onDismissButtonTapped();
+ mOverlayController.setClipData(mSampleClipData, "second.package");
+ mCallbacks.onDismissButtonTapped();
+
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
+ verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
+ verifyNoMoreInteractions(mUiEventLogger);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt
new file mode 100644
index 0000000..4b88b44
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls
+
+import android.content.pm.UserInfo
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+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)
+@OptIn(ExperimentalCoroutinesApi::class)
+class ControlsSettingsRepositoryImplTest : SysuiTestCase() {
+
+ companion object {
+ private const val LOCKSCREEN_SHOW = Settings.Secure.LOCKSCREEN_SHOW_CONTROLS
+ private const val LOCKSCREEN_ACTION = Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS
+
+ private fun createUser(id: Int): UserInfo {
+ return UserInfo(id, "user_$id", 0)
+ }
+
+ private val ALL_USERS = (0..1).map { it to createUser(it) }.toMap()
+ }
+
+ private lateinit var underTest: ControlsSettingsRepository
+
+ private lateinit var testScope: TestScope
+ private lateinit var secureSettings: FakeSettings
+ private lateinit var userRepository: FakeUserRepository
+
+ @Before
+ fun setUp() {
+ secureSettings = FakeSettings()
+ userRepository = FakeUserRepository()
+ userRepository.setUserInfos(ALL_USERS.values.toList())
+
+ val coroutineDispatcher = UnconfinedTestDispatcher()
+ testScope = TestScope(coroutineDispatcher)
+
+ underTest =
+ ControlsSettingsRepositoryImpl(
+ scope = testScope.backgroundScope,
+ backgroundDispatcher = coroutineDispatcher,
+ userRepository = userRepository,
+ secureSettings = secureSettings,
+ )
+ }
+
+ @Test
+ fun showInLockScreen() =
+ testScope.runTest {
+ setUser(0)
+ val values = mutableListOf<Boolean>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.canShowControlsInLockscreen.toList(values)
+ }
+ assertThat(values.last()).isFalse()
+
+ secureSettings.putBool(LOCKSCREEN_SHOW, true)
+ assertThat(values.last()).isTrue()
+
+ secureSettings.putBool(LOCKSCREEN_SHOW, false)
+ assertThat(values.last()).isFalse()
+
+ secureSettings.putBoolForUser(LOCKSCREEN_SHOW, true, 1)
+ assertThat(values.last()).isFalse()
+
+ setUser(1)
+ assertThat(values.last()).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun showInLockScreen_changesInOtherUsersAreNotQueued() =
+ testScope.runTest {
+ setUser(0)
+
+ val values = mutableListOf<Boolean>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.canShowControlsInLockscreen.toList(values)
+ }
+
+ secureSettings.putBoolForUser(LOCKSCREEN_SHOW, true, 1)
+ secureSettings.putBoolForUser(LOCKSCREEN_SHOW, false, 1)
+
+ setUser(1)
+ assertThat(values.last()).isFalse()
+ assertThat(values).containsNoneIn(listOf(true))
+
+ job.cancel()
+ }
+
+ @Test
+ fun actionInLockScreen() =
+ testScope.runTest {
+ setUser(0)
+ val values = mutableListOf<Boolean>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.allowActionOnTrivialControlsInLockscreen.toList(values)
+ }
+ assertThat(values.last()).isFalse()
+
+ secureSettings.putBool(LOCKSCREEN_ACTION, true)
+ assertThat(values.last()).isTrue()
+
+ secureSettings.putBool(LOCKSCREEN_ACTION, false)
+ assertThat(values.last()).isFalse()
+
+ secureSettings.putBoolForUser(LOCKSCREEN_ACTION, true, 1)
+ assertThat(values.last()).isFalse()
+
+ setUser(1)
+ assertThat(values.last()).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun actionInLockScreen_changesInOtherUsersAreNotQueued() =
+ testScope.runTest {
+ setUser(0)
+
+ val values = mutableListOf<Boolean>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.allowActionOnTrivialControlsInLockscreen.toList(values)
+ }
+
+ secureSettings.putBoolForUser(LOCKSCREEN_ACTION, true, 1)
+ secureSettings.putBoolForUser(LOCKSCREEN_ACTION, false, 1)
+
+ setUser(1)
+ assertThat(values.last()).isFalse()
+ assertThat(values).containsNoneIn(listOf(true))
+
+ job.cancel()
+ }
+
+ @Test
+ fun valueIsUpdatedWhenNotSubscribed() =
+ testScope.runTest {
+ setUser(0)
+ assertThat(underTest.canShowControlsInLockscreen.value).isFalse()
+
+ secureSettings.putBool(LOCKSCREEN_SHOW, true)
+
+ assertThat(underTest.canShowControlsInLockscreen.value).isTrue()
+ }
+
+ private suspend fun setUser(id: Int) {
+ secureSettings.userId = id
+ userRepository.setSelectedUserInfo(ALL_USERS[id]!!)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt
new file mode 100644
index 0000000..8a1bed2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeControlsSettingsRepository : ControlsSettingsRepository {
+ private val _canShowControlsInLockscreen = MutableStateFlow(false)
+ override val canShowControlsInLockscreen = _canShowControlsInLockscreen.asStateFlow()
+ private val _allowActionOnTrivialControlsInLockscreen = MutableStateFlow(false)
+ override val allowActionOnTrivialControlsInLockscreen =
+ _allowActionOnTrivialControlsInLockscreen.asStateFlow()
+
+ fun setCanShowControlsInLockscreen(value: Boolean) {
+ _canShowControlsInLockscreen.value = value
+ }
+
+ fun setAllowActionOnTrivialControlsInLockscreen(value: Boolean) {
+ _allowActionOnTrivialControlsInLockscreen.value = value
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 4ed5649c..1d00d6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -18,30 +18,24 @@
import android.content.Context
import android.content.SharedPreferences
-import android.database.ContentObserver
-import android.net.Uri
-import android.os.Handler
-import android.os.UserHandle
-import android.provider.Settings
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.FakeControlsSettingsRepository
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.TaskViewFactory
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
@@ -79,8 +73,6 @@
@Mock
private lateinit var secureSettings: SecureSettings
@Mock
- private lateinit var mainHandler: Handler
- @Mock
private lateinit var userContextProvider: UserContextProvider
companion object {
@@ -91,17 +83,15 @@
private lateinit var coordinator: ControlActionCoordinatorImpl
private lateinit var action: ControlActionCoordinatorImpl.Action
+ private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(secureSettings.getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS))
- .thenReturn(Settings.Secure
- .getUriFor(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS))
- `when`(secureSettings.getIntForUser(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
- 0, UserHandle.USER_CURRENT))
- .thenReturn(1)
+ controlsSettingsRepository = FakeControlsSettingsRepository()
+ controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+ controlsSettingsRepository.setCanShowControlsInLockscreen(true)
coordinator = spy(ControlActionCoordinatorImpl(
mContext,
@@ -115,7 +105,7 @@
vibratorHelper,
secureSettings,
userContextProvider,
- mainHandler
+ controlsSettingsRepository
))
val userContext = mock(Context::class.java)
@@ -128,9 +118,6 @@
`when`(pref.getInt(DeviceControlsControllerImpl.PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
.thenReturn(2)
- verify(secureSettings).registerContentObserverForUser(any(Uri::class.java),
- anyBoolean(), any(ContentObserver::class.java), anyInt())
-
`when`(cvh.cws.ci.controlId).thenReturn(ID)
`when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
action = spy(coordinator.Action(ID, {}, false, true))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index c31fd82..1b34706 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.controls.controller
import android.app.PendingIntent
-import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
@@ -31,7 +30,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.backup.BackupHelper
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
@@ -85,10 +83,8 @@
@Mock
private lateinit var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper
@Mock
- private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock
private lateinit var listingController: ControlsListingController
- @Mock(stubOnly = true)
+ @Mock
private lateinit var userTracker: UserTracker
@Mock
private lateinit var userFileManager: UserFileManager
@@ -104,7 +100,7 @@
ArgumentCaptor<ControlsBindingController.LoadCallback>
@Captor
- private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
@Captor
private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -170,16 +166,15 @@
uiController,
bindingController,
listingController,
- broadcastDispatcher,
userFileManager,
+ userTracker,
Optional.of(persistenceWrapper),
- mock(DumpManager::class.java),
- userTracker
+ mock(DumpManager::class.java)
)
controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
- verify(broadcastDispatcher).registerReceiver(
- capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL), anyInt(), any()
+ verify(userTracker).addCallback(
+ capture(userTrackerCallbackCaptor), any()
)
verify(listingController).addCallback(capture(listingCallbackCaptor))
@@ -227,11 +222,10 @@
uiController,
bindingController,
listingController,
- broadcastDispatcher,
userFileManager,
+ userTracker,
Optional.of(persistenceWrapper),
- mock(DumpManager::class.java),
- userTracker
+ mock(DumpManager::class.java)
)
assertEquals(listOf(TEST_STRUCTURE_INFO), controller_other.getFavorites())
}
@@ -518,14 +512,8 @@
delayableExecutor.runAllReady()
reset(persistenceWrapper)
- val intent = Intent(Intent.ACTION_USER_SWITCHED).apply {
- putExtra(Intent.EXTRA_USER_HANDLE, otherUser)
- }
- val pendingResult = mock(BroadcastReceiver.PendingResult::class.java)
- `when`(pendingResult.sendingUserId).thenReturn(otherUser)
- broadcastReceiverCaptor.value.pendingResult = pendingResult
- broadcastReceiverCaptor.value.onReceive(mContext, intent)
+ userTrackerCallbackCaptor.value.onUserChanged(otherUser, mContext)
verify(persistenceWrapper).changeFileAndBackupManager(any(), any())
verify(persistenceWrapper).readFavorites()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 77f451f..48fc46b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -17,19 +17,18 @@
package com.android.systemui.controls.dagger
import android.testing.AndroidTestingRunner
-import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.FakeControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.settings.SecureSettings
import dagger.Lazy
import java.util.Optional
import org.junit.Assert.assertEquals
@@ -63,13 +62,13 @@
@Mock
private lateinit var lockPatternUtils: LockPatternUtils
@Mock
- private lateinit var secureSettings: SecureSettings
- @Mock
private lateinit var optionalControlsTileResourceConfiguration:
Optional<ControlsTileResourceConfiguration>
@Mock
private lateinit var controlsTileResourceConfiguration: ControlsTileResourceConfiguration
+ private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
+
companion object {
fun <T> eq(value: T): T = Mockito.eq(value) ?: value
}
@@ -78,6 +77,8 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ controlsSettingsRepository = FakeControlsSettingsRepository()
+
`when`(userTracker.userHandle.identifier).thenReturn(0)
`when`(optionalControlsTileResourceConfiguration.orElse(any()))
.thenReturn(controlsTileResourceConfiguration)
@@ -125,8 +126,7 @@
`when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(STRONG_AUTH_NOT_REQUIRED)
`when`(keyguardStateController.isUnlocked()).thenReturn(false)
- `when`(secureSettings.getInt(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), anyInt()))
- .thenReturn(0)
+ controlsSettingsRepository.setCanShowControlsInLockscreen(false)
val component = setupComponent(true)
assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility())
@@ -137,9 +137,7 @@
`when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(STRONG_AUTH_NOT_REQUIRED)
`when`(keyguardStateController.isUnlocked()).thenReturn(false)
- `when`(secureSettings.getIntForUser(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS),
- anyInt(), anyInt()))
- .thenReturn(1)
+ controlsSettingsRepository.setCanShowControlsInLockscreen(true)
val component = setupComponent(true)
assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility())
@@ -147,8 +145,7 @@
@Test
fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() {
- `when`(secureSettings.getInt(eq(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS), anyInt()))
- .thenReturn(0)
+ controlsSettingsRepository.setCanShowControlsInLockscreen(false)
`when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(STRONG_AUTH_NOT_REQUIRED)
`when`(keyguardStateController.isUnlocked()).thenReturn(true)
@@ -187,7 +184,7 @@
lockPatternUtils,
keyguardStateController,
userTracker,
- secureSettings,
+ controlsSettingsRepository,
optionalControlsTileResourceConfiguration
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
index 1e4a9e4..765c4c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -34,9 +35,8 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@@ -70,7 +70,7 @@
fun testOnServicesUpdated_nullLoadLabel() {
val captor = ArgumentCaptor
.forClass(ControlsListingController.ControlsListingCallback::class.java)
- val controlsServiceInfo = mock(ControlsServiceInfo::class.java)
+ val controlsServiceInfo = mock<ControlsServiceInfo>()
val serviceInfo = listOf(controlsServiceInfo)
`when`(controlsServiceInfo.loadLabel()).thenReturn(null)
verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
@@ -81,4 +81,32 @@
assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
}
+
+ @Test
+ fun testOnServicesUpdatedDoesntHavePanels() {
+ val captor = ArgumentCaptor
+ .forClass(ControlsListingController.ControlsListingCallback::class.java)
+ val serviceInfo = listOf(
+ ControlsServiceInfo("no panel", null),
+ ControlsServiceInfo("panel", mock())
+ )
+ verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
+
+ captor.value.onServicesUpdated(serviceInfo)
+ backgroundExecutor.runAllReady()
+ uiExecutor.runAllReady()
+
+ assertThat(adapter.itemCount).isEqualTo(1)
+ }
+
+ fun ControlsServiceInfo(
+ label: CharSequence,
+ panelComponentName: ComponentName? = null
+ ): ControlsServiceInfo {
+ return mock {
+ `when`(this.loadLabel()).thenReturn(label)
+ `when`(this.panelActivity).thenReturn(panelComponentName)
+ `when`(this.loadIcon()).thenReturn(mock())
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 0b72a68..3b6f7d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -10,10 +10,12 @@
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
import java.util.concurrent.CountDownLatch
import org.junit.Before
import org.junit.Rule
@@ -30,9 +32,11 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class ControlsEditingActivityTest : SysuiTestCase() {
+ private val uiExecutor = FakeExecutor(FakeSystemClock())
+
@Mock lateinit var controller: ControlsControllerImpl
- @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock lateinit var userTracker: UserTracker
@Mock lateinit var customIconCache: CustomIconCache
@@ -54,8 +58,9 @@
) {
override fun create(intent: Intent?): TestableControlsEditingActivity {
return TestableControlsEditingActivity(
+ uiExecutor,
controller,
- broadcastDispatcher,
+ userTracker,
customIconCache,
uiController,
mockDispatcher,
@@ -92,13 +97,14 @@
}
public class TestableControlsEditingActivity(
+ private val executor: FakeExecutor,
private val controller: ControlsControllerImpl,
- private val broadcastDispatcher: BroadcastDispatcher,
+ private val userTracker: UserTracker,
private val customIconCache: CustomIconCache,
private val uiController: ControlsUiController,
private val mockDispatcher: OnBackInvokedDispatcher,
private val latch: CountDownLatch
- ) : ControlsEditingActivity(controller, broadcastDispatcher, customIconCache, uiController) {
+ ) : ControlsEditingActivity(executor, controller, userTracker, customIconCache, uiController) {
override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
return mockDispatcher
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index 4b0f7e6..3655232 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -5,14 +5,15 @@
import android.testing.TestableLooper
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
+import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
@@ -37,7 +38,7 @@
@Mock lateinit var listingController: ControlsListingController
- @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock lateinit var userTracker: UserTracker
@Mock lateinit var uiController: ControlsUiController
@@ -60,7 +61,7 @@
executor,
controller,
listingController,
- broadcastDispatcher,
+ userTracker,
uiController,
mockDispatcher,
latch
@@ -79,6 +80,8 @@
activityRule.launchActivity(intent)
}
+ // b/259549854 to root-cause and fix
+ @FlakyTest
@Test
fun testBackCallbackRegistrationAndUnregistration() {
// 1. ensure that launching the activity results in it registering a callback
@@ -97,7 +100,7 @@
executor: Executor,
controller: ControlsControllerImpl,
listingController: ControlsListingController,
- broadcastDispatcher: BroadcastDispatcher,
+ userTracker: UserTracker,
uiController: ControlsUiController,
private val mockDispatcher: OnBackInvokedDispatcher,
private val latch: CountDownLatch
@@ -106,7 +109,7 @@
executor,
controller,
listingController,
- broadcastDispatcher,
+ userTracker,
uiController
) {
override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index dedc723..c677f19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -31,6 +31,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.settingslib.applications.ServiceListing
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dump.DumpManager
@@ -46,6 +47,7 @@
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -109,6 +111,12 @@
.thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
mContext.setMockPackageManager(packageManager)
+ mContext.orCreateTestableResources
+ .addOverride(
+ R.array.config_controlsPreferredPackages,
+ arrayOf(componentName.packageName)
+ )
+
// Return true by default, we'll test the false path
`when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true)
@@ -480,6 +488,48 @@
assertNull(controller.getCurrentServices()[0].panelActivity)
}
+ @Test
+ fun testPackageNotPreferred_nullPanel() {
+ mContext.orCreateTestableResources
+ .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
+
+ val serviceInfo = ServiceInfo(
+ componentName,
+ activityName
+ )
+
+ `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+ .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+
+ setUpQueryResult(listOf(
+ ActivityInfo(
+ activityName,
+ exported = true,
+ permission = Manifest.permission.BIND_CONTROLS
+ )
+ ))
+
+ val list = listOf(serviceInfo)
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+
+ assertNull(controller.getCurrentServices()[0].panelActivity)
+ }
+
+ @Test
+ fun testListingsNotModifiedByCallback() {
+ // This test checks that if the list passed to the callback is modified, it has no effect
+ // in the resulting services
+ val list = mutableListOf<ServiceInfo>()
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ list.add(ServiceInfo(ComponentName("a", "b")))
+ executor.runAllReady()
+
+ assertTrue(controller.getCurrentServices().isEmpty())
+ }
+
private fun ServiceInfo(
componentName: ComponentName,
panelActivityComponentName: ComponentName? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
index acc6222..56c3efe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -25,11 +25,11 @@
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executor
@@ -56,7 +56,7 @@
@Mock lateinit var controlsController: ControlsController
- @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock lateinit var userTracker: UserTracker
@Mock lateinit var uiController: ControlsUiController
@@ -80,7 +80,7 @@
backExecutor,
listingController,
controlsController,
- broadcastDispatcher,
+ userTracker,
uiController,
mockDispatcher,
latch
@@ -118,7 +118,7 @@
backExecutor: Executor,
listingController: ControlsListingController,
controlsController: ControlsController,
- broadcastDispatcher: BroadcastDispatcher,
+ userTracker: UserTracker,
uiController: ControlsUiController,
private val mockDispatcher: OnBackInvokedDispatcher,
private val latch: CountDownLatch
@@ -128,7 +128,7 @@
backExecutor,
listingController,
controlsController,
- broadcastDispatcher,
+ userTracker,
uiController
) {
override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
index efb3db7..314b176 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import org.junit.After
@@ -46,9 +47,10 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
@MediumTest
@RunWith(AndroidTestingRunner::class)
@@ -67,6 +69,10 @@
private lateinit var controller: ControlsController
@Mock
+ private lateinit var mainExecutor: Executor
+ @Mock
+ private lateinit var userTracker: UserTracker
+ @Mock
private lateinit var listingController: ControlsListingController
@Mock
private lateinit var iIntentSender: IIntentSender
@@ -81,8 +87,9 @@
) {
override fun create(intent: Intent?): TestControlsRequestDialog {
return TestControlsRequestDialog(
+ mainExecutor,
controller,
- fakeBroadcastDispatcher,
+ userTracker,
listingController
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
index 3f6308b..ec239f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/TestControlsRequestDialog.kt
@@ -16,11 +16,13 @@
package com.android.systemui.controls.management
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
class TestControlsRequestDialog(
+ mainExecutor: Executor,
controller: ControlsController,
- dispatcher: BroadcastDispatcher,
+ userTracker: UserTracker,
listingController: ControlsListingController
-) : ControlsRequestDialog(controller, dispatcher, listingController)
\ No newline at end of file
+) : ControlsRequestDialog(mainExecutor, controller, userTracker, listingController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 49c7442..e679b13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -17,8 +17,10 @@
package com.android.systemui.controls.ui
import android.content.ComponentName
+import android.content.Context
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsMetricsLogger
@@ -26,6 +28,7 @@
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
@@ -34,9 +37,12 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.TaskViewFactory
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
+import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,7 +50,7 @@
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
import org.mockito.Mockito.mock
-import org.mockito.Mockito.times
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -63,16 +69,22 @@
@Mock lateinit var keyguardStateController: KeyguardStateController
@Mock lateinit var userFileManager: UserFileManager
@Mock lateinit var userTracker: UserTracker
+ @Mock lateinit var taskViewFactory: TaskViewFactory
+ @Mock lateinit var activityContext: Context
+ @Mock lateinit var dumpManager: DumpManager
val sharedPreferences = FakeSharedPreferences()
var uiExecutor = FakeExecutor(FakeSystemClock())
var bgExecutor = FakeExecutor(FakeSystemClock())
lateinit var underTest: ControlsUiControllerImpl
+ lateinit var parent: FrameLayout
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ parent = FrameLayout(mContext)
+
underTest =
ControlsUiControllerImpl(
Lazy { controlsController },
@@ -82,12 +94,13 @@
Lazy { controlsListingController },
controlActionCoordinator,
activityStarter,
- shadeController,
iconCache,
controlsMetricsLogger,
keyguardStateController,
userFileManager,
- userTracker
+ userTracker,
+ Optional.of(taskViewFactory),
+ dumpManager
)
`when`(
userFileManager.getSharedPreferences(
@@ -105,8 +118,8 @@
@Test
fun testGetPreferredStructure() {
val structureInfo = mock(StructureInfo::class.java)
- underTest.getPreferredStructure(listOf(structureInfo))
- verify(userFileManager, times(2))
+ underTest.getPreferredSelectedItem(listOf(structureInfo))
+ verify(userFileManager)
.getSharedPreferences(
fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
mode = 0,
@@ -116,25 +129,30 @@
@Test
fun testGetPreferredStructure_differentUserId() {
- val structureInfo =
+ val selectedItems =
listOf(
- StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList()),
- StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList()),
+ SelectedItem.StructureItem(
+ StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
+ ),
+ SelectedItem.StructureItem(
+ StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList())
+ ),
)
+ val structures = selectedItems.map { it.structure }
sharedPreferences
.edit()
- .putString("controls_component", structureInfo[0].componentName.flattenToString())
- .putString("controls_structure", structureInfo[0].structure.toString())
+ .putString("controls_component", selectedItems[0].componentName.flattenToString())
+ .putString("controls_structure", selectedItems[0].name.toString())
.commit()
val differentSharedPreferences = FakeSharedPreferences()
differentSharedPreferences
.edit()
- .putString("controls_component", structureInfo[1].componentName.flattenToString())
- .putString("controls_structure", structureInfo[1].structure.toString())
+ .putString("controls_component", selectedItems[1].componentName.flattenToString())
+ .putString("controls_structure", selectedItems[1].name.toString())
.commit()
- val previousPreferredStructure = underTest.getPreferredStructure(structureInfo)
+ val previousPreferredStructure = underTest.getPreferredSelectedItem(structures)
`when`(
userFileManager.getSharedPreferences(
@@ -146,10 +164,39 @@
.thenReturn(differentSharedPreferences)
`when`(userTracker.userId).thenReturn(1)
- val currentPreferredStructure = underTest.getPreferredStructure(structureInfo)
+ val currentPreferredStructure = underTest.getPreferredSelectedItem(structures)
- assertThat(previousPreferredStructure).isEqualTo(structureInfo[0])
- assertThat(currentPreferredStructure).isEqualTo(structureInfo[1])
+ assertThat(previousPreferredStructure).isEqualTo(selectedItems[0])
+ assertThat(currentPreferredStructure).isEqualTo(selectedItems[1])
assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
}
+
+ @Test
+ fun testGetPreferredPanel() {
+ val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+ sharedPreferences
+ .edit()
+ .putString("controls_component", panel.componentName.flattenToString())
+ .putString("controls_structure", panel.appName.toString())
+ .putBoolean("controls_is_panel", true)
+ .commit()
+
+ val selected = underTest.getPreferredSelectedItem(emptyList())
+
+ assertThat(selected).isEqualTo(panel)
+ }
+
+ @Test
+ fun testPanelDoesNotRefreshControls() {
+ val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+ sharedPreferences
+ .edit()
+ .putString("controls_component", panel.componentName.flattenToString())
+ .putString("controls_structure", panel.appName.toString())
+ .putBoolean("controls_is_panel", true)
+ .commit()
+
+ underTest.show(parent, {}, activityContext)
+ verify(controlsController, never()).refreshStatus(any(), any())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
new file mode 100644
index 0000000..5cd2ace
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/PanelTaskViewControllerTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls.ui
+
+import android.app.ActivityOptions
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.boundsOnScreen
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.TaskView
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class PanelTaskViewControllerTest : SysuiTestCase() {
+
+ companion object {
+ val FAKE_BOUNDS = Rect(10, 20, 30, 40)
+ }
+
+ @Mock private lateinit var activityContext: Context
+ @Mock private lateinit var taskView: TaskView
+ @Mock private lateinit var pendingIntent: PendingIntent
+ @Mock private lateinit var hideRunnable: () -> Unit
+
+ @Captor private lateinit var listenerCaptor: ArgumentCaptor<TaskView.Listener>
+
+ private lateinit var uiExecutor: FakeExecutor
+ private lateinit var underTest: PanelTaskViewController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(taskView.boundsOnScreen).thenAnswer { (it.arguments[0] as Rect).set(FAKE_BOUNDS) }
+ whenever(taskView.post(any())).thenAnswer {
+ uiExecutor.execute(it.arguments[0] as Runnable)
+ true
+ }
+
+ uiExecutor = FakeExecutor(FakeSystemClock())
+
+ underTest =
+ PanelTaskViewController(
+ activityContext,
+ uiExecutor,
+ pendingIntent,
+ taskView,
+ hideRunnable
+ )
+ }
+
+ @Test
+ fun testLaunchTaskViewAttachedListener() {
+ underTest.launchTaskView()
+ verify(taskView).setListener(eq(uiExecutor), any())
+ }
+
+ @Test
+ fun testTaskViewOnInitializeStartsActivity() {
+ underTest.launchTaskView()
+ verify(taskView).setListener(any(), capture(listenerCaptor))
+
+ listenerCaptor.value.onInitialized()
+ uiExecutor.runAllReady()
+
+ val intentCaptor = argumentCaptor<Intent>()
+ val optionsCaptor = argumentCaptor<ActivityOptions>()
+
+ verify(taskView)
+ .startActivity(
+ eq(pendingIntent),
+ /* fillInIntent */ capture(intentCaptor),
+ capture(optionsCaptor),
+ eq(FAKE_BOUNDS)
+ )
+
+ assertThat(intentCaptor.value.flags)
+ .isEqualTo(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_MULTIPLE_TASK)
+ assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue()
+ }
+
+ @Test
+ fun testHideRunnableCalledWhenBackOnRoot() {
+ underTest.launchTaskView()
+ verify(taskView).setListener(any(), capture(listenerCaptor))
+
+ listenerCaptor.value.onBackPressedOnTaskRoot(0)
+
+ verify(hideRunnable).invoke()
+ }
+
+ @Test
+ fun testTaskViewReleasedOnDismiss() {
+ underTest.dismiss()
+ verify(taskView).release()
+ }
+
+ @Test
+ fun testTaskViewReleasedOnBackOnRoot() {
+ underTest.launchTaskView()
+ verify(taskView).setListener(any(), capture(listenerCaptor))
+
+ listenerCaptor.value.onBackPressedOnTaskRoot(0)
+ verify(taskView).release()
+ }
+
+ @Test
+ fun testOnTaskRemovalStarted() {
+ underTest.launchTaskView()
+ verify(taskView).setListener(any(), capture(listenerCaptor))
+
+ listenerCaptor.value.onTaskRemovalStarted(0)
+ verify(taskView).release()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/SelectionItemTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/SelectionItemTest.kt
new file mode 100644
index 0000000..57176f0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/SelectionItemTest.kt
@@ -0,0 +1,112 @@
+package com.android.systemui.controls.ui
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SelectionItemTest : SysuiTestCase() {
+
+ @Test
+ fun testMatchBadComponentName_false() {
+ val selectionItem =
+ SelectionItem(
+ appName = "app",
+ structure = "structure",
+ icon = mock(),
+ componentName = ComponentName("pkg", "cls"),
+ uid = 0,
+ panelComponentName = null
+ )
+
+ assertThat(
+ selectionItem.matches(
+ SelectedItem.StructureItem(
+ StructureInfo(ComponentName("", ""), "s", emptyList())
+ )
+ )
+ )
+ .isFalse()
+ assertThat(selectionItem.matches(SelectedItem.PanelItem("name", ComponentName("", ""))))
+ .isFalse()
+ }
+
+ @Test
+ fun testMatchSameComponentName_panelSelected_true() {
+ val componentName = ComponentName("pkg", "cls")
+
+ val selectionItem =
+ SelectionItem(
+ appName = "app",
+ structure = "structure",
+ icon = mock(),
+ componentName = componentName,
+ uid = 0,
+ panelComponentName = null
+ )
+ assertThat(selectionItem.matches(SelectedItem.PanelItem("name", componentName))).isTrue()
+ }
+
+ @Test
+ fun testMatchSameComponentName_panelSelection_true() {
+ val componentName = ComponentName("pkg", "cls")
+
+ val selectionItem =
+ SelectionItem(
+ appName = "app",
+ structure = "structure",
+ icon = mock(),
+ componentName = componentName,
+ uid = 0,
+ panelComponentName = ComponentName("pkg", "panel")
+ )
+ assertThat(selectionItem.matches(SelectedItem.PanelItem("name", componentName))).isTrue()
+ }
+
+ @Test
+ fun testMatchSameComponentSameStructure_true() {
+ val componentName = ComponentName("pkg", "cls")
+ val structureName = "structure"
+
+ val structureItem =
+ SelectedItem.StructureItem(StructureInfo(componentName, structureName, emptyList()))
+
+ val selectionItem =
+ SelectionItem(
+ appName = "app",
+ structure = structureName,
+ icon = mock(),
+ componentName = componentName,
+ uid = 0,
+ panelComponentName = null
+ )
+ assertThat(selectionItem.matches(structureItem)).isTrue()
+ }
+
+ @Test
+ fun testMatchSameComponentDifferentStructure_false() {
+ val componentName = ComponentName("pkg", "cls")
+ val structureName = "structure"
+
+ val structureItem =
+ SelectedItem.StructureItem(StructureInfo(componentName, structureName, emptyList()))
+
+ val selectionItem =
+ SelectionItem(
+ appName = "app",
+ structure = "other",
+ icon = mock(),
+ componentName = componentName,
+ uid = 0,
+ panelComponentName = null
+ )
+ assertThat(selectionItem.matches(structureItem)).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index c40c187..20d3cd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeSensors.TriggerSensor;
import com.android.systemui.plugins.SensorManagerPlugin;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -97,6 +98,8 @@
@Mock
private DevicePostureController mDevicePostureController;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private ProximitySensor mProximitySensor;
// Capture listeners so that they can be used to send events
@@ -426,7 +429,7 @@
DozeSensors dozeSensors = new DozeSensors(mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
- mDevicePostureController);
+ mDevicePostureController, mUserTracker);
for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
assertFalse(sensor.mIgnoresSetting);
@@ -438,7 +441,7 @@
super(mSensorManager, mDozeParameters,
mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
mProximitySensor, mFakeSettings, mAuthController,
- mDevicePostureController);
+ mDevicePostureController, mUserTracker);
for (TriggerSensor sensor : mTriggerSensors) {
if (sensor instanceof PluginSensor
&& ((PluginSensor) sensor).mPluginSensor.getType()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 6091d3a..82432ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
import com.android.systemui.log.SessionTracker;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -98,6 +99,8 @@
@Mock
private DevicePostureController mDevicePostureController;
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private SessionTracker mSessionTracker;
private DozeTriggers mTriggers;
@@ -131,7 +134,7 @@
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
mProximityCheck, mDozeLog, mBroadcastDispatcher, new FakeSettings(),
mAuthController, mUiEventLogger, mSessionTracker, mKeyguardStateController,
- mDevicePostureController);
+ mDevicePostureController, mUserTracker);
mTriggers.setDozeMachine(mMachine);
waitForSensorManager();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
new file mode 100644
index 0000000..99406ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -0,0 +1,125 @@
+package com.android.systemui.dreams
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.statusbar.BlurUtils
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
+
+ companion object {
+ private const val DREAM_IN_BLUR_ANIMATION_DURATION = 1L
+ private const val DREAM_IN_BLUR_ANIMATION_DELAY = 2L
+ private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L
+ private const val DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY = 4L
+ private const val DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY = 5L
+ private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6
+ private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L
+ private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L
+ private const val DREAM_OUT_TRANSLATION_Y_DELAY_TOP = 9L
+ private const val DREAM_OUT_ALPHA_DURATION = 10L
+ private const val DREAM_OUT_ALPHA_DELAY_BOTTOM = 11L
+ private const val DREAM_OUT_ALPHA_DELAY_TOP = 12L
+ private const val DREAM_OUT_BLUR_DURATION = 13L
+ }
+
+ @Mock private lateinit var mockAnimator: AnimatorSet
+ @Mock private lateinit var blurUtils: BlurUtils
+ @Mock private lateinit var hostViewController: ComplicationHostViewController
+ @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
+ @Mock private lateinit var stateController: DreamOverlayStateController
+ private lateinit var controller: DreamOverlayAnimationsController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ controller =
+ DreamOverlayAnimationsController(
+ blurUtils,
+ hostViewController,
+ statusBarViewController,
+ stateController,
+ DREAM_IN_BLUR_ANIMATION_DURATION,
+ DREAM_IN_BLUR_ANIMATION_DELAY,
+ DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
+ DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY,
+ DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY,
+ DREAM_OUT_TRANSLATION_Y_DISTANCE,
+ DREAM_OUT_TRANSLATION_Y_DURATION,
+ DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM,
+ DREAM_OUT_TRANSLATION_Y_DELAY_TOP,
+ DREAM_OUT_ALPHA_DURATION,
+ DREAM_OUT_ALPHA_DELAY_BOTTOM,
+ DREAM_OUT_ALPHA_DELAY_TOP,
+ DREAM_OUT_BLUR_DURATION
+ )
+ }
+
+ @Test
+ fun testExitAnimationOnEnd() {
+ val mockCallback: () -> Unit = mock()
+
+ controller.startExitAnimations(
+ view = mock(),
+ doneCallback = mockCallback,
+ animatorBuilder = { mockAnimator }
+ )
+
+ val captor = argumentCaptor<Animator.AnimatorListener>()
+ verify(mockAnimator).addListener(captor.capture())
+ val listener = captor.value
+
+ verify(mockCallback, never()).invoke()
+ listener.onAnimationEnd(mockAnimator)
+ verify(mockCallback, times(1)).invoke()
+ }
+
+ @Test
+ fun testCancellation() {
+ controller.startExitAnimations(
+ view = mock(),
+ doneCallback = mock(),
+ animatorBuilder = { mockAnimator }
+ )
+
+ verify(mockAnimator, never()).cancel()
+ controller.cancelAnimations()
+ verify(mockAnimator, times(1)).cancel()
+ }
+
+ @Test
+ fun testExitAfterStartWillCancel() {
+ val mockStartAnimator: AnimatorSet = mock()
+ val mockExitAnimator: AnimatorSet = mock()
+
+ controller.startEntryAnimations(view = mock(), animatorBuilder = { mockStartAnimator })
+
+ verify(mockStartAnimator, never()).cancel()
+
+ controller.startExitAnimations(
+ view = mock(),
+ doneCallback = mock(),
+ animatorBuilder = { mockExitAnimator }
+ )
+
+ // Verify that we cancelled the start animator in favor of the exit
+ // animator.
+ verify(mockStartAnimator, times(1)).cancel()
+ verify(mockExitAnimator, never()).cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 517804d..73c226d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -204,7 +204,7 @@
mController.onViewAttached();
verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
- verify(mAnimationsController, never()).cancelRunningEntryAnimations();
+ verify(mAnimationsController, never()).cancelAnimations();
}
@Test
@@ -221,6 +221,6 @@
mController.onViewAttached();
mController.onViewDetached();
- verify(mAnimationsController).cancelRunningEntryAnimations();
+ verify(mAnimationsController).cancelAnimations();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index f04a37f..ffb8342 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -20,6 +20,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -337,4 +338,28 @@
verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
}
+
+ @Test
+ public void testWakeUp() throws RemoteException {
+ final IBinder proxy = mService.onBind(new Intent());
+ final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+ // Inform the overlay service of dream starting.
+ overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ true /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ final Runnable callback = mock(Runnable.class);
+ mService.onWakeUp(callback);
+ mMainExecutor.runAllReady();
+ verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
+ }
+
+ @Test
+ public void testWakeUpBeforeStartDoesNothing() {
+ final Runnable callback = mock(Runnable.class);
+ mService.onWakeUp(callback);
+ mMainExecutor.runAllReady();
+ verify(mDreamOverlayContainerViewController, never()).wakeUp(callback, mMainExecutor);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
index 849ac5e..06a944e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutEngineTest.java
@@ -347,21 +347,21 @@
addComplication(engine, thirdViewInfo);
- // The first added view should now be underneath the second view.
+ // The first added view should now be underneath the third view.
verifyChange(firstViewInfo, false, lp -> {
assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
assertThat(lp.topMargin).isEqualTo(margin);
});
- // The second view should be in underneath the third view.
+ // The second view should be to the start of the third view.
verifyChange(secondViewInfo, false, lp -> {
assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
assertThat(lp.getMarginEnd()).isEqualTo(margin);
});
- // The third view should be in at the top.
+ // The third view should be at the top end corner. No margin should be applied.
verifyChange(thirdViewInfo, true, lp -> {
assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
@@ -425,14 +425,14 @@
addComplication(engine, thirdViewInfo);
- // The first added view should now be underneath the second view.
+ // The first added view should now be underneath the third view.
verifyChange(firstViewInfo, false, lp -> {
assertThat(lp.topToBottom == thirdViewInfo.view.getId()).isTrue();
assertThat(lp.endToEnd == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
assertThat(lp.topMargin).isEqualTo(complicationMargin);
});
- // The second view should be in underneath the third view.
+ // The second view should be to the start of the third view.
verifyChange(secondViewInfo, false, lp -> {
assertThat(lp.endToStart == thirdViewInfo.view.getId()).isTrue();
assertThat(lp.topToTop == ConstraintLayout.LayoutParams.PARENT_ID).isTrue();
@@ -441,6 +441,133 @@
}
/**
+ * Ensures layout sets correct max width constraint.
+ */
+ @Test
+ public void testWidthConstraint() {
+ final int maxWidth = 20;
+ final ComplicationLayoutEngine engine =
+ new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+
+ final ViewInfo viewStartDirection = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_START,
+ 0,
+ 5,
+ maxWidth),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+ final ViewInfo viewEndDirection = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_START,
+ ComplicationLayoutParams.DIRECTION_END,
+ 0,
+ 5,
+ maxWidth),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ addComplication(engine, viewStartDirection);
+ addComplication(engine, viewEndDirection);
+
+ // Verify both horizontal direction views have max width set correctly, and max height is
+ // not set.
+ verifyChange(viewStartDirection, false, lp -> {
+ assertThat(lp.matchConstraintMaxWidth).isEqualTo(maxWidth);
+ assertThat(lp.matchConstraintMaxHeight).isEqualTo(0);
+ });
+ verifyChange(viewEndDirection, false, lp -> {
+ assertThat(lp.matchConstraintMaxWidth).isEqualTo(maxWidth);
+ assertThat(lp.matchConstraintMaxHeight).isEqualTo(0);
+ });
+ }
+
+ /**
+ * Ensures layout sets correct max height constraint.
+ */
+ @Test
+ public void testHeightConstraint() {
+ final int maxHeight = 20;
+ final ComplicationLayoutEngine engine =
+ new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+
+ final ViewInfo viewUpDirection = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_BOTTOM
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_UP,
+ 0,
+ 5,
+ maxHeight),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+ final ViewInfo viewDownDirection = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0,
+ 5,
+ maxHeight),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ addComplication(engine, viewUpDirection);
+ addComplication(engine, viewDownDirection);
+
+ // Verify both vertical direction views have max height set correctly, and max width is
+ // not set.
+ verifyChange(viewUpDirection, false, lp -> {
+ assertThat(lp.matchConstraintMaxHeight).isEqualTo(maxHeight);
+ assertThat(lp.matchConstraintMaxWidth).isEqualTo(0);
+ });
+ verifyChange(viewDownDirection, false, lp -> {
+ assertThat(lp.matchConstraintMaxHeight).isEqualTo(maxHeight);
+ assertThat(lp.matchConstraintMaxWidth).isEqualTo(0);
+ });
+ }
+
+ /**
+ * Ensures layout does not set any constraint if not specified.
+ */
+ @Test
+ public void testConstraintNotSetWhenNotSpecified() {
+ final ComplicationLayoutEngine engine =
+ new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+
+ final ViewInfo view = new ViewInfo(
+ new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP
+ | ComplicationLayoutParams.POSITION_END,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 0,
+ 5),
+ Complication.CATEGORY_STANDARD,
+ mLayout);
+
+ addComplication(engine, view);
+
+ // Verify neither max height nor max width set.
+ verifyChange(view, false, lp -> {
+ assertThat(lp.matchConstraintMaxHeight).isEqualTo(0);
+ assertThat(lp.matchConstraintMaxWidth).isEqualTo(0);
+ });
+ }
+
+ /**
* Ensures layout in a particular position updates.
*/
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
index cb7e47b..fdb4cc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationLayoutParamsTest.java
@@ -100,7 +100,7 @@
* Ensures unspecified margin uses default.
*/
@Test
- public void testUnspecifiedMarginUsesDefault() {
+ public void testDefaultMargin() {
final ComplicationLayoutParams params = new ComplicationLayoutParams(
100,
100,
@@ -136,13 +136,15 @@
ComplicationLayoutParams.POSITION_TOP,
ComplicationLayoutParams.DIRECTION_DOWN,
3,
- 10);
+ 10,
+ 20);
final ComplicationLayoutParams copy = new ComplicationLayoutParams(params);
assertThat(copy.getDirection() == params.getDirection()).isTrue();
assertThat(copy.getPosition() == params.getPosition()).isTrue();
assertThat(copy.getWeight() == params.getWeight()).isTrue();
assertThat(copy.getMargin(0) == params.getMargin(1)).isTrue();
+ assertThat(copy.getConstraint() == params.getConstraint()).isTrue();
assertThat(copy.height == params.height).isTrue();
assertThat(copy.width == params.width).isTrue();
}
@@ -168,4 +170,31 @@
assertThat(copy.height == params.height).isTrue();
assertThat(copy.width == params.width).isTrue();
}
+
+ /**
+ * Ensures that constraint is set correctly.
+ */
+ @Test
+ public void testConstraint() {
+ final ComplicationLayoutParams paramsWithoutConstraint = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 3,
+ 10);
+ assertThat(paramsWithoutConstraint.constraintSpecified()).isFalse();
+
+ final int constraint = 10;
+ final ComplicationLayoutParams paramsWithConstraint = new ComplicationLayoutParams(
+ 100,
+ 100,
+ ComplicationLayoutParams.POSITION_TOP,
+ ComplicationLayoutParams.DIRECTION_DOWN,
+ 3,
+ 10,
+ constraint);
+ assertThat(paramsWithConstraint.constraintSpecified()).isTrue();
+ assertThat(paramsWithConstraint.getConstraint()).isEqualTo(constraint);
+ }
}
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 aa8c93e..e6d3a69 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
@@ -35,6 +35,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
@@ -84,13 +85,19 @@
private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor;
@Mock
- private ImageView mView;
+ private View mView;
+
+ @Mock
+ private ImageView mHomeControlsView;
@Mock
private ActivityStarter mActivityStarter;
@Mock
- UiEventLogger mUiEventLogger;
+ private UiEventLogger mUiEventLogger;
+
+ @Captor
+ private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor;
@Before
public void setup() {
@@ -102,6 +109,7 @@
when(mControlsComponent.getControlsListingController()).thenReturn(
Optional.of(mControlsListingController));
when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE);
+ when(mView.findViewById(R.id.home_controls_chip)).thenReturn(mHomeControlsView);
}
@Test
@@ -164,6 +172,29 @@
verify(mDreamOverlayStateController).addComplication(mComplication);
}
+ @Test
+ public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() {
+ final DreamHomeControlsComplication.Registrant registrant =
+ new DreamHomeControlsComplication.Registrant(mComplication,
+ mDreamOverlayStateController, mControlsComponent);
+ registrant.start();
+
+ setServiceAvailable(true);
+ setHaveFavorites(false);
+
+ // Complication not available on start.
+ verify(mDreamOverlayStateController, never()).addComplication(mComplication);
+
+ // Favorite controls added, complication should be available now.
+ setHaveFavorites(true);
+
+ // Dream overlay becomes active.
+ setDreamOverlayActive(true);
+
+ // Verify complication is added.
+ verify(mDreamOverlayStateController).addComplication(mComplication);
+ }
+
/**
* Ensures clicking home controls chip logs UiEvent.
*/
@@ -180,9 +211,9 @@
final ArgumentCaptor<View.OnClickListener> clickListenerCaptor =
ArgumentCaptor.forClass(View.OnClickListener.class);
- verify(mView).setOnClickListener(clickListenerCaptor.capture());
+ verify(mHomeControlsView).setOnClickListener(clickListenerCaptor.capture());
- clickListenerCaptor.getValue().onClick(mView);
+ clickListenerCaptor.getValue().onClick(mHomeControlsView);
verify(mUiEventLogger).log(
DreamHomeControlsComplication.DreamHomeControlsChipViewController
.DreamOverlayEvent.DREAM_HOME_CONTROLS_TAPPED);
@@ -196,10 +227,17 @@
private void setServiceAvailable(boolean value) {
final List<ControlsServiceInfo> serviceInfos = mock(List.class);
+ when(mControlsListingController.getCurrentServices()).thenReturn(serviceInfos);
when(serviceInfos.isEmpty()).thenReturn(!value);
triggerControlsListingCallback(serviceInfos);
}
+ private void setDreamOverlayActive(boolean value) {
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(value);
+ verify(mDreamOverlayStateController).addCallback(mStateCallbackCaptor.capture());
+ mStateCallbackCaptor.getValue().onStateChanged();
+ }
+
private void triggerControlsListingCallback(List<ControlsServiceInfo> serviceInfos) {
verify(mControlsListingController).addCallback(mCallbackCaptor.capture());
mCallbackCaptor.getValue().onServicesUpdated(serviceInfos);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
index 14a5702..4e3aca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.dreams.touch;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
@@ -33,6 +31,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -52,6 +51,7 @@
@RunWith(AndroidTestingRunner.class)
public class HideComplicationTouchHandlerTest extends SysuiTestCase {
private static final int RESTORE_TIMEOUT = 1000;
+ private static final int HIDE_DELAY = 500;
@Mock
Complication.VisibilityController mVisibilityController;
@@ -71,11 +71,18 @@
@Mock
DreamTouchHandler.TouchSession mSession;
- FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ @Mock
+ DreamOverlayStateController mStateController;
+
+ FakeSystemClock mClock;
+
+ FakeExecutor mFakeExecutor;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mClock = new FakeSystemClock();
+ mFakeExecutor = new FakeExecutor(mClock);
}
/**
@@ -86,10 +93,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report multiple active sessions.
when(mSession.getActiveSessionCount()).thenReturn(2);
@@ -103,8 +111,10 @@
// Verify session end.
verify(mSession).pop();
+ mClock.advanceTime(HIDE_DELAY);
+
// Verify no interaction with visibility controller.
- verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+ verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -115,10 +125,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report one session.
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -132,8 +143,10 @@
// Verify session end.
verify(mSession).pop();
+ mClock.advanceTime(HIDE_DELAY);
+
// Verify no interaction with visibility controller.
- verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+ verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -144,10 +157,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report one session
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -177,8 +191,10 @@
// Verify session ended.
verify(mSession).pop();
+ mClock.advanceTime(HIDE_DELAY);
+
// Verify no interaction with visibility controller.
- verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+ verify(mVisibilityController, never()).setVisibility(anyInt());
}
/**
@@ -189,10 +205,11 @@
final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
mVisibilityController,
RESTORE_TIMEOUT,
+ HIDE_DELAY,
mTouchInsetManager,
mStatusBarKeyguardViewManager,
mFakeExecutor,
- mHandler);
+ mStateController);
// Report one session
when(mSession.getActiveSessionCount()).thenReturn(1);
@@ -221,11 +238,11 @@
inputEventListenerCaptor.getValue().onInputEvent(mMotionEvent);
mFakeExecutor.runAllReady();
- // Verify callback to restore visibility cancelled.
- verify(mHandler).removeCallbacks(any());
-
+ // Verify visibility controller doesn't hide until after timeout
+ verify(mVisibilityController, never()).setVisibility(eq(View.INVISIBLE));
+ mClock.advanceTime(HIDE_DELAY);
// Verify visibility controller told to hide complications.
- verify(mVisibilityController).setVisibility(eq(View.INVISIBLE), anyBoolean());
+ verify(mVisibilityController).setVisibility(eq(View.INVISIBLE));
Mockito.clearInvocations(mVisibilityController, mHandler);
@@ -235,11 +252,8 @@
mFakeExecutor.runAllReady();
// Verify visibility controller told to show complications.
- ArgumentCaptor<Runnable> delayRunnableCaptor = ArgumentCaptor.forClass(Runnable.class);
- verify(mHandler).postDelayed(delayRunnableCaptor.capture(),
- eq(Long.valueOf(RESTORE_TIMEOUT)));
- delayRunnableCaptor.getValue().run();
- verify(mVisibilityController).setVisibility(eq(View.VISIBLE), anyBoolean());
+ mClock.advanceTime(RESTORE_TIMEOUT);
+ verify(mVisibilityController).setVisibility(eq(View.VISIBLE));
// Verify session ended.
verify(mSession).pop();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index 65ae90b..19135d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -103,8 +103,8 @@
// THEN only the requested ones have their dump() method called
verify(dumpable1).dump(pw, args)
verify(dumpable2, never()).dump(
- any(PrintWriter::class.java),
- any(Array<String>::class.java))
+ any(PrintWriter::class.java),
+ any(Array<String>::class.java))
verify(dumpable3).dump(pw, args)
verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
verify(buffer2).dump(pw, 0)
@@ -126,9 +126,9 @@
@Test
fun testCriticalDump() {
// GIVEN a variety of registered dumpables and buffers
- dumpManager.registerDumpable("dumpable1", dumpable1)
- dumpManager.registerDumpable("dumpable2", dumpable2)
- dumpManager.registerDumpable("dumpable3", dumpable3)
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerNormalDumpable("dumpable3", dumpable3)
dumpManager.registerBuffer("buffer1", buffer1)
dumpManager.registerBuffer("buffer2", buffer2)
@@ -136,10 +136,12 @@
val args = arrayOf("--dump-priority", "CRITICAL")
dumpHandler.dump(fd, pw, args)
- // THEN all modules are dumped (but no buffers)
+ // THEN only critical modules are dumped (and no buffers)
verify(dumpable1).dump(pw, args)
verify(dumpable2).dump(pw, args)
- verify(dumpable3).dump(pw, args)
+ verify(dumpable3, never()).dump(
+ any(PrintWriter::class.java),
+ any(Array<String>::class.java))
verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
}
@@ -147,9 +149,9 @@
@Test
fun testNormalDump() {
// GIVEN a variety of registered dumpables and buffers
- dumpManager.registerDumpable("dumpable1", dumpable1)
- dumpManager.registerDumpable("dumpable2", dumpable2)
- dumpManager.registerDumpable("dumpable3", dumpable3)
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerNormalDumpable("dumpable3", dumpable3)
dumpManager.registerBuffer("buffer1", buffer1)
dumpManager.registerBuffer("buffer2", buffer2)
@@ -157,16 +159,14 @@
val args = arrayOf("--dump-priority", "NORMAL")
dumpHandler.dump(fd, pw, args)
- // THEN all buffers are dumped (but no modules)
+ // THEN the normal module and all buffers are dumped
verify(dumpable1, never()).dump(
any(PrintWriter::class.java),
any(Array<String>::class.java))
verify(dumpable2, never()).dump(
any(PrintWriter::class.java),
any(Array<String>::class.java))
- verify(dumpable3, never()).dump(
- any(PrintWriter::class.java),
- any(Array<String>::class.java))
+ verify(dumpable3).dump(pw, args)
verify(buffer1).dump(pw, 0)
verify(buffer2).dump(pw, 0)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
new file mode 100644
index 0000000..0c5a74c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2020 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.dump
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.Dumpable
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.util.mockito.any
+import java.io.PrintWriter
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class DumpManagerTest : SysuiTestCase() {
+
+ @Mock private lateinit var pw: PrintWriter
+
+ @Mock private lateinit var dumpable1: Dumpable
+ @Mock private lateinit var dumpable2: Dumpable
+ @Mock private lateinit var dumpable3: Dumpable
+
+ @Mock private lateinit var buffer1: LogBuffer
+ @Mock private lateinit var buffer2: LogBuffer
+
+ private val dumpManager = DumpManager()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testDumpTarget_dumpable() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+
+ // WHEN a dumpable is dumped explicitly
+ val args = arrayOf<String>()
+ dumpManager.dumpTarget("dumpable2", pw, arrayOf(), tailLength = 0)
+
+ // THEN only the requested one has their dump() method called
+ verify(dumpable1, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable2).dump(pw, args)
+ verify(dumpable3, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
+ verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+ }
+
+ @Test
+ fun testDumpTarget_buffer() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+
+ // WHEN a buffer is dumped explicitly
+ dumpManager.dumpTarget("buffer1", pw, arrayOf(), tailLength = 14)
+
+ // THEN only the requested one has their dump() method called
+ verify(dumpable1, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable2, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable2, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(buffer1).dump(pw, tailLength = 14)
+ verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+ }
+
+ @Test
+ fun testDumpableMatchingIsBasedOnEndOfTag() {
+ // GIVEN a dumpable registered to the manager
+ dumpManager.registerCriticalDumpable("com.android.foo.bar.dumpable1", dumpable1)
+
+ // WHEN that module is dumped
+ val args = arrayOf<String>()
+ dumpManager.dumpTarget("dumpable1", pw, arrayOf(), tailLength = 14)
+
+ // THEN its dump() method is called
+ verify(dumpable1).dump(pw, args)
+ }
+
+ @Test
+ fun testDumpDumpables() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+
+ // WHEN a dumpable dump is requested
+ val args = arrayOf<String>()
+ dumpManager.dumpDumpables(pw, args)
+
+ // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
+ verify(dumpable1).dump(pw, args)
+ verify(dumpable2).dump(pw, args)
+ verify(dumpable3).dump(pw, args)
+ verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
+ verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+ }
+
+ @Test
+ fun testDumpBuffers() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+
+ // WHEN a buffer dump is requested
+ dumpManager.dumpBuffers(pw, tailLength = 1)
+
+ // THEN all buffers are dumped (and no dumpables)
+ verify(dumpable1, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable2, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable3, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(buffer1).dump(pw, tailLength = 1)
+ verify(buffer2).dump(pw, tailLength = 1)
+ }
+
+ @Test
+ fun testCriticalDump() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+
+ // WHEN a critical dump is requested
+ val args = arrayOf<String>()
+ dumpManager.dumpCritical(pw, args)
+
+ // THEN only critical modules are dumped (and no buffers)
+ verify(dumpable1).dump(pw, args)
+ verify(dumpable2).dump(pw, args)
+ verify(dumpable3, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
+ verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+ }
+
+ @Test
+ fun testNormalDump() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+
+ // WHEN a normal dump is requested
+ val args = arrayOf<String>()
+ dumpManager.dumpNormal(pw, args, tailLength = 2)
+
+ // THEN the normal module and all buffers are dumped
+ verify(dumpable1, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable2, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable3).dump(pw, args)
+ verify(buffer1).dump(pw, tailLength = 2)
+ verify(buffer2).dump(pw, tailLength = 2)
+ }
+
+ @Test
+ fun testUnregister() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+
+ dumpManager.unregisterDumpable("dumpable2")
+ dumpManager.unregisterDumpable("dumpable3")
+
+ // WHEN a dumpables dump is requested
+ val args = arrayOf<String>()
+ dumpManager.dumpDumpables(pw, args)
+
+ // THEN the unregistered dumpables (both normal and critical) are not dumped
+ verify(dumpable1).dump(pw, args)
+ verify(dumpable2, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ verify(dumpable3, never())
+ .dump(any(PrintWriter::class.java), any(Array<String>::class.java))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
new file mode 100644
index 0000000..1e7b1f2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
+ private lateinit var restarter: FeatureFlagsDebugRestarter
+
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter)
+ }
+
+ @Test
+ fun testRestart_ImmediateWhenAsleep() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ restarter.restart()
+ verify(systemExitRestarter).restart()
+ }
+
+ @Test
+ fun testRestart_WaitsForSceenOff() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+
+ restarter.restart()
+ verify(systemExitRestarter, never()).restart()
+
+ val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+ verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+ captor.value.onFinishedGoingToSleep()
+
+ verify(systemExitRestarter).restart()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
new file mode 100644
index 0000000..68ca48d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+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.statusbar.policy.BatteryController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
+ private lateinit var restarter: FeatureFlagsReleaseRestarter
+
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var batteryController: BatteryController
+ @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ restarter =
+ FeatureFlagsReleaseRestarter(
+ wakefulnessLifecycle,
+ batteryController,
+ executor,
+ systemExitRestarter
+ )
+ }
+
+ @Test
+ fun testRestart_ScheduledWhenReady() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testRestart_RestartsWhenIdle() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ restarter.restart()
+ verify(systemExitRestarter, never()).restart()
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ verify(systemExitRestarter).restart()
+ }
+
+ @Test
+ fun testRestart_NotScheduledWhenAwake() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
+ @Test
+ fun testRestart_NotScheduledWhenNotPluggedIn() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
+ @Test
+ fun testRestart_NotDoubleSheduled() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testWakefulnessLifecycle_CanRestart() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+
+ val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+ verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+
+ captor.value.onFinishedGoingToSleep()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testBatteryController_CanRestart() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+
+ val captor =
+ ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
+ verify(batteryController).addCallback(captor.capture())
+
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ captor.value.onBatteryLevelChanged(0, true, true)
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 8b1554c..d52616b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -63,6 +63,7 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -103,6 +104,7 @@
@Mock private SecureSettings mSecureSettings;
@Mock private Resources mResources;
@Mock private ConfigurationController mConfigurationController;
+ @Mock private UserTracker mUserTracker;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private UserManager mUserManager;
@Mock private TrustManager mTrustManager;
@@ -152,6 +154,7 @@
mVibratorHelper,
mResources,
mConfigurationController,
+ mUserTracker,
mKeyguardStateController,
mUserManager,
mTrustManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
new file mode 100644
index 0000000..cef452b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard
+
+import android.content.ContentValues
+import android.content.pm.PackageManager
+import android.content.pm.ProviderInfo
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SystemUIAppComponentFactoryBase
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
+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
+import kotlinx.coroutines.runBlocking
+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
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ private lateinit var underTest: KeyguardQuickAffordanceProvider
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = KeyguardQuickAffordanceProvider()
+ val scope = CoroutineScope(IMMEDIATE)
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ )
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ appContext = context,
+ scope = scope,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
+ configs =
+ setOf(
+ FakeKeyguardQuickAffordanceConfig(
+ key = AFFORDANCE_1,
+ pickerName = AFFORDANCE_1_NAME,
+ pickerIconResourceId = 1,
+ ),
+ FakeKeyguardQuickAffordanceConfig(
+ key = AFFORDANCE_2,
+ pickerName = AFFORDANCE_2_NAME,
+ pickerIconResourceId = 2,
+ ),
+ ),
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = localUserSelectionManager,
+ ),
+ dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
+ )
+ underTest.interactor =
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor =
+ KeyguardInteractor(
+ repository = FakeKeyguardRepository(),
+ ),
+ registry = mock(),
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ },
+ repository = { quickAffordanceRepository },
+ )
+
+ underTest.attachInfoForTesting(
+ context,
+ ProviderInfo().apply { authority = Contract.AUTHORITY },
+ )
+ context.contentResolver.addProvider(Contract.AUTHORITY, underTest)
+ context.testablePermissions.setPermission(
+ Contract.PERMISSION,
+ PackageManager.PERMISSION_GRANTED,
+ )
+ }
+
+ @Test
+ fun `onAttachInfo - reportsContext`() {
+ val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock()
+ underTest.setContextAvailableCallback(callback)
+
+ underTest.attachInfo(context, null)
+
+ verify(callback).onContextAvailable(context)
+ }
+
+ @Test
+ fun getType() {
+ assertThat(underTest.getType(Contract.AffordanceTable.URI))
+ .isEqualTo(
+ "vnd.android.cursor.dir/vnd." +
+ "${Contract.AUTHORITY}.${Contract.AffordanceTable.TABLE_NAME}"
+ )
+ assertThat(underTest.getType(Contract.SlotTable.URI))
+ .isEqualTo(
+ "vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}.${Contract.SlotTable.TABLE_NAME}"
+ )
+ assertThat(underTest.getType(Contract.SelectionTable.URI))
+ .isEqualTo(
+ "vnd.android.cursor.dir/vnd." +
+ "${Contract.AUTHORITY}.${Contract.SelectionTable.TABLE_NAME}"
+ )
+ }
+
+ @Test
+ fun `insert and query selection`() =
+ runBlocking(IMMEDIATE) {
+ val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ val affordanceId = AFFORDANCE_2
+ val affordanceName = AFFORDANCE_2_NAME
+
+ insertSelection(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ )
+
+ assertThat(querySelections())
+ .isEqualTo(
+ listOf(
+ Selection(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ affordanceName = affordanceName,
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `query slots`() =
+ runBlocking(IMMEDIATE) {
+ assertThat(querySlots())
+ .isEqualTo(
+ listOf(
+ Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ capacity = 1,
+ ),
+ Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ capacity = 1,
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun `query affordances`() =
+ runBlocking(IMMEDIATE) {
+ assertThat(queryAffordances())
+ .isEqualTo(
+ listOf(
+ Affordance(
+ id = AFFORDANCE_1,
+ name = AFFORDANCE_1_NAME,
+ iconResourceId = 1,
+ ),
+ Affordance(
+ id = AFFORDANCE_2,
+ name = AFFORDANCE_2_NAME,
+ iconResourceId = 2,
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun `delete and query selection`() =
+ runBlocking(IMMEDIATE) {
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ )
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = AFFORDANCE_2,
+ )
+
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
+ " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
+ arrayOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ AFFORDANCE_2,
+ ),
+ )
+
+ assertThat(querySelections())
+ .isEqualTo(
+ listOf(
+ Selection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ affordanceName = AFFORDANCE_1_NAME,
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `delete all selections in a slot`() =
+ runBlocking(IMMEDIATE) {
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ )
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = AFFORDANCE_2,
+ )
+
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ Contract.SelectionTable.Columns.SLOT_ID,
+ arrayOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ ),
+ )
+
+ assertThat(querySelections())
+ .isEqualTo(
+ listOf(
+ Selection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ affordanceName = AFFORDANCE_1_NAME,
+ )
+ )
+ )
+ }
+
+ private fun insertSelection(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ context.contentResolver.insert(
+ Contract.SelectionTable.URI,
+ ContentValues().apply {
+ put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
+ put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
+ }
+ )
+ }
+
+ private fun querySelections(): List<Selection> {
+ return context.contentResolver
+ .query(
+ Contract.SelectionTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val slotIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
+ val affordanceIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+ val affordanceNameColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
+ if (
+ slotIdColumnIndex == -1 ||
+ affordanceIdColumnIndex == -1 ||
+ affordanceNameColumnIndex == -1
+ ) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ Selection(
+ slotId = cursor.getString(slotIdColumnIndex),
+ affordanceId = cursor.getString(affordanceIdColumnIndex),
+ affordanceName = cursor.getString(affordanceNameColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ private fun querySlots(): List<Slot> {
+ return context.contentResolver
+ .query(
+ Contract.SlotTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
+ val capacityColumnIndex =
+ cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
+ if (idColumnIndex == -1 || capacityColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ Slot(
+ id = cursor.getString(idColumnIndex),
+ capacity = cursor.getInt(capacityColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ private fun queryAffordances(): List<Affordance> {
+ return context.contentResolver
+ .query(
+ Contract.AffordanceTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex = cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
+ val nameColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
+ val iconColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
+ if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ Affordance(
+ id = cursor.getString(idColumnIndex),
+ name = cursor.getString(nameColumnIndex),
+ iconResourceId = cursor.getInt(iconColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ data class Slot(
+ val id: String,
+ val capacity: Int,
+ )
+
+ data class Affordance(
+ val id: String,
+ val name: String,
+ val iconResourceId: Int,
+ )
+
+ data class Selection(
+ val slotId: String,
+ val affordanceId: String,
+ val affordanceName: String,
+ )
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val AFFORDANCE_1 = "affordance_1"
+ private const val AFFORDANCE_2 = "affordance_2"
+ private const val AFFORDANCE_1_NAME = "affordance_1_name"
+ private const val AFFORDANCE_2_NAME = "affordance_2_name"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 23516c9..729a1cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -48,6 +48,7 @@
import com.android.systemui.SystemUIInitializerImpl;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -93,6 +94,8 @@
private NextAlarmController mNextAlarmController;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private UserTracker mUserTracker;
private TestableKeyguardSliceProvider mProvider;
private boolean mIsZenMode;
@@ -105,6 +108,7 @@
mProvider.attachInfo(getContext(), null);
reset(mContentResolver);
SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST)));
+ when(mUserTracker.getUserId()).thenReturn(100);
}
@After
@@ -267,6 +271,7 @@
mKeyguardBypassController = KeyguardSliceProviderTest.this.mKeyguardBypassController;
mMediaManager = KeyguardSliceProviderTest.this.mNotificationMediaManager;
mKeyguardUpdateMonitor = KeyguardSliceProviderTest.this.mKeyguardUpdateMonitor;
+ mUserTracker = KeyguardSliceProviderTest.this.mUserTracker;
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b6780a1..d17e374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -57,6 +57,7 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -64,6 +65,7 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -86,6 +88,7 @@
public class KeyguardViewMediatorTest extends SysuiTestCase {
private KeyguardViewMediator mViewMediator;
+ private @Mock UserTracker mUserTracker;
private @Mock DevicePolicyManager mDevicePolicyManager;
private @Mock LockPatternUtils mLockPatternUtils;
private @Mock KeyguardUpdateMonitor mUpdateMonitor;
@@ -110,6 +113,7 @@
private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
private @Mock DreamOverlayStateController mDreamOverlayStateController;
private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+ private @Mock ScrimController mScrimController;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -286,6 +290,7 @@
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
+ mUserTracker,
mFalsingCollector,
mLockPatternUtils,
mBroadcastDispatcher,
@@ -311,7 +316,8 @@
mDreamOverlayStateController,
() -> mShadeController,
mNotificationShadeWindowControllerLazy,
- () -> mActivityLaunchAnimator);
+ () -> mActivityLaunchAnimator,
+ () -> mScrimController);
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
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
new file mode 100644
index 0000000..7205f30
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.CameraGestureHelper
+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
+
+@SmallTest
+@RunWith(JUnit4::class)
+class CameraQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var cameraGestureHelper: CameraGestureHelper
+ @Mock private lateinit var context: Context
+
+ private lateinit var underTest: CameraQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ CameraQuickAffordanceConfig(
+ context,
+ ) {
+ cameraGestureHelper
+ }
+ }
+
+ @Test
+ fun `affordance triggered -- camera launch called`() {
+ // When
+ val result = underTest.onTriggered(null)
+
+ // Then
+ verify(cameraGestureHelper)
+ .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ }
+}
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
new file mode 100644
index 0000000..9fa7db1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.statusbar.policy.FlashlightController
+import com.android.systemui.utils.leaks.FakeFlashlightController
+import com.android.systemui.utils.leaks.LeakCheckedTest
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+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)
+class FlashlightQuickAffordanceConfigTest : LeakCheckedTest() {
+
+ @Mock private lateinit var context: Context
+ private lateinit var flashlightController: FakeFlashlightController
+ private lateinit var underTest: FlashlightQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ injectLeakCheckedDependency(FlashlightController::class.java)
+ MockitoAnnotations.initMocks(this)
+
+ flashlightController =
+ SysuiLeakCheck().getLeakChecker(FlashlightController::class.java)
+ as FakeFlashlightController
+ underTest = FlashlightQuickAffordanceConfig(context, flashlightController)
+ }
+
+ @Test
+ fun `flashlight is off -- triggered -- icon is on and active`() = runTest {
+ // given
+ flashlightController.isEnabled = false
+ flashlightController.isAvailable = true
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+ // when
+ underTest.onTriggered(null)
+ val lastValue = values.last()
+
+ // then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertEquals(
+ R.drawable.qs_flashlight_icon_on,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
+ as? Icon.Resource)
+ ?.res
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight is on -- triggered -- icon is off and inactive`() = runTest {
+ // given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = true
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+ // when
+ underTest.onTriggered(null)
+ val lastValue = values.last()
+
+ // then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertEquals(
+ R.drawable.qs_flashlight_icon_off,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
+ as? Icon.Resource)
+ ?.res
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight is on -- receives error -- icon is off and inactive`() = runTest {
+ // given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+ // when
+ flashlightController.onFlashlightError()
+ val lastValue = values.last()
+
+ // then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertEquals(
+ R.drawable.qs_flashlight_icon_off,
+ ((lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).icon
+ as? Icon.Resource)
+ ?.res
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight availability now off -- hidden`() = runTest {
+ // given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+ // when
+ flashlightController.onFlashlightAvailabilityChanged(false)
+ val lastValue = values.last()
+
+ // then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight availability now on -- flashlight on -- inactive and icon off`() = runTest {
+ // given
+ flashlightController.isEnabled = true
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+ // when
+ flashlightController.onFlashlightAvailabilityChanged(true)
+ val lastValue = values.last()
+
+ // then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertTrue(
+ (lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState
+ is ActivationState.Active
+ )
+ assertEquals(R.drawable.qs_flashlight_icon_on, (lastValue.icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight availability now on -- flashlight off -- inactive and icon off`() = runTest {
+ // given
+ flashlightController.isEnabled = false
+ flashlightController.isAvailable = false
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = launch(UnconfinedTestDispatcher()) { underTest.lockScreenState.toList(values) }
+
+ // when
+ flashlightController.onFlashlightAvailabilityChanged(true)
+ val lastValue = values.last()
+
+ // then
+ assertTrue(lastValue is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+ assertTrue(
+ (lastValue as KeyguardQuickAffordanceConfig.LockScreenState.Visible).activationState
+ is ActivationState.Inactive
+ )
+ assertEquals(R.drawable.qs_flashlight_icon_off, (lastValue.icon as? Icon.Resource)?.res)
+ job.cancel()
+ }
+
+ @Test
+ fun `flashlight available -- picker state default`() = runTest {
+ // given
+ flashlightController.isAvailable = true
+
+ // when
+ val result = underTest.getPickerScreenState()
+
+ // then
+ assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.Default)
+ }
+
+ @Test
+ fun `flashlight not available -- picker state unavailable`() = runTest {
+ // given
+ flashlightController.isAvailable = false
+
+ // when
+ val result = underTest.getPickerScreenState()
+
+ // then
+ assertTrue(result is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index c94cec6..322014a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -24,8 +24,9 @@
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import java.util.Optional
+import java.util.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -40,7 +41,6 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -93,6 +93,14 @@
whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
whenever(component.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
+ whenever(controlsListingController.getCurrentServices())
+ .thenReturn(
+ if (hasServiceInfos) {
+ listOf(mock(), mock())
+ } else {
+ emptyList()
+ }
+ )
whenever(component.canShowWhileLockedSetting)
.thenReturn(MutableStateFlow(canShowWhileLocked))
whenever(component.getVisibility())
@@ -144,6 +152,17 @@
KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java
}
)
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(
+ when {
+ !isFeatureEnabled ->
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice::class
+ .java
+ hasServiceInfos && hasFavorites ->
+ KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java
+ else -> KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java
+ }
+ )
job.cancel()
}
}
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
new file mode 100644
index 0000000..3b0169d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.content.res.Resources
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.any
+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.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+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
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() {
+
+ @Mock private lateinit var sharedPrefs: FakeSharedPreferences
+
+ private lateinit var underTest: KeyguardQuickAffordanceLegacySettingSyncer
+
+ private lateinit var testScope: TestScope
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var selectionManager: KeyguardQuickAffordanceLocalUserSelectionManager
+ private lateinit var settings: FakeSettings
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ val context: Context = mock()
+ sharedPrefs = FakeSharedPreferences()
+ whenever(context.getSharedPreferences(anyString(), any())).thenReturn(sharedPrefs)
+ val resources: Resources = mock()
+ whenever(resources.getStringArray(R.array.config_keyguardQuickAffordanceDefaults))
+ .thenReturn(emptyArray())
+ whenever(context.resources).thenReturn(resources)
+
+ testDispatcher = UnconfinedTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ selectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
+ context = context,
+ userFileManager =
+ mock {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = FakeUserTracker(),
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ )
+ settings = FakeSettings()
+ settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)
+ settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET, 0)
+ settings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0)
+
+ underTest =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = testScope,
+ backgroundDispatcher = testDispatcher,
+ secureSettings = settings,
+ selectionsManager = selectionManager,
+ )
+ }
+
+ @Test
+ fun `Setting a setting selects the affordance`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ settings.putInt(
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ 1,
+ )
+
+ assertThat(
+ selectionManager
+ .getSelections()
+ .getOrDefault(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ emptyList()
+ )
+ )
+ .contains(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `Clearing a setting selects the affordance`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ settings.putInt(
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ 1,
+ )
+ settings.putInt(
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ 0,
+ )
+
+ assertThat(
+ selectionManager
+ .getSelections()
+ .getOrDefault(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ emptyList()
+ )
+ )
+ .doesNotContain(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `Selecting an affordance sets its setting`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ selectionManager.setSelections(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ listOf(BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET)
+ )
+
+ advanceUntilIdle()
+ assertThat(settings.getInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET)).isEqualTo(1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `Unselecting an affordance clears its setting`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ selectionManager.setSelections(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ listOf(BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET)
+ )
+ selectionManager.setSelections(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ emptyList()
+ )
+
+ assertThat(settings.getInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET)).isEqualTo(0)
+
+ job.cancel()
+ }
+}
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
new file mode 100644
index 0000000..67091a9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+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
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() {
+
+ @Mock private lateinit var userFileManager: UserFileManager
+
+ private lateinit var underTest: KeyguardQuickAffordanceLocalUserSelectionManager
+
+ private lateinit var userTracker: FakeUserTracker
+ private lateinit var sharedPrefs: MutableMap<Int, SharedPreferences>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ sharedPrefs = mutableMapOf()
+ whenever(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())).thenAnswer {
+ val userId = it.arguments[2] as Int
+ sharedPrefs.getOrPut(userId) { FakeSharedPreferences() }
+ }
+ userTracker = FakeUserTracker()
+ val dispatcher = UnconfinedTestDispatcher()
+ Dispatchers.setMain(dispatcher)
+
+ underTest =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
+ context = context,
+ userFileManager = userFileManager,
+ userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun setSelections() = runTest {
+ overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ ),
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(affordanceId2),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1, affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1, affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ )
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(),
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `remembers selections by user`() = runTest {
+ val slot1 = "slot_1"
+ val slot2 = "slot_2"
+ val affordance1 = "affordance_1"
+ val affordance2 = "affordance_2"
+ val affordance3 = "affordance_3"
+
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+
+ val userInfos =
+ listOf(
+ UserInfo(/* id= */ 0, "zero", /* flags= */ 0),
+ UserInfo(/* id= */ 1, "one", /* flags= */ 0),
+ )
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ underTest.setSelections(
+ slotId = slot1,
+ affordanceIds = listOf(affordance1),
+ )
+ underTest.setSelections(
+ slotId = slot2,
+ affordanceIds = listOf(affordance2),
+ )
+
+ // Switch to user 1
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 1,
+ )
+ // We never set selections on user 1, so it should be empty.
+ assertSelections(
+ observed = affordanceIdsBySlotId.last(),
+ expected = emptyMap(),
+ )
+ // Now, let's set selections on user 1.
+ underTest.setSelections(
+ slotId = slot1,
+ affordanceIds = listOf(affordance2),
+ )
+ underTest.setSelections(
+ slotId = slot2,
+ affordanceIds = listOf(affordance3),
+ )
+ assertSelections(
+ observed = affordanceIdsBySlotId.last(),
+ expected =
+ mapOf(
+ slot1 to listOf(affordance2),
+ slot2 to listOf(affordance3),
+ ),
+ )
+
+ // Switch back to user 0.
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ // Assert that we still remember the old selections for user 0.
+ assertSelections(
+ observed = affordanceIdsBySlotId.last(),
+ expected =
+ mapOf(
+ slot1 to listOf(affordance1),
+ slot2 to listOf(affordance2),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `selections respects defaults`() = runTest {
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf(
+ "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+ "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+ ),
+ )
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1, affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `selections ignores defaults after selecting an affordance`() = runTest {
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf(
+ "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+ "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+ ),
+ )
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+
+ underTest.setSelections(slotId1, listOf(affordanceId2))
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId2),
+ slotId2 to listOf(affordanceId2),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `selections ignores defaults after clearing a slot`() = runTest {
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf(
+ "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+ "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+ ),
+ )
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+
+ underTest.setSelections(slotId1, listOf())
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(),
+ slotId2 to listOf(affordanceId2),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `responds to backup and restore by reloading the selections from disk`() = runTest {
+ overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+ clearInvocations(userFileManager)
+
+ fakeBroadcastDispatcher.registeredReceivers.firstOrNull()?.onReceive(context, Intent())
+
+ verify(userFileManager, atLeastOnce()).getSharedPreferences(anyString(), anyInt(), anyInt())
+ job.cancel()
+ }
+
+ private fun assertSelections(
+ observed: Map<String, List<String>>?,
+ expected: Map<String, List<String>>,
+ ) {
+ assertThat(underTest.getSelections()).isEqualTo(expected)
+ assertThat(observed).isEqualTo(expected)
+ }
+}
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
new file mode 100644
index 0000000..d7e9cf1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+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.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() {
+
+ @Mock private lateinit var userHandle: UserHandle
+
+ private lateinit var underTest: KeyguardQuickAffordanceRemoteUserSelectionManager
+
+ private lateinit var clientFactory: FakeKeyguardQuickAffordanceProviderClientFactory
+ private lateinit var testScope: TestScope
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var userTracker: FakeUserTracker
+ private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient
+ private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(userHandle.identifier).thenReturn(UserHandle.USER_SYSTEM)
+ whenever(userHandle.isSystem).thenReturn(true)
+ client1 = FakeKeyguardQuickAffordanceProviderClient()
+ client2 = FakeKeyguardQuickAffordanceProviderClient()
+
+ userTracker = FakeUserTracker()
+ userTracker.set(
+ userInfos =
+ listOf(
+ UserInfo(
+ UserHandle.USER_SYSTEM,
+ "Primary",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ OTHER_USER_ID_1,
+ "Secondary 1",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ OTHER_USER_ID_2,
+ "Secondary 2",
+ /* flags= */ 0,
+ ),
+ ),
+ selectedUserIndex = 0,
+ )
+
+ clientFactory =
+ FakeKeyguardQuickAffordanceProviderClientFactory(
+ userTracker,
+ ) { selectedUserId ->
+ when (selectedUserId) {
+ OTHER_USER_ID_1 -> client1
+ OTHER_USER_ID_2 -> client2
+ else -> error("No client set-up for user $selectedUserId!")
+ }
+ }
+
+ testDispatcher = StandardTestDispatcher()
+ testScope = TestScope(testDispatcher)
+
+ underTest =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = testScope.backgroundScope,
+ userTracker = userTracker,
+ clientFactory = clientFactory,
+ userHandle = userHandle,
+ )
+ }
+
+ @Test
+ fun `selections - primary user process`() =
+ testScope.runTest {
+ val values = mutableListOf<Map<String, List<String>>>()
+ val job = launch { underTest.selections.toList(values) }
+
+ runCurrent()
+ assertThat(values.last()).isEmpty()
+
+ client1.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ )
+ client2.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+ )
+
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 1,
+ )
+ runCurrent()
+ assertThat(values.last())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ ),
+ )
+ )
+
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 2,
+ )
+ runCurrent()
+ assertThat(values.last())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+ listOf(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+ ),
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `selections - secondary user process - always empty`() =
+ testScope.runTest {
+ whenever(userHandle.isSystem).thenReturn(false)
+ val values = mutableListOf<Map<String, List<String>>>()
+ val job = launch { underTest.selections.toList(values) }
+
+ runCurrent()
+ assertThat(values.last()).isEmpty()
+
+ client1.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ )
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 1,
+ )
+ runCurrent()
+ assertThat(values.last()).isEmpty()
+
+ job.cancel()
+ }
+
+ @Test
+ fun setSelections() =
+ testScope.runTest {
+ userTracker.set(
+ userInfos = userTracker.userProfiles,
+ selectedUserIndex = 1,
+ )
+ runCurrent()
+
+ underTest.setSelections(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceIds = listOf(FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1),
+ )
+ runCurrent()
+
+ assertThat(underTest.getSelections())
+ .isEqualTo(
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1,
+ ),
+ )
+ )
+ }
+
+ companion object {
+ private const val OTHER_USER_ID_1 = UserHandle.MIN_SECONDARY_USER_ID + 1
+ private const val OTHER_USER_ID_2 = UserHandle.MIN_SECONDARY_USER_ID + 2
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
deleted file mode 100644
index d2422ad..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.data.quickaffordance
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
-
- private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
-
- @Before
- fun setUp() {
- underTest = KeyguardQuickAffordanceSelectionManager()
- }
-
- @Test
- fun setSelections() =
- runBlocking(IMMEDIATE) {
- var affordanceIdsBySlotId: Map<String, List<String>>? = null
- val job = underTest.selections.onEach { affordanceIdsBySlotId = it }.launchIn(this)
- val slotId1 = "slot1"
- val slotId2 = "slot2"
- val affordanceId1 = "affordance1"
- val affordanceId2 = "affordance2"
- val affordanceId3 = "affordance3"
-
- underTest.setSelections(
- slotId = slotId1,
- affordanceIds = listOf(affordanceId1),
- )
- assertSelections(
- affordanceIdsBySlotId,
- mapOf(
- slotId1 to listOf(affordanceId1),
- ),
- )
-
- underTest.setSelections(
- slotId = slotId2,
- affordanceIds = listOf(affordanceId2),
- )
- assertSelections(
- affordanceIdsBySlotId,
- mapOf(
- slotId1 to listOf(affordanceId1),
- slotId2 to listOf(affordanceId2),
- )
- )
-
- underTest.setSelections(
- slotId = slotId1,
- affordanceIds = listOf(affordanceId1, affordanceId3),
- )
- assertSelections(
- affordanceIdsBySlotId,
- mapOf(
- slotId1 to listOf(affordanceId1, affordanceId3),
- slotId2 to listOf(affordanceId2),
- )
- )
-
- underTest.setSelections(
- slotId = slotId1,
- affordanceIds = listOf(affordanceId3),
- )
- assertSelections(
- affordanceIdsBySlotId,
- mapOf(
- slotId1 to listOf(affordanceId3),
- slotId2 to listOf(affordanceId2),
- )
- )
-
- underTest.setSelections(
- slotId = slotId2,
- affordanceIds = listOf(),
- )
- assertSelections(
- affordanceIdsBySlotId,
- mapOf(
- slotId1 to listOf(affordanceId3),
- slotId2 to listOf(),
- )
- )
-
- job.cancel()
- }
-
- private suspend fun assertSelections(
- observed: Map<String, List<String>>?,
- expected: Map<String, List<String>>,
- ) {
- assertThat(underTest.getSelections()).isEqualTo(expected)
- assertThat(observed).isEqualTo(expected)
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
-}
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 2bd8e9a..6255980 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
@@ -24,17 +24,18 @@
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
+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.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -134,6 +135,33 @@
)
}
+ @Test
+ fun `getPickerScreenState - enabled if configured on device - can open camera`() = runTest {
+ whenever(controller.isAvailableOnDevice).thenReturn(true)
+ whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default)
+ }
+
+ @Test
+ fun `getPickerScreenState - disabled if configured on device - cannot open camera`() = runTest {
+ whenever(controller.isAvailableOnDevice).thenReturn(true)
+ whenever(controller.isAbleToOpenCameraApp).thenReturn(false)
+
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable if not configured on device`() = runTest {
+ whenever(controller.isAvailableOnDevice).thenReturn(false)
+ whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.LockScreenState?) {
assertThat(latest)
.isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
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 5178154..d875dd9 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
@@ -33,9 +33,11 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.wallet.controller.QuickAccessWalletController
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.runBlockingTest
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,6 +46,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@@ -59,7 +62,7 @@
underTest =
QuickAccessWalletKeyguardQuickAffordanceConfig(
- mock(),
+ context,
walletController,
activityStarter,
)
@@ -151,6 +154,44 @@
)
}
+ @Test
+ fun `getPickerScreenState - default`() = runTest {
+ setUpState()
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable`() = runTest {
+ setUpState(
+ isWalletEnabled = false,
+ )
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ @Test
+ fun `getPickerScreenState - disabled when there is no icon`() = runTest {
+ setUpState(
+ hasWalletIcon = false,
+ )
+
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+ }
+
+ @Test
+ fun `getPickerScreenState - disabled when there is no card`() = runTest {
+ setUpState(
+ hasSelectedCard = false,
+ )
+
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
+ }
+
private fun setUpState(
isWalletEnabled: Boolean = true,
isWalletQuerySuccessful: Boolean = true,
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 5a7f2bb..c40488a 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
@@ -17,14 +17,27 @@
package com.android.systemui.keyguard.data.repository
+import android.content.pm.UserInfo
+import android.os.UserHandle
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
+import com.android.systemui.util.FakeSharedPreferences
+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
@@ -32,10 +45,13 @@
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
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
@@ -46,17 +62,75 @@
private lateinit var config1: FakeKeyguardQuickAffordanceConfig
private lateinit var config2: FakeKeyguardQuickAffordanceConfig
+ private lateinit var userTracker: FakeUserTracker
+ private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient
+ private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient
@Before
fun setUp() {
- config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
- config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
+ config1 =
+ FakeKeyguardQuickAffordanceConfig(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1
+ )
+ config2 =
+ FakeKeyguardQuickAffordanceConfig(
+ FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2
+ )
+ val scope = CoroutineScope(IMMEDIATE)
+ userTracker = FakeUserTracker()
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ )
+ client1 = FakeKeyguardQuickAffordanceProviderClient()
+ client2 = FakeKeyguardQuickAffordanceProviderClient()
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory =
+ FakeKeyguardQuickAffordanceProviderClientFactory(
+ userTracker,
+ ) { selectedUserId ->
+ when (selectedUserId) {
+ SECONDARY_USER_1 -> client1
+ SECONDARY_USER_2 -> client2
+ else -> error("No set-up client for user $selectedUserId!")
+ }
+ },
+ userHandle = UserHandle.SYSTEM,
+ )
+
underTest =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = localUserSelectionManager,
+ ),
configs = setOf(config1, config2),
+ dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
}
@@ -99,42 +173,105 @@
}
@Test
- fun getAffordancePickerRepresentations() {
- assertThat(underTest.getAffordancePickerRepresentations())
+ fun getAffordancePickerRepresentations() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.getAffordancePickerRepresentations())
+ .isEqualTo(
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config1.key,
+ name = config1.pickerName,
+ iconResourceId = config1.pickerIconResourceId,
+ ),
+ KeyguardQuickAffordancePickerRepresentation(
+ id = config2.key,
+ name = config2.pickerName,
+ iconResourceId = config2.pickerIconResourceId,
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun getSlotPickerRepresentations() {
+ val slot1 = "slot1"
+ val slot2 = "slot2"
+ val slot3 = "slot3"
+ context.orCreateTestableResources.addOverride(
+ R.array.config_keyguardQuickAffordanceSlots,
+ arrayOf(
+ "$slot1:2",
+ "$slot2:4",
+ "$slot3:5",
+ ),
+ )
+
+ assertThat(underTest.getSlotPickerRepresentations())
.isEqualTo(
listOf(
- KeyguardQuickAffordancePickerRepresentation(
- id = config1.key,
- name = config1.pickerName,
- iconResourceId = config1.pickerIconResourceId,
+ KeyguardSlotPickerRepresentation(
+ id = slot1,
+ maxSelectedAffordances = 2,
),
- KeyguardQuickAffordancePickerRepresentation(
- id = config2.key,
- name = config2.pickerName,
- iconResourceId = config2.pickerIconResourceId,
+ KeyguardSlotPickerRepresentation(
+ id = slot2,
+ maxSelectedAffordances = 4,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = slot3,
+ maxSelectedAffordances = 5,
),
)
)
}
@Test
- fun getSlotPickerRepresentations() {
- assertThat(underTest.getSlotPickerRepresentations())
- .isEqualTo(
- listOf(
- KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- maxSelectedAffordances = 1,
+ fun `selections for secondary user`() =
+ runBlocking(IMMEDIATE) {
+ userTracker.set(
+ userInfos =
+ listOf(
+ UserInfo(
+ UserHandle.USER_SYSTEM,
+ "Primary",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ SECONDARY_USER_1,
+ "Secondary 1",
+ /* flags= */ 0,
+ ),
+ UserInfo(
+ SECONDARY_USER_2,
+ "Secondary 2",
+ /* flags= */ 0,
+ ),
),
- KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- maxSelectedAffordances = 1,
- ),
- )
+ selectedUserIndex = 2,
)
- }
+ client2.insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2,
+ )
+ val observed = mutableListOf<Map<String, List<KeyguardQuickAffordanceConfig>>>()
+ val job = underTest.selections.onEach { observed.add(it) }.launchIn(this)
+ yield()
- private suspend fun assertSelections(
+ assertSelections(
+ observed = observed.last(),
+ expected =
+ mapOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+ listOf(
+ config2,
+ ),
+ )
+ )
+
+ job.cancel()
+ }
+
+ private fun assertSelections(
observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
) {
@@ -148,5 +285,7 @@
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 6ba0634..13fc9fc 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
@@ -22,18 +22,25 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Position
import com.android.systemui.doze.DozeHost
+import com.android.systemui.doze.DozeMachine
+import com.android.systemui.doze.DozeTransitionCallback
+import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -52,6 +59,7 @@
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
+ @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
private lateinit var underTest: KeyguardRepositoryImpl
@@ -67,272 +75,349 @@
biometricUnlockController,
keyguardStateController,
keyguardUpdateMonitor,
+ dozeTransitionListener,
)
}
@Test
- fun animateBottomAreaDozingTransitions() = runBlockingTest {
- assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
+ fun animateBottomAreaDozingTransitions() =
+ runTest(UnconfinedTestDispatcher()) {
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
- underTest.setAnimateDozingTransitions(true)
- assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+ underTest.setAnimateDozingTransitions(true)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
- underTest.setAnimateDozingTransitions(false)
- assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
+ underTest.setAnimateDozingTransitions(false)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
- underTest.setAnimateDozingTransitions(true)
- assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
- }
+ underTest.setAnimateDozingTransitions(true)
+ assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
+ }
@Test
- fun bottomAreaAlpha() = runBlockingTest {
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+ fun bottomAreaAlpha() =
+ runTest(UnconfinedTestDispatcher()) {
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
- underTest.setBottomAreaAlpha(0.1f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
+ underTest.setBottomAreaAlpha(0.1f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
- underTest.setBottomAreaAlpha(0.2f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
+ underTest.setBottomAreaAlpha(0.2f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
- underTest.setBottomAreaAlpha(0.3f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
+ underTest.setBottomAreaAlpha(0.3f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
- underTest.setBottomAreaAlpha(0.5f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
+ underTest.setBottomAreaAlpha(0.5f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
- underTest.setBottomAreaAlpha(1.0f)
- assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
- }
+ underTest.setBottomAreaAlpha(1.0f)
+ assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
+ }
@Test
- fun clockPosition() = runBlockingTest {
- assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
+ fun clockPosition() =
+ runTest(UnconfinedTestDispatcher()) {
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
- underTest.setClockPosition(0, 1)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
+ underTest.setClockPosition(0, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
- underTest.setClockPosition(1, 9)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
+ underTest.setClockPosition(1, 9)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
- underTest.setClockPosition(1, 0)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
+ underTest.setClockPosition(1, 0)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
- underTest.setClockPosition(3, 1)
- assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
- }
+ underTest.setClockPosition(3, 1)
+ assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
+ }
@Test
- fun isKeyguardShowing() = runBlockingTest {
- whenever(keyguardStateController.isShowing).thenReturn(false)
- var latest: Boolean? = null
- val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
+ fun isKeyguardShowing() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardStateController.isShowing).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
- assertThat(latest).isFalse()
- assertThat(underTest.isKeyguardShowing()).isFalse()
+ assertThat(latest).isFalse()
+ assertThat(underTest.isKeyguardShowing()).isFalse()
- val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
- whenever(keyguardStateController.isShowing).thenReturn(true)
- captor.value.onKeyguardShowingChanged()
- assertThat(latest).isTrue()
- assertThat(underTest.isKeyguardShowing()).isTrue()
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+ captor.value.onKeyguardShowingChanged()
+ assertThat(latest).isTrue()
+ assertThat(underTest.isKeyguardShowing()).isTrue()
- whenever(keyguardStateController.isShowing).thenReturn(false)
- captor.value.onKeyguardShowingChanged()
- assertThat(latest).isFalse()
- assertThat(underTest.isKeyguardShowing()).isFalse()
+ whenever(keyguardStateController.isShowing).thenReturn(false)
+ captor.value.onKeyguardShowingChanged()
+ assertThat(latest).isFalse()
+ assertThat(underTest.isKeyguardShowing()).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun isDozing() = runBlockingTest {
- var latest: Boolean? = null
- val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ fun isDozing() =
+ runTest(UnconfinedTestDispatcher()) {
+ var latest: Boolean? = null
+ val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
- val captor = argumentCaptor<DozeHost.Callback>()
- verify(dozeHost).addCallback(captor.capture())
+ val captor = argumentCaptor<DozeHost.Callback>()
+ verify(dozeHost).addCallback(captor.capture())
- captor.value.onDozingChanged(true)
- assertThat(latest).isTrue()
+ captor.value.onDozingChanged(true)
+ assertThat(latest).isTrue()
- captor.value.onDozingChanged(false)
- assertThat(latest).isFalse()
+ captor.value.onDozingChanged(false)
+ assertThat(latest).isFalse()
- job.cancel()
- verify(dozeHost).removeCallback(captor.value)
- }
+ job.cancel()
+ verify(dozeHost).removeCallback(captor.value)
+ }
@Test
- fun `isDozing - starts with correct initial value for isDozing`() = runBlockingTest {
- var latest: Boolean? = null
+ fun `isDozing - starts with correct initial value for isDozing`() =
+ runTest(UnconfinedTestDispatcher()) {
+ var latest: Boolean? = null
- whenever(statusBarStateController.isDozing).thenReturn(true)
- var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
- assertThat(latest).isTrue()
- job.cancel()
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+ var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isTrue()
+ job.cancel()
- whenever(statusBarStateController.isDozing).thenReturn(false)
- job = underTest.isDozing.onEach { latest = it }.launchIn(this)
- assertThat(latest).isFalse()
- job.cancel()
- }
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+ job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+ assertThat(latest).isFalse()
+ job.cancel()
+ }
@Test
- fun dozeAmount() = runBlockingTest {
- val values = mutableListOf<Float>()
- val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
+ fun dozeAmount() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+ val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
- val captor = argumentCaptor<StatusBarStateController.StateListener>()
- verify(statusBarStateController).addCallback(captor.capture())
+ val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ verify(statusBarStateController).addCallback(captor.capture())
- captor.value.onDozeAmountChanged(0.433f, 0.4f)
- captor.value.onDozeAmountChanged(0.498f, 0.5f)
- captor.value.onDozeAmountChanged(0.661f, 0.65f)
+ captor.value.onDozeAmountChanged(0.433f, 0.4f)
+ captor.value.onDozeAmountChanged(0.498f, 0.5f)
+ captor.value.onDozeAmountChanged(0.661f, 0.65f)
- assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
+ assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
- job.cancel()
- verify(statusBarStateController).removeCallback(captor.value)
- }
+ job.cancel()
+ verify(statusBarStateController).removeCallback(captor.value)
+ }
@Test
- fun wakefulness() = runBlockingTest {
- val values = mutableListOf<WakefulnessModel>()
- val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
+ fun wakefulness() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<WakefulnessModel>()
+ val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
- val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
- verify(wakefulnessLifecycle).addObserver(captor.capture())
+ val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
+ verify(wakefulnessLifecycle).addObserver(captor.capture())
- captor.value.onStartedWakingUp()
- captor.value.onFinishedWakingUp()
- captor.value.onStartedGoingToSleep()
- captor.value.onFinishedGoingToSleep()
+ captor.value.onStartedWakingUp()
+ captor.value.onFinishedWakingUp()
+ captor.value.onStartedGoingToSleep()
+ captor.value.onFinishedGoingToSleep()
- assertThat(values)
- .isEqualTo(
- listOf(
- // Initial value will be ASLEEP
- WakefulnessModel.ASLEEP,
- WakefulnessModel.STARTING_TO_WAKE,
- WakefulnessModel.AWAKE,
- WakefulnessModel.STARTING_TO_SLEEP,
- WakefulnessModel.ASLEEP,
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initial value will be ASLEEP
+ WakefulnessModel.ASLEEP,
+ WakefulnessModel.STARTING_TO_WAKE,
+ WakefulnessModel.AWAKE,
+ WakefulnessModel.STARTING_TO_SLEEP,
+ WakefulnessModel.ASLEEP,
+ )
)
- )
- job.cancel()
- verify(wakefulnessLifecycle).removeObserver(captor.value)
- }
+ job.cancel()
+ verify(wakefulnessLifecycle).removeObserver(captor.value)
+ }
@Test
- fun isUdfpsSupported() = runBlockingTest {
- whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
- assertThat(underTest.isUdfpsSupported()).isTrue()
+ fun isUdfpsSupported() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
+ assertThat(underTest.isUdfpsSupported()).isTrue()
- whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(false)
- assertThat(underTest.isUdfpsSupported()).isFalse()
- }
+ whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(false)
+ assertThat(underTest.isUdfpsSupported()).isFalse()
+ }
@Test
- fun isBouncerShowing() = runBlockingTest {
- whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
- var latest: Boolean? = null
- val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
+ fun isBouncerShowing() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isFalse()
- val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
- whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
- captor.value.onBouncerShowingChanged()
- assertThat(latest).isTrue()
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isTrue()
- whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
- captor.value.onBouncerShowingChanged()
- assertThat(latest).isFalse()
+ whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+ captor.value.onBouncerShowingChanged()
+ assertThat(latest).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun isKeyguardGoingAway() = runBlockingTest {
- whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
- var latest: Boolean? = null
- val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
+ fun isKeyguardGoingAway() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isFalse()
- val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
- whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
- captor.value.onKeyguardGoingAwayChanged()
- assertThat(latest).isTrue()
+ whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
+ captor.value.onKeyguardGoingAwayChanged()
+ assertThat(latest).isTrue()
- whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
- captor.value.onKeyguardGoingAwayChanged()
- assertThat(latest).isFalse()
+ whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
+ captor.value.onKeyguardGoingAwayChanged()
+ assertThat(latest).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun isDreaming() = runBlockingTest {
- whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
- var latest: Boolean? = null
- val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+ fun isDreaming() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isFalse()
- val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
- verify(keyguardUpdateMonitor).registerCallback(captor.capture())
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(captor.capture())
- captor.value.onDreamingStateChanged(true)
- assertThat(latest).isTrue()
+ captor.value.onDreamingStateChanged(true)
+ assertThat(latest).isTrue()
- captor.value.onDreamingStateChanged(false)
- assertThat(latest).isFalse()
+ captor.value.onDreamingStateChanged(false)
+ assertThat(latest).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun biometricUnlockState() = runBlockingTest {
- val values = mutableListOf<BiometricUnlockModel>()
- val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+ fun biometricUnlockState() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<BiometricUnlockModel>()
+ val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
- val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
- verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+ val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
+ verify(biometricUnlockController).addBiometricModeListener(captor.capture())
- captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
- captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
- captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
- captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
- captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
- captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
+ captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
- assertThat(values)
- .isEqualTo(
- listOf(
- // Initial value will be NONE, followed by onModeChanged() call
- BiometricUnlockModel.NONE,
- BiometricUnlockModel.NONE,
- BiometricUnlockModel.WAKE_AND_UNLOCK,
- BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
- BiometricUnlockModel.SHOW_BOUNCER,
- BiometricUnlockModel.ONLY_WAKE,
- BiometricUnlockModel.UNLOCK_COLLAPSING,
- BiometricUnlockModel.DISMISS_BOUNCER,
- BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initial value will be NONE, followed by onModeChanged() call
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.NONE,
+ BiometricUnlockModel.WAKE_AND_UNLOCK,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+ BiometricUnlockModel.SHOW_BOUNCER,
+ BiometricUnlockModel.ONLY_WAKE,
+ BiometricUnlockModel.UNLOCK_COLLAPSING,
+ BiometricUnlockModel.DISMISS_BOUNCER,
+ BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+ )
)
+
+ job.cancel()
+ verify(biometricUnlockController).removeBiometricModeListener(captor.value)
+ }
+
+ @Test
+ fun dozeTransitionModel() =
+ runTest(UnconfinedTestDispatcher()) {
+ // For the initial state
+ whenever(dozeTransitionListener.oldState).thenReturn(DozeMachine.State.UNINITIALIZED)
+ whenever(dozeTransitionListener.newState).thenReturn(DozeMachine.State.UNINITIALIZED)
+
+ val values = mutableListOf<DozeTransitionModel>()
+ val job = underTest.dozeTransitionModel.onEach(values::add).launchIn(this)
+
+ val listener =
+ withArgCaptor<DozeTransitionCallback> {
+ verify(dozeTransitionListener).addCallback(capture())
+ }
+
+ // These don't have to reflect real transitions from the DozeMachine. Only that the
+ // transitions are properly emitted
+ listener.onDozeTransition(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE)
+ listener.onDozeTransition(DozeMachine.State.DOZE, DozeMachine.State.DOZE_AOD)
+ listener.onDozeTransition(DozeMachine.State.DOZE_AOD_DOCKED, DozeMachine.State.FINISH)
+ listener.onDozeTransition(
+ DozeMachine.State.DOZE_REQUEST_PULSE,
+ DozeMachine.State.DOZE_PULSING
+ )
+ listener.onDozeTransition(
+ DozeMachine.State.DOZE_SUSPEND_TRIGGERS,
+ DozeMachine.State.DOZE_PULSE_DONE
+ )
+ listener.onDozeTransition(
+ DozeMachine.State.DOZE_AOD_PAUSING,
+ DozeMachine.State.DOZE_AOD_PAUSED
)
- job.cancel()
- verify(biometricUnlockController).removeBiometricModeListener(captor.value)
- }
+ assertThat(values)
+ .isEqualTo(
+ listOf(
+ // Initial value will be UNINITIALIZED
+ DozeTransitionModel(
+ DozeStateModel.UNINITIALIZED,
+ DozeStateModel.UNINITIALIZED
+ ),
+ DozeTransitionModel(DozeStateModel.INITIALIZED, DozeStateModel.DOZE),
+ DozeTransitionModel(DozeStateModel.DOZE, DozeStateModel.DOZE_AOD),
+ DozeTransitionModel(DozeStateModel.DOZE_AOD_DOCKED, DozeStateModel.FINISH),
+ DozeTransitionModel(
+ DozeStateModel.DOZE_REQUEST_PULSE,
+ DozeStateModel.DOZE_PULSING
+ ),
+ DozeTransitionModel(
+ DozeStateModel.DOZE_SUSPEND_TRIGGERS,
+ DozeStateModel.DOZE_PULSE_DONE
+ ),
+ DozeTransitionModel(
+ DozeStateModel.DOZE_AOD_PAUSING,
+ DozeStateModel.DOZE_AOD_PAUSED
+ ),
+ )
+ )
+
+ job.cancel()
+ verify(dozeTransitionListener).removeCallback(listener)
+ }
}
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 8b6603d..1c1f039 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
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.Intent
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -29,18 +30,25 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runBlockingTest
@@ -50,6 +58,8 @@
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameter
import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.same
import org.mockito.Mock
@@ -201,7 +211,6 @@
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
@Mock private lateinit var expandable: Expandable
@@ -214,12 +223,14 @@
@JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
@JvmField @Parameter(4) var startActivity: Boolean = false
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+ private lateinit var userTracker: UserTracker
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(expandable.activityLaunchController()).thenReturn(animationController)
+ userTracker = FakeUserTracker()
homeControls =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
val quickAccessWallet =
@@ -228,12 +239,48 @@
)
val qrCodeScanner =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+ val scope = CoroutineScope(IMMEDIATE)
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ )
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = localUserSelectionManager,
+ ),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+ dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
underTest =
KeyguardQuickAffordanceInteractor(
@@ -318,7 +365,6 @@
needStrongAuthAfterBoot: Boolean = true,
keyguardIsUnlocked: Boolean = false,
) {
- whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(any()))
.thenReturn(
if (needStrongAuthAfterBoot) {
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 3364535..11fe905 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
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -26,20 +27,27 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
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
@@ -53,6 +61,8 @@
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
import org.mockito.MockitoAnnotations
@@ -89,13 +99,49 @@
)
qrCodeScanner =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+ val scope = CoroutineScope(IMMEDIATE)
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ )
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = localUserSelectionManager,
+ ),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+ dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
featureFlags =
FakeFeatureFlags().apply {
@@ -284,7 +330,13 @@
.isEqualTo(
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
- listOf(homeControls.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = homeControls.key,
+ name = homeControls.pickerName,
+ iconResourceId = homeControls.pickerIconResourceId,
+ ),
+ ),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
)
@@ -313,7 +365,13 @@
.isEqualTo(
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
- listOf(quickAccessWallet.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = quickAccessWallet.key,
+ name = quickAccessWallet.pickerName,
+ iconResourceId = quickAccessWallet.pickerIconResourceId,
+ ),
+ ),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
)
)
@@ -345,9 +403,21 @@
.isEqualTo(
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
- listOf(quickAccessWallet.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = quickAccessWallet.key,
+ name = quickAccessWallet.pickerName,
+ iconResourceId = quickAccessWallet.pickerIconResourceId,
+ ),
+ ),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
- listOf(qrCodeScanner.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = qrCodeScanner.key,
+ name = qrCodeScanner.pickerName,
+ iconResourceId = qrCodeScanner.pickerIconResourceId,
+ ),
+ ),
)
)
@@ -411,7 +481,13 @@
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
- listOf(quickAccessWallet.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = quickAccessWallet.key,
+ name = quickAccessWallet.pickerName,
+ iconResourceId = quickAccessWallet.pickerIconResourceId,
+ ),
+ ),
)
)
@@ -472,7 +548,13 @@
mapOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
- listOf(quickAccessWallet.key),
+ listOf(
+ KeyguardQuickAffordancePickerRepresentation(
+ id = quickAccessWallet.key,
+ name = quickAccessWallet.pickerName,
+ iconResourceId = quickAccessWallet.pickerIconResourceId,
+ ),
+ ),
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index 21e5068..a6fc13b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -19,6 +19,7 @@
import android.os.Looper
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.view.View
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -43,6 +44,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -93,7 +95,6 @@
@Test
fun testShow_isScrimmed() {
mPrimaryBouncerInteractor.show(true)
- verify(repository).setShowMessage(null)
verify(repository).setOnScreenTurnedOff(false)
verify(repository).setKeyguardAuthenticated(null)
verify(repository).setPrimaryHide(false)
@@ -106,6 +107,7 @@
verify(repository).setPrimaryVisible(true)
verify(repository).setPrimaryShow(any(KeyguardBouncerModel::class.java))
verify(repository).setPrimaryShowingSoon(false)
+ verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.VISIBLE)
}
@Test
@@ -129,6 +131,7 @@
verify(repository).setPrimaryVisible(false)
verify(repository).setPrimaryHide(true)
verify(repository).setPrimaryShow(null)
+ verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
}
@Test
@@ -171,8 +174,10 @@
@Test
fun testShowMessage() {
+ val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
mPrimaryBouncerInteractor.showMessage("abc", null)
- verify(repository).setShowMessage(BouncerShowMessageModel("abc", null))
+ verify(repository).setShowMessage(argCaptor.capture())
+ assertThat(argCaptor.value.message).isEqualTo("abc")
}
@Test
@@ -196,6 +201,12 @@
}
@Test
+ fun testNotifyShowedMessage() {
+ mPrimaryBouncerInteractor.onMessageShown()
+ verify(repository).setShowMessage(null)
+ }
+
+ @Test
fun testOnScreenTurnedOff() {
mPrimaryBouncerInteractor.onScreenTurnedOff()
verify(repository).setOnScreenTurnedOff(true)
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 78148c4..83a5d0e 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
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Intent
+import android.os.UserHandle
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -27,8 +28,11 @@
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
@@ -38,10 +42,13 @@
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
@@ -56,6 +63,7 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verifyZeroInteractions
@@ -115,17 +123,53 @@
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
+ val scope = CoroutineScope(IMMEDIATE)
+ val localUserSelectionManager =
+ KeyguardQuickAffordanceLocalUserSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ )
+ val remoteUserSelectionManager =
+ KeyguardQuickAffordanceRemoteUserSelectionManager(
+ scope = scope,
+ userTracker = userTracker,
+ clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
+ userHandle = UserHandle.SYSTEM,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ localUserSelectionManager = localUserSelectionManager,
+ remoteUserSelectionManager = remoteUserSelectionManager,
+ userTracker = userTracker,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = localUserSelectionManager,
+ ),
configs =
setOf(
homeControlsQuickAffordanceConfig,
quickAccessWalletAffordanceConfig,
qrCodeScannerAffordanceConfig,
),
+ dumpManager = mock(),
+ userHandle = UserHandle.SYSTEM,
)
underTest =
KeyguardBottomAreaViewModel(
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
new file mode 100644
index 0000000..3727134
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+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 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)
+class KeyguardBouncerViewModelTest : SysuiTestCase() {
+ lateinit var underTest: KeyguardBouncerViewModel
+ @Mock lateinit var bouncerView: BouncerView
+ @Mock lateinit var bouncerInteractor: PrimaryBouncerInteractor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
+ }
+
+ @Test
+ fun setMessage() =
+ runBlocking(Dispatchers.Main.immediate) {
+ val flow = MutableStateFlow<BouncerShowMessageModel?>(null)
+ var message: BouncerShowMessageModel? = null
+ Mockito.`when`(bouncerInteractor.showMessage)
+ .thenReturn(flow as Flow<BouncerShowMessageModel>)
+ // Reinitialize the view model.
+ underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
+
+ flow.value = BouncerShowMessageModel(message = "abc", colorStateList = null)
+
+ val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this)
+ assertThat(message?.message).isEqualTo("abc")
+ job.cancel()
+ }
+}
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
new file mode 100644
index 0000000..432764a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.table
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class TableChangeTest : SysuiTestCase() {
+
+ @Test
+ fun setString_isString() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set("fakeValue")
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("fakeValue")
+ }
+
+ @Test
+ fun setBoolean_isBoolean() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(true)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("true")
+ }
+
+ @Test
+ fun setInt_isInt() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(8900)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getVal()).isEqualTo("8900")
+ }
+
+ @Test
+ fun setThenReset_isEmpty() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "", columnName = "fakeName")
+ underTest.set(8900)
+ underTest.reset(timestamp = 0, columnPrefix = "prefix", columnName = "name")
+
+ assertThat(underTest.hasData()).isFalse()
+ assertThat(underTest.getVal()).isEqualTo("null")
+ }
+
+ @Test
+ fun getName_hasPrefix() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+
+ assertThat(underTest.getName()).contains("fakePrefix")
+ assertThat(underTest.getName()).contains("fakeName")
+ }
+
+ @Test
+ fun getName_noPrefix() {
+ val underTest = TableChange(columnPrefix = "", columnName = "fakeName")
+
+ assertThat(underTest.getName()).contains("fakeName")
+ }
+
+ @Test
+ fun resetThenSet_hasNewValue() {
+ val underTest = TableChange()
+
+ underTest.reset(timestamp = 100, columnPrefix = "prefix", columnName = "original")
+ underTest.set("fakeValue")
+ underTest.reset(timestamp = 0, columnPrefix = "", columnName = "updated")
+ underTest.set(8900)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getName()).contains("updated")
+ assertThat(underTest.getName()).doesNotContain("prefix")
+ assertThat(underTest.getName()).doesNotContain("original")
+ assertThat(underTest.getVal()).isEqualTo("8900")
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
new file mode 100644
index 0000000..2c8d7ab
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.table
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class TableLogBufferTest : SysuiTestCase() {
+ private lateinit var underTest: TableLogBuffer
+
+ private lateinit var systemClock: FakeSystemClock
+ private lateinit var outputWriter: StringWriter
+
+ @Before
+ fun setup() {
+ systemClock = FakeSystemClock()
+ outputWriter = StringWriter()
+
+ underTest = TableLogBuffer(MAX_SIZE, NAME, systemClock)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun maxSizeZero_throwsException() {
+ TableLogBuffer(maxSize = 0, "name", systemClock)
+ }
+
+ @Test
+ fun dumpChanges_hasHeader() {
+ val dumpedString = dumpChanges()
+
+ assertThat(logLines(dumpedString)[0]).isEqualTo(HEADER_PREFIX + NAME)
+ }
+
+ @Test
+ fun dumpChanges_hasVersion() {
+ val dumpedString = dumpChanges()
+
+ assertThat(logLines(dumpedString)[1]).isEqualTo("version $VERSION")
+ }
+
+ @Test
+ fun dumpChanges_hasFooter() {
+ val dumpedString = dumpChanges()
+
+ assertThat(logLines(dumpedString).last()).isEqualTo(FOOTER_PREFIX + NAME)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun dumpChanges_str_separatorNotAllowedInPrefix() {
+ val next =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("columnName", "stringValue")
+ }
+ }
+ underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun dumpChanges_bool_separatorNotAllowedInPrefix() {
+ val next =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("columnName", true)
+ }
+ }
+ underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun dumpChanges_int_separatorNotAllowedInPrefix() {
+ val next =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("columnName", 567)
+ }
+ }
+ underTest.logDiffs("some${SEPARATOR}thing", TestDiffable(), next)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun dumpChanges_str_separatorNotAllowedInColumnName() {
+ val next =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("column${SEPARATOR}Name", "stringValue")
+ }
+ }
+ underTest.logDiffs("prefix", TestDiffable(), next)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun dumpChanges_bool_separatorNotAllowedInColumnName() {
+ val next =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("column${SEPARATOR}Name", true)
+ }
+ }
+ underTest.logDiffs("prefix", TestDiffable(), next)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun dumpChanges_int_separatorNotAllowedInColumnName() {
+ val next =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("column${SEPARATOR}Name", 456)
+ }
+ }
+ underTest.logDiffs("prefix", TestDiffable(), next)
+ }
+
+ @Test
+ fun logChange_bool_dumpsCorrectly() {
+ systemClock.setCurrentTimeMillis(4000L)
+
+ underTest.logChange("prefix", "columnName", true)
+
+ val dumpedString = dumpChanges()
+ val expected =
+ TABLE_LOG_DATE_FORMAT.format(4000L) +
+ SEPARATOR +
+ "prefix.columnName" +
+ SEPARATOR +
+ "true"
+ assertThat(dumpedString).contains(expected)
+ }
+
+ @Test
+ fun dumpChanges_strChange_logsFromNext() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("stringValChange", "prevStringVal")
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("stringValChange", "newStringVal")
+ }
+ }
+
+ underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ val expected =
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ "prefix.stringValChange" +
+ SEPARATOR +
+ "newStringVal"
+ assertThat(dumpedString).contains(expected)
+ assertThat(dumpedString).doesNotContain("prevStringVal")
+ }
+
+ @Test
+ fun dumpChanges_boolChange_logsFromNext() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", false)
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", true)
+ }
+ }
+
+ underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ val expected =
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ "prefix.booleanValChange" +
+ SEPARATOR +
+ "true"
+ assertThat(dumpedString).contains(expected)
+ assertThat(dumpedString).doesNotContain("false")
+ }
+
+ @Test
+ fun dumpChanges_intChange_logsFromNext() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("intValChange", 12345)
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("intValChange", 67890)
+ }
+ }
+
+ underTest.logDiffs("prefix", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ val expected =
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ "prefix.intValChange" +
+ SEPARATOR +
+ "67890"
+ assertThat(dumpedString).contains(expected)
+ assertThat(dumpedString).doesNotContain("12345")
+ }
+
+ @Test
+ fun dumpChanges_noPrefix() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", false)
+ }
+ }
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("booleanValChange", true)
+ }
+ }
+
+ // WHEN there's a blank prefix
+ underTest.logDiffs("", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ // THEN the dump still works
+ val expected =
+ TABLE_LOG_DATE_FORMAT.format(100L) + SEPARATOR + "booleanValChange" + SEPARATOR + "true"
+ assertThat(dumpedString).contains(expected)
+ }
+
+ @Test
+ fun dumpChanges_multipleChangesForSameColumn_logs() {
+ lateinit var valToDump: String
+
+ val diffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("valChange", valToDump)
+ }
+ }
+
+ systemClock.setCurrentTimeMillis(12000L)
+ valToDump = "stateValue12"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ systemClock.setCurrentTimeMillis(20000L)
+ valToDump = "stateValue20"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ systemClock.setCurrentTimeMillis(40000L)
+ valToDump = "stateValue40"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ systemClock.setCurrentTimeMillis(45000L)
+ valToDump = "stateValue45"
+ underTest.logDiffs(columnPrefix = "", diffable, diffable)
+
+ val dumpedString = dumpChanges()
+
+ val expected1 =
+ TABLE_LOG_DATE_FORMAT.format(12000L) +
+ SEPARATOR +
+ "valChange" +
+ SEPARATOR +
+ "stateValue12"
+ val expected2 =
+ TABLE_LOG_DATE_FORMAT.format(20000L) +
+ SEPARATOR +
+ "valChange" +
+ SEPARATOR +
+ "stateValue20"
+ val expected3 =
+ TABLE_LOG_DATE_FORMAT.format(40000L) +
+ SEPARATOR +
+ "valChange" +
+ SEPARATOR +
+ "stateValue40"
+ val expected4 =
+ TABLE_LOG_DATE_FORMAT.format(45000L) +
+ SEPARATOR +
+ "valChange" +
+ SEPARATOR +
+ "stateValue45"
+ assertThat(dumpedString).contains(expected1)
+ assertThat(dumpedString).contains(expected2)
+ assertThat(dumpedString).contains(expected3)
+ assertThat(dumpedString).contains(expected4)
+ }
+
+ @Test
+ fun dumpChanges_multipleChangesAtOnce_logs() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ val prevDiffable = object : TestDiffable() {}
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("status", "in progress")
+ row.logChange("connected", false)
+ }
+ }
+
+ underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ val timestamp = TABLE_LOG_DATE_FORMAT.format(100L)
+ val expected1 = timestamp + SEPARATOR + "status" + SEPARATOR + "in progress"
+ val expected2 = timestamp + SEPARATOR + "connected" + SEPARATOR + "false"
+ assertThat(dumpedString).contains(expected1)
+ assertThat(dumpedString).contains(expected2)
+ }
+
+ @Test
+ fun logChange_rowInitializer_dumpsCorrectly() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ underTest.logChange("") { row ->
+ row.logChange("column1", "val1")
+ row.logChange("column2", 2)
+ row.logChange("column3", true)
+ }
+
+ val dumpedString = dumpChanges()
+
+ val timestamp = TABLE_LOG_DATE_FORMAT.format(100L)
+ val expected1 = timestamp + SEPARATOR + "column1" + SEPARATOR + "val1"
+ val expected2 = timestamp + SEPARATOR + "column2" + SEPARATOR + "2"
+ val expected3 = timestamp + SEPARATOR + "column3" + SEPARATOR + "true"
+ assertThat(dumpedString).contains(expected1)
+ assertThat(dumpedString).contains(expected2)
+ assertThat(dumpedString).contains(expected3)
+ }
+
+ @Test
+ fun logChangeAndLogDiffs_bothLogged() {
+ systemClock.setCurrentTimeMillis(100L)
+
+ underTest.logChange("") { row ->
+ row.logChange("column1", "val1")
+ row.logChange("column2", 2)
+ row.logChange("column3", true)
+ }
+
+ systemClock.setCurrentTimeMillis(200L)
+ val prevDiffable = object : TestDiffable() {}
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("column1", "newVal1")
+ row.logChange("column2", 222)
+ row.logChange("column3", false)
+ }
+ }
+
+ underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+
+ val dumpedString = dumpChanges()
+
+ val timestamp1 = TABLE_LOG_DATE_FORMAT.format(100L)
+ val expected1 = timestamp1 + SEPARATOR + "column1" + SEPARATOR + "val1"
+ val expected2 = timestamp1 + SEPARATOR + "column2" + SEPARATOR + "2"
+ val expected3 = timestamp1 + SEPARATOR + "column3" + SEPARATOR + "true"
+ val timestamp2 = TABLE_LOG_DATE_FORMAT.format(200L)
+ val expected4 = timestamp2 + SEPARATOR + "column1" + SEPARATOR + "newVal1"
+ val expected5 = timestamp2 + SEPARATOR + "column2" + SEPARATOR + "222"
+ val expected6 = timestamp2 + SEPARATOR + "column3" + SEPARATOR + "false"
+ assertThat(dumpedString).contains(expected1)
+ assertThat(dumpedString).contains(expected2)
+ assertThat(dumpedString).contains(expected3)
+ assertThat(dumpedString).contains(expected4)
+ assertThat(dumpedString).contains(expected5)
+ assertThat(dumpedString).contains(expected6)
+ }
+
+ @Test
+ fun dumpChanges_rotatesIfBufferIsFull() {
+ lateinit var valToDump: String
+
+ val prevDiffable = object : TestDiffable() {}
+ val nextDiffable =
+ object : TestDiffable() {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {
+ row.logChange("status", valToDump)
+ }
+ }
+
+ for (i in 0 until MAX_SIZE + 3) {
+ valToDump = "testString[$i]"
+ underTest.logDiffs(columnPrefix = "", prevDiffable, nextDiffable)
+ }
+
+ val dumpedString = dumpChanges()
+
+ assertThat(dumpedString).doesNotContain("testString[0]")
+ assertThat(dumpedString).doesNotContain("testString[1]")
+ assertThat(dumpedString).doesNotContain("testString[2]")
+ assertThat(dumpedString).contains("testString[3]")
+ assertThat(dumpedString).contains("testString[${MAX_SIZE + 2}]")
+ }
+
+ private fun dumpChanges(): String {
+ underTest.dump(PrintWriter(outputWriter), arrayOf())
+ return outputWriter.toString()
+ }
+
+ private fun logLines(string: String): List<String> {
+ return string.split("\n").filter { it.isNotBlank() }
+ }
+
+ private open class TestDiffable : Diffable<TestDiffable> {
+ override fun logDiffs(prevVal: TestDiffable, row: TableRowLogger) {}
+ }
+}
+
+private const val NAME = "TestTableBuffer"
+private const val MAX_SIZE = 10
+
+// Copying these here from [TableLogBuffer] so that we catch any accidental versioning change
+private const val HEADER_PREFIX = "SystemUI StateChangeTableSection START: "
+private const val FOOTER_PREFIX = "SystemUI StateChangeTableSection END: "
+private const val SEPARATOR = "|" // TBD
+private const val VERSION = "1"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
index 7cd8e74..56c91bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
@@ -42,6 +42,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -464,7 +465,7 @@
fun onFalseTapOrTouch() {
whenever(mockController.getTransportControls()).thenReturn(mockTransport)
whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true)
- whenever(falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)).thenReturn(true)
+ whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true)
viewModel.updateController(mockController)
val pos = 169
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
index 575b1c6..9d33e6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
@@ -22,13 +22,13 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.broadcast.BroadcastSender
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
import com.android.systemui.media.controls.ui.MediaPlayerData
import com.android.systemui.media.controls.util.MediaUiEventLogger
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -64,7 +64,7 @@
class MediaDataFilterTest : SysuiTestCase() {
@Mock private lateinit var listener: MediaDataManager.Listener
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var broadcastSender: BroadcastSender
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
@@ -85,7 +85,7 @@
mediaDataFilter =
MediaDataFilter(
context,
- broadcastDispatcher,
+ userTracker,
broadcastSender,
lockscreenUserManager,
executor,
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 11eb26b..52b694f 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
@@ -20,6 +20,8 @@
import android.app.Notification.MediaStyle
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
+import android.app.smartspace.SmartspaceConfig
+import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceTarget
import android.content.Intent
import android.graphics.Bitmap
@@ -60,7 +62,6 @@
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -106,6 +107,7 @@
lateinit var metadataBuilder: MediaMetadata.Builder
lateinit var backgroundExecutor: FakeExecutor
lateinit var foregroundExecutor: FakeExecutor
+ lateinit var uiExecutor: FakeExecutor
@Mock lateinit var dumpManager: DumpManager
@Mock lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
@@ -117,6 +119,7 @@
@Mock lateinit var listener: MediaDataManager.Listener
@Mock lateinit var pendingIntent: PendingIntent
@Mock lateinit var activityStarter: ActivityStarter
+ @Mock lateinit var smartspaceManager: SmartspaceManager
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -131,6 +134,7 @@
@Mock private lateinit var tunerService: TunerService
@Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
@Captor lateinit var callbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
+ @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
@@ -145,6 +149,7 @@
fun setup() {
foregroundExecutor = FakeExecutor(clock)
backgroundExecutor = FakeExecutor(clock)
+ uiExecutor = FakeExecutor(clock)
smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
Settings.Secure.putInt(
context.contentResolver,
@@ -155,6 +160,7 @@
MediaDataManager(
context = context,
backgroundExecutor = backgroundExecutor,
+ uiExecutor = uiExecutor,
foregroundExecutor = foregroundExecutor,
mediaControllerFactory = mediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
@@ -172,7 +178,8 @@
systemClock = clock,
tunerService = tunerService,
mediaFlags = mediaFlags,
- logger = logger
+ logger = logger,
+ smartspaceManager = smartspaceManager,
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -191,6 +198,7 @@
putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
+ verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
whenever(controller.transportControls).thenReturn(transportControls)
whenever(controller.playbackInfo).thenReturn(playbackInfo)
@@ -767,15 +775,14 @@
.onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
}
- @Ignore("b/233283726")
@Test
fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
verify(logger).getNewInstanceId()
smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- foregroundExecutor.advanceClockToLast()
- foregroundExecutor.runAllReady()
+ uiExecutor.advanceClockToLast()
+ uiExecutor.runAllReady()
verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
verifyNoMoreInteractions(logger)
@@ -798,7 +805,6 @@
.onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
}
- @Ignore("b/229838140")
@Test
fun testMediaRecommendationDisabled_removesSmartspaceData() {
// GIVEN a media recommendation card is present
@@ -815,7 +821,9 @@
tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
// THEN listeners are notified
+ uiExecutor.advanceClockToLast()
foregroundExecutor.advanceClockToLast()
+ uiExecutor.runAllReady()
foregroundExecutor.runAllReady()
verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
index 84fdfd7..136ace1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.media.controls.models.player.MediaDeviceData
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.settings.UserTracker
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -79,6 +80,7 @@
class MediaResumeListenerTest : SysuiTestCase() {
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var device: MediaDeviceData
@Mock private lateinit var token: MediaSession.Token
@@ -131,12 +133,15 @@
whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
whenever(mockContext.packageManager).thenReturn(context.packageManager)
whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
+ whenever(mockContext.userId).thenReturn(context.userId)
executor = FakeExecutor(clock)
resumeListener =
MediaResumeListener(
mockContext,
broadcastDispatcher,
+ userTracker,
+ executor,
executor,
tunerService,
resumeBrowserFactory,
@@ -177,6 +182,8 @@
MediaResumeListener(
context,
broadcastDispatcher,
+ userTracker,
+ executor,
executor,
tunerService,
resumeBrowserFactory,
@@ -185,7 +192,7 @@
)
listener.setManager(mediaDataManager)
verify(broadcastDispatcher, never())
- .registerReceiver(eq(listener.userChangeReceiver), any(), any(), any(), anyInt(), any())
+ .registerReceiver(eq(listener.userUnlockReceiver), any(), any(), any(), anyInt(), any())
// When data is loaded, we do NOT execute or update anything
listener.onMediaDataLoaded(KEY, OLD_KEY, data)
@@ -289,7 +296,7 @@
resumeListener.setManager(mediaDataManager)
verify(broadcastDispatcher)
.registerReceiver(
- eq(resumeListener.userChangeReceiver),
+ eq(resumeListener.userUnlockReceiver),
any(),
any(),
any(),
@@ -299,7 +306,8 @@
// When we get an unlock event
val intent = Intent(Intent.ACTION_USER_UNLOCKED)
- resumeListener.userChangeReceiver.onReceive(context, intent)
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
+ resumeListener.userUnlockReceiver.onReceive(context, intent)
// Then we should attempt to find recent media for each saved component
verify(resumeBrowser, times(3)).findRecentMedia()
@@ -375,6 +383,8 @@
MediaResumeListener(
mockContext,
broadcastDispatcher,
+ userTracker,
+ executor,
executor,
tunerService,
resumeBrowserFactory,
@@ -386,7 +396,8 @@
// When we load a component that was played recently
val intent = Intent(Intent.ACTION_USER_UNLOCKED)
- resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
+ resumeListener.userUnlockReceiver.onReceive(mockContext, intent)
// We add its resume controls
verify(resumeBrowser, times(1)).findRecentMedia()
@@ -404,6 +415,8 @@
MediaResumeListener(
mockContext,
broadcastDispatcher,
+ userTracker,
+ executor,
executor,
tunerService,
resumeBrowserFactory,
@@ -415,7 +428,8 @@
// When we load a component that is not recent
val intent = Intent(Intent.ACTION_USER_UNLOCKED)
- resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
+ resumeListener.userUnlockReceiver.onReceive(mockContext, intent)
// We do not try to add resume controls
verify(resumeBrowser, times(0)).findRecentMedia()
@@ -443,6 +457,8 @@
MediaResumeListener(
mockContext,
broadcastDispatcher,
+ userTracker,
+ executor,
executor,
tunerService,
resumeBrowserFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
index a8f4138..a943746 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
@@ -25,7 +25,8 @@
import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.media.controls.models.player.MediaViewHolder
import com.android.systemui.monet.ColorScheme
-import com.android.systemui.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -62,6 +63,7 @@
@Mock private lateinit var mediaViewHolder: MediaViewHolder
@Mock private lateinit var gutsViewHolder: GutsViewHolder
@Mock private lateinit var multiRippleController: MultiRippleController
+ @Mock private lateinit var turbulenceNoiseController: TurbulenceNoiseController
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
@@ -76,6 +78,7 @@
context,
mediaViewHolder,
multiRippleController,
+ turbulenceNoiseController,
animatingColorTransitionFactory
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 1ad2ca9b..761773b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -78,9 +78,10 @@
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.ripple.MultiRippleView
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
@@ -178,6 +179,7 @@
private lateinit var dismiss: FrameLayout
private lateinit var dismissText: TextView
private lateinit var multiRippleView: MultiRippleView
+ private lateinit var turbulenceNoiseView: TurbulenceNoiseView
private lateinit var session: MediaSession
private lateinit var device: MediaDeviceData
@@ -210,7 +212,10 @@
private lateinit var recSubtitle3: TextView
private var shouldShowBroadcastButton: Boolean = false
private val fakeFeatureFlag =
- FakeFeatureFlags().apply { this.set(Flags.UMO_SURFACE_RIPPLE, false) }
+ FakeFeatureFlags().apply {
+ this.set(Flags.UMO_SURFACE_RIPPLE, false)
+ this.set(Flags.MEDIA_FALSING_PENALTY, true)
+ }
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -382,6 +387,7 @@
}
multiRippleView = MultiRippleView(context, null)
+ turbulenceNoiseView = TurbulenceNoiseView(context, null)
whenever(viewHolder.player).thenReturn(view)
whenever(viewHolder.appIcon).thenReturn(appIcon)
@@ -425,6 +431,7 @@
whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
+ whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView)
}
/** Initialize elements for the recommendation view holder */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
new file mode 100644
index 0000000..4aa982e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.taptotransfer.receiver
+
+import android.content.Context
+import android.os.Handler
+import android.os.PowerManager
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttLogger
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
+import com.android.systemui.util.wakelock.WakeLock
+
+class FakeMediaTttChipControllerReceiver(
+ commandQueue: CommandQueue,
+ context: Context,
+ logger: MediaTttLogger,
+ windowManager: WindowManager,
+ mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
+ configurationController: ConfigurationController,
+ powerManager: PowerManager,
+ mainHandler: Handler,
+ mediaTttFlags: MediaTttFlags,
+ uiEventLogger: MediaTttReceiverUiEventLogger,
+ viewUtil: ViewUtil,
+ wakeLockBuilder: WakeLock.Builder,
+) :
+ MediaTttChipControllerReceiver(
+ commandQueue,
+ context,
+ logger,
+ windowManager,
+ mainExecutor,
+ accessibilityManager,
+ configurationController,
+ powerManager,
+ mainHandler,
+ mediaTttFlags,
+ uiEventLogger,
+ viewUtil,
+ wakeLockBuilder,
+ ) {
+ override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+ // Just bypass the animation in tests
+ onAnimationEnd.run()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 68a5f47..23f7cdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -114,7 +114,7 @@
fakeWakeLockBuilder = WakeLockFake.Builder(context)
fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
- controllerReceiver = MediaTttChipControllerReceiver(
+ controllerReceiver = FakeMediaTttChipControllerReceiver(
commandQueue,
context,
logger,
@@ -261,7 +261,12 @@
@Test
fun updateView_noOverrides_usesInfoFromAppIcon() {
controllerReceiver.displayView(
- ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride = null)
+ ChipReceiverInfo(
+ routeInfo,
+ appIconDrawableOverride = null,
+ appNameOverride = null,
+ id = "id",
+ )
)
val view = getChipView()
@@ -274,7 +279,12 @@
val drawableOverride = context.getDrawable(R.drawable.ic_celebration)!!
controllerReceiver.displayView(
- ChipReceiverInfo(routeInfo, drawableOverride, appNameOverride = null)
+ ChipReceiverInfo(
+ routeInfo,
+ drawableOverride,
+ appNameOverride = null,
+ id = "id",
+ )
)
val view = getChipView()
@@ -286,7 +296,12 @@
val appNameOverride = "Sweet New App"
controllerReceiver.displayView(
- ChipReceiverInfo(routeInfo, appIconDrawableOverride = null, appNameOverride)
+ ChipReceiverInfo(
+ routeInfo,
+ appIconDrawableOverride = null,
+ appNameOverride,
+ id = "id",
+ )
)
val view = getChipView()
@@ -340,7 +355,7 @@
.addFeature("feature")
.setClientPackageName(packageName)
.build()
- return ChipReceiverInfo(routeInfo, null, null)
+ return ChipReceiverInfo(routeInfo, null, null, id = "id")
}
private fun ViewGroup.getAppIconView() = this.requireViewById<ImageView>(R.id.app_icon)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 939af16..d35a212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -4,6 +4,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+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
@@ -11,11 +12,11 @@
import com.android.wm.shell.util.GroupedRecentTaskInfo
import com.google.common.truth.Truth.assertThat
import java.util.*
+import java.util.function.Consumer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
-import java.util.function.Consumer
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -23,8 +24,14 @@
private val dispatcher = Dispatchers.Unconfined
private val recentTasks: RecentTasks = mock()
+ private val userTracker: UserTracker = mock()
private val recentTaskListProvider =
- ShellRecentTaskListProvider(dispatcher, Runnable::run, Optional.of(recentTasks))
+ ShellRecentTaskListProvider(
+ dispatcher,
+ Runnable::run,
+ Optional.of(recentTasks),
+ userTracker
+ )
@Test
fun loadRecentTasks_oneTask_returnsTheSameTask() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 6c03730..1865ef6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.navigationbar;
+import static android.app.StatusBarManager.WINDOW_NAVIGATION_BAR;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
@@ -47,6 +48,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -69,6 +71,10 @@
@SmallTest
public class NavBarHelperTest extends SysuiTestCase {
+ private static final int DISPLAY_ID = 0;
+ private static final int WINDOW = WINDOW_NAVIGATION_BAR;
+ private static final int STATE_ID = 0;
+
@Mock
AccessibilityManager mAccessibilityManager;
@Mock
@@ -93,6 +99,8 @@
DumpManager mDumpManager;
@Mock
NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater;
+ @Mock
+ CommandQueue mCommandQueue;
private AccessibilityManager.AccessibilityServicesStateChangeListener
mAccessibilityServicesStateChangeListener;
@@ -114,7 +122,7 @@
mAccessibilityButtonModeObserver, mAccessibilityButtonTargetObserver,
mSystemActions, mOverviewProxyService, mAssistManagerLazy,
() -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
- mNavigationModeController, mUserTracker, mDumpManager);
+ mNavigationModeController, mUserTracker, mDumpManager, mCommandQueue);
}
@@ -241,6 +249,45 @@
ACCESSIBILITY_BUTTON_CLICKABLE_STATE);
}
+ @Test
+ public void registerCommandQueueCallbacks() {
+ mNavBarHelper.init();
+ verify(mCommandQueue, times(1)).addCallback(any());
+ }
+
+ @Test
+ public void saveMostRecentSysuiState() {
+ mNavBarHelper.init();
+ mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW, STATE_ID);
+ NavBarHelper.CurrentSysuiState state1 = mNavBarHelper.getCurrentSysuiState();
+
+ // Update window state
+ int newState = STATE_ID + 1;
+ mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW, newState);
+ NavBarHelper.CurrentSysuiState state2 = mNavBarHelper.getCurrentSysuiState();
+
+ // Ensure we get most recent state back
+ assertThat(state1.mWindowState).isNotEqualTo(state2.mWindowState);
+ assertThat(state1.mWindowStateDisplayId).isEqualTo(state2.mWindowStateDisplayId);
+ assertThat(state2.mWindowState).isEqualTo(newState);
+ }
+
+ @Test
+ public void ignoreNonNavbarSysuiState() {
+ mNavBarHelper.init();
+ mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW, STATE_ID);
+ NavBarHelper.CurrentSysuiState state1 = mNavBarHelper.getCurrentSysuiState();
+
+ // Update window state for other window type
+ int newState = STATE_ID + 1;
+ mNavBarHelper.setWindowState(DISPLAY_ID, WINDOW + 1, newState);
+ NavBarHelper.CurrentSysuiState state2 = mNavBarHelper.getCurrentSysuiState();
+
+ // Ensure we get first state back
+ assertThat(state2.mWindowState).isEqualTo(state1.mWindowState);
+ assertThat(state2.mWindowState).isNotEqualTo(newState);
+ }
+
private List<String> createFakeShortcutTargets() {
return new ArrayList<>(List.of("a", "b", "c", "d"));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index c1fa9b3..80adbf0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -44,14 +44,11 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.IntentFilter;
import android.content.res.Resources;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.telecom.TelecomManager;
import android.testing.AndroidTestingRunner;
@@ -79,7 +76,6 @@
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
@@ -119,6 +115,7 @@
import org.mockito.MockitoAnnotations;
import java.util.Optional;
+import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -166,7 +163,7 @@
@Mock
private Handler mHandler;
@Mock
- private BroadcastDispatcher mBroadcastDispatcher;
+ private UserTracker mUserTracker;
@Mock
private UiEventLogger mUiEventLogger;
@Mock
@@ -247,7 +244,7 @@
mSystemActions, mOverviewProxyService,
() -> mock(AssistManager.class), () -> Optional.of(mCentralSurfaces),
mKeyguardStateController, mock(NavigationModeController.class),
- mock(UserTracker.class), mock(DumpManager.class)));
+ mock(UserTracker.class), mock(DumpManager.class), mock(CommandQueue.class)));
mNavigationBar = createNavBar(mContext);
mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
});
@@ -315,14 +312,10 @@
}
@Test
- public void testRegisteredWithDispatcher() {
+ public void testRegisteredWithUserTracker() {
mNavigationBar.init();
mNavigationBar.onViewAttached();
- verify(mBroadcastDispatcher).registerReceiverWithHandler(
- any(BroadcastReceiver.class),
- any(IntentFilter.class),
- any(Handler.class),
- any(UserHandle.class));
+ verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class));
}
@Test
@@ -438,6 +431,12 @@
verify(mNavigationBarView).setVisibility(View.INVISIBLE);
}
+ @Test
+ public void testOnInit_readCurrentSysuiState() {
+ mNavigationBar.init();
+ verify(mNavBarHelper, times(1)).getCurrentSysuiState();
+ }
+
private NavigationBar createNavBar(Context context) {
DeviceProvisionedController deviceProvisionedController =
mock(DeviceProvisionedController.class);
@@ -457,7 +456,7 @@
mStatusBarStateController,
mStatusBarKeyguardViewManager,
mMockSysUiState,
- mBroadcastDispatcher,
+ mUserTracker,
mCommandQueue,
Optional.of(mock(Pip.class)),
Optional.of(mock(Recents.class)),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index c377c37..338182a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -48,6 +48,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.power.PowerUI.WarningsUI;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -85,6 +86,7 @@
private PowerUI mPowerUI;
@Mock private EnhancedEstimates mEnhancedEstimates;
@Mock private PowerManager mPowerManager;
+ @Mock private UserTracker mUserTracker;
@Mock private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock private IThermalService mThermalServiceMock;
private IThermalEventListener mUsbThermalEventListener;
@@ -682,7 +684,8 @@
private void createPowerUi() {
mPowerUI = new PowerUI(
mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy,
- mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager);
+ mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager,
+ mUserTracker);
mPowerUI.mThermalService = mThermalServiceMock;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 346d1e6..65210d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -31,7 +31,6 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -133,7 +132,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isFalse();
+ assertThat(mController.isAbleToOpenCameraApp()).isFalse();
}
@Test
@@ -152,7 +151,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
}
@Test
@@ -162,7 +161,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
}
@Test
@@ -172,7 +171,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
}
@Test
@@ -182,7 +181,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/abc.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
}
@Test
@@ -192,7 +191,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isFalse();
+ assertThat(mController.isAbleToOpenCameraApp()).isFalse();
}
@Test
@@ -202,21 +201,21 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
"def/.ijk", false);
verifyActivityDetails("def/.ijk");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
null, false);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
// Once from setup + twice from this function
verify(mCallback, times(3)).onQRCodeScannerActivityChanged();
@@ -229,7 +228,7 @@
/* enableOnLockScreen */ true);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isFalse();
+ assertThat(mController.isAbleToOpenCameraApp()).isFalse();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
@@ -237,14 +236,14 @@
verifyActivityDetails("def/.ijk");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
null, false);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isFalse();
+ assertThat(mController.isAbleToOpenCameraApp()).isFalse();
verify(mCallback, times(2)).onQRCodeScannerActivityChanged();
}
@@ -296,19 +295,19 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
UserHandle.USER_CURRENT);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
UserHandle.USER_CURRENT);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
// Once from setup + twice from this function
verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged();
}
@@ -320,13 +319,13 @@
/* enableOnLockScreen */ true);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
QR_CODE_SCANNER_PREFERENCE_CHANGE);
verifyActivityDetails(null);
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isFalse();
+ assertThat(mController.isAbleToOpenCameraApp()).isFalse();
// Unregister once again and make sure it affects the next register event
mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
@@ -335,7 +334,7 @@
QR_CODE_SCANNER_PREFERENCE_CHANGE);
verifyActivityDetails("abc/.def");
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
}
@Test
@@ -345,7 +344,7 @@
/* enableOnLockScreen */ false);
assertThat(mController.getIntent()).isNotNull();
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
- assertThat(mController.isEnabledForQuickSettings()).isTrue();
+ assertThat(mController.isAbleToOpenCameraApp()).isTrue();
assertThat(getSettingsQRCodeDefaultComponent()).isNull();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index cd7a949..aedb935 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -439,6 +440,34 @@
verify(mQSPanelController).setExpanded(false);
}
+ @Test
+ public void startsListeningAfterStateChangeToExpanded_inSplitShade() {
+ QSFragment fragment = resumeAndGetFragment();
+ enableSplitShade();
+ fragment.setQsVisible(true);
+ clearInvocations(mQSPanelController);
+
+ fragment.setExpanded(true);
+ verify(mQSPanelController).setListening(true, true);
+ }
+
+ @Test
+ public void testUpdateQSBounds_setMediaClipCorrectly() {
+ QSFragment fragment = resumeAndGetFragment();
+ disableSplitShade();
+
+ Rect mediaHostClip = new Rect();
+ when(mQSPanelController.getPaddingBottom()).thenReturn(50);
+ setLocationOnScreen(mQSPanelScrollView, 25);
+ when(mQSPanelScrollView.getMeasuredHeight()).thenReturn(200);
+ when(mQSMediaHost.getCurrentClipping()).thenReturn(mediaHostClip);
+
+ fragment.updateQsBounds();
+
+ assertEquals(25, mediaHostClip.top);
+ assertEquals(175, mediaHostClip.bottom);
+ }
+
@Override
protected Fragment instantiate(Context context, String className, Bundle arguments) {
MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index c452872..fb1a720 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -54,6 +54,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.nano.SystemUIProtoDump;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -66,7 +67,6 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 5abc0e1..35c8cc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -27,6 +27,8 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.content.Context;
+import android.content.res.Resources;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -42,16 +44,22 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TileLayoutTest extends SysuiTestCase {
- private TileLayout mTileLayout;
+ private Resources mResources;
private int mLayoutSizeForOneTile;
+ private TileLayout mTileLayout; // under test
@Before
public void setUp() throws Exception {
- mTileLayout = new TileLayout(mContext);
+ Context context = Mockito.spy(mContext);
+ mResources = Mockito.spy(context.getResources());
+ Mockito.when(mContext.getResources()).thenReturn(mResources);
+
+ mTileLayout = new TileLayout(context);
// Layout needs to leave space for the tile margins. Three times the margin size is
// sufficient for any number of columns.
mLayoutSizeForOneTile =
@@ -203,4 +211,21 @@
verify(tileRecord1.tileView).setPosition(0);
verify(tileRecord2.tileView).setPosition(1);
}
+
+ @Test
+ public void resourcesChanged_updateResources_returnsTrue() {
+ Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+ mTileLayout.updateResources(); // setup with 1
+ Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2);
+
+ assertEquals(true, mTileLayout.updateResources());
+ }
+
+ @Test
+ public void resourcesSame_updateResources_returnsFalse() {
+ Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1);
+ mTileLayout.updateResources(); // setup with 1
+
+ assertEquals(false, mTileLayout.updateResources());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 213eca8..25c95ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -42,12 +42,12 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 2c2ddbb..23466cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -16,10 +16,8 @@
package com.android.systemui.qs.footer.domain.interactor
-import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.os.UserHandle
import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -30,23 +28,20 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.QSSecurityFooterUtils
import com.android.systemui.qs.footer.FooterActionsTestUtils
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.truth.correspondence.FakeUiEvent
import com.android.systemui.truth.correspondence.LogMaker
-import com.android.systemui.user.UserSwitcherActivity
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.nullable
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestCoroutineScheduler
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,7 +56,7 @@
@Before
fun setUp() {
- utils = FooterActionsTestUtils(context, TestableLooper.get(this))
+ utils = FooterActionsTestUtils(context, TestableLooper.get(this), TestCoroutineScheduler())
}
@Test
@@ -156,54 +151,4 @@
// We only unlock the device.
verify(activityStarter).postQSRunnableDismissingKeyguard(any())
}
-
- @Test
- fun showUserSwitcher_fullScreenDisabled() {
- val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
- val userSwitchDialogController = mock<UserSwitchDialogController>()
- val underTest =
- utils.footerActionsInteractor(
- featureFlags = featureFlags,
- userSwitchDialogController = userSwitchDialogController,
- )
-
- val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
-
- // Dialog is shown.
- verify(userSwitchDialogController).showDialog(context, expandable)
- }
-
- @Test
- fun showUserSwitcher_fullScreenEnabled() {
- val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
- val activityStarter = mock<ActivityStarter>()
- val underTest =
- utils.footerActionsInteractor(
- featureFlags = featureFlags,
- activityStarter = activityStarter,
- )
-
- // The clicked expandable.
- val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
-
- // Dialog is shown.
- val intentCaptor = argumentCaptor<Intent>()
- verify(activityStarter)
- .startActivity(
- intentCaptor.capture(),
- /* dismissShade= */ eq(true),
- /* ActivityLaunchAnimator.Controller= */ nullable(),
- /* showOverLockscreenWhenLocked= */ eq(true),
- eq(UserHandle.SYSTEM),
- )
- assertThat(intentCaptor.value.component)
- .isEqualTo(
- ComponentName(
- context,
- UserSwitcherActivity::class.java,
- )
- )
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 081a218..47afa70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.FakeFgsManagerController
import com.android.systemui.qs.QSSecurityFooterUtils
import com.android.systemui.qs.footer.FooterActionsTestUtils
@@ -44,12 +45,9 @@
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -62,16 +60,20 @@
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class FooterActionsViewModelTest : SysuiTestCase() {
+ private val testScope = TestScope()
private lateinit var utils: FooterActionsTestUtils
- private val testDispatcher = UnconfinedTestDispatcher(TestCoroutineScheduler())
@Before
fun setUp() {
- utils = FooterActionsTestUtils(context, TestableLooper.get(this))
+ utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler)
+ }
+
+ private fun runTest(block: suspend TestScope.() -> Unit) {
+ testScope.runTest(testBody = block)
}
@Test
- fun settingsButton() = runBlockingTest {
+ fun settingsButton() = runTest {
val underTest = utils.footerActionsViewModel(showPowerButton = false)
val settings = underTest.settings
@@ -87,7 +89,7 @@
}
@Test
- fun powerButton() = runBlockingTest {
+ fun powerButton() = runTest {
// Without power button.
val underTestWithoutPower = utils.footerActionsViewModel(showPowerButton = false)
assertThat(underTestWithoutPower.power).isNull()
@@ -114,7 +116,7 @@
}
@Test
- fun userSwitcher() = runBlockingTest {
+ fun userSwitcher() = runTest {
val picture: Drawable = mock()
val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
val settings = FakeSettings()
@@ -135,7 +137,6 @@
showPowerButton = false,
footerActionsInteractor =
utils.footerActionsInteractor(
- bgDispatcher = testDispatcher,
userSwitcherRepository =
utils.userSwitcherRepository(
userTracker = userTracker,
@@ -143,22 +144,12 @@
userManager = userManager,
userInfoController = userInfoController,
userSwitcherController = userSwitcherControllerWrapper.controller,
- bgDispatcher = testDispatcher,
),
)
)
// Collect the user switcher into currentUserSwitcher.
- var currentUserSwitcher: FooterActionsButtonViewModel? = null
- val job = launch { underTest.userSwitcher.collect { currentUserSwitcher = it } }
- fun currentUserSwitcher(): FooterActionsButtonViewModel? {
- // Make sure we finish collecting the current user switcher. This is necessary because
- // combined flows launch multiple coroutines in the current scope so we need to make
- // sure we process all coroutines triggered by our flow collection before we make
- // assertions on the current buttons.
- advanceUntilIdle()
- return currentUserSwitcher
- }
+ val currentUserSwitcher = collectLastValue(underTest.userSwitcher)
// The user switcher is disabled.
assertThat(currentUserSwitcher()).isNull()
@@ -203,12 +194,10 @@
// in guest mode.
userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() }
assertThat(iconTint()).isNull()
-
- job.cancel()
}
@Test
- fun security() = runBlockingTest {
+ fun security() = runTest {
val securityController = FakeSecurityController()
val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
@@ -224,22 +213,15 @@
footerActionsInteractor =
utils.footerActionsInteractor(
qsSecurityFooterUtils = qsSecurityFooterUtils,
- bgDispatcher = testDispatcher,
securityRepository =
utils.securityRepository(
securityController = securityController,
- bgDispatcher = testDispatcher,
),
),
)
// Collect the security model into currentSecurity.
- var currentSecurity: FooterActionsSecurityButtonViewModel? = null
- val job = launch { underTest.security.collect { currentSecurity = it } }
- fun currentSecurity(): FooterActionsSecurityButtonViewModel? {
- advanceUntilIdle()
- return currentSecurity
- }
+ val currentSecurity = collectLastValue(underTest.security)
// By default, we always return a null SecurityButtonConfig.
assertThat(currentSecurity()).isNull()
@@ -270,12 +252,10 @@
security = currentSecurity()
assertThat(security).isNotNull()
assertThat(security!!.onClick).isNull()
-
- job.cancel()
}
@Test
- fun foregroundServices() = runBlockingTest {
+ fun foregroundServices() = runTest {
val securityController = FakeSecurityController()
val fgsManagerController =
FakeFgsManagerController(
@@ -300,21 +280,14 @@
securityRepository =
utils.securityRepository(
securityController,
- bgDispatcher = testDispatcher,
),
foregroundServicesRepository =
utils.foregroundServicesRepository(fgsManagerController),
- bgDispatcher = testDispatcher,
),
)
// Collect the security model into currentSecurity.
- var currentForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
- val job = launch { underTest.foregroundServices.collect { currentForegroundServices = it } }
- fun currentForegroundServices(): FooterActionsForegroundServicesButtonViewModel? {
- advanceUntilIdle()
- return currentForegroundServices
- }
+ val currentForegroundServices = collectLastValue(underTest.foregroundServices)
// We don't show the foreground services button if the number of running packages is not
// > 1.
@@ -356,12 +329,10 @@
}
securityController.updateState {}
assertThat(currentForegroundServices()?.displayText).isFalse()
-
- job.cancel()
}
@Test
- fun observeDeviceMonitoringDialogRequests() = runBlockingTest {
+ fun observeDeviceMonitoringDialogRequests() = runTest {
val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
val broadcastDispatcher = mock<BroadcastDispatcher>()
@@ -390,7 +361,6 @@
utils.footerActionsInteractor(
qsSecurityFooterUtils = qsSecurityFooterUtils,
broadcastDispatcher = broadcastDispatcher,
- bgDispatcher = testDispatcher,
),
)
@@ -415,7 +385,4 @@
underTest.onVisibilityChangeRequested(visible = true)
assertThat(underTest.isVisible.value).isTrue()
}
-
- private fun runBlockingTest(block: suspend TestScope.() -> Unit) =
- runTest(testDispatcher) { block() }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index 73a0cbc..030c59f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.GlobalSettings
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
@@ -64,6 +65,8 @@
private lateinit var mConnectivityManager: Lazy<ConnectivityManager>
@Mock
private lateinit var mGlobalSettings: GlobalSettings
+ @Mock
+ private lateinit var mUserTracker: UserTracker
private lateinit var mTestableLooper: TestableLooper
private lateinit var mTile: AirplaneModeTile
@@ -87,7 +90,8 @@
mQsLogger,
mBroadcastDispatcher,
mConnectivityManager,
- mGlobalSettings)
+ mGlobalSettings,
+ mUserTracker)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index f7b9438e..e0b3125 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
@@ -118,8 +119,9 @@
`when`(qsHost.context).thenReturn(spiedContext)
`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
`when`(controlsComponent.isEnabled()).thenReturn(true)
- `when`(controlsController.getPreferredStructure())
- .thenReturn(StructureInfo(ComponentName("pkg", "cls"), "structure", listOf()))
+ `when`(controlsController.getPreferredSelection())
+ .thenReturn(SelectedItem.StructureItem(
+ StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
secureSettings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 1)
setupControlsComponent()
@@ -226,12 +228,12 @@
capture(listingCallbackCaptor)
)
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
- `when`(controlsController.getPreferredStructure()).thenReturn(
- StructureInfo(
+ `when`(controlsController.getPreferredSelection()).thenReturn(
+ SelectedItem.StructureItem(StructureInfo(
ComponentName("pkg", "cls"),
"structure",
listOf(ControlInfo("id", "title", "subtitle", 1))
- )
+ ))
)
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -247,8 +249,9 @@
capture(listingCallbackCaptor)
)
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
- `when`(controlsController.getPreferredStructure())
- .thenReturn(StructureInfo(ComponentName("pkg", "cls"), "structure", listOf()))
+ `when`(controlsController.getPreferredSelection())
+ .thenReturn(SelectedItem.StructureItem(
+ StructureInfo(ComponentName("pkg", "cls"), "structure", listOf())))
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
testableLooper.processAllMessages()
@@ -257,6 +260,22 @@
}
@Test
+ fun testStateActiveIfPreferredIsPanel() {
+ verify(controlsListingController).observe(
+ any(LifecycleOwner::class.java),
+ capture(listingCallbackCaptor)
+ )
+ `when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
+ `when`(controlsController.getPreferredSelection())
+ .thenReturn(SelectedItem.PanelItem("appName", ComponentName("pkg", "cls")))
+
+ listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
+ testableLooper.processAllMessages()
+
+ assertThat(tile.state.state).isEqualTo(Tile.STATE_ACTIVE)
+ }
+
+ @Test
fun testStateInactiveIfLocked() {
verify(controlsListingController).observe(
any(LifecycleOwner::class.java),
@@ -303,12 +322,12 @@
)
`when`(controlsComponent.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
`when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
- `when`(controlsController.getPreferredStructure()).thenReturn(
- StructureInfo(
- ComponentName("pkg", "cls"),
- "structure",
- listOf(ControlInfo("id", "title", "subtitle", 1))
- )
+ `when`(controlsController.getPreferredSelection()).thenReturn(
+ SelectedItem.StructureItem(StructureInfo(
+ ComponentName("pkg", "cls"),
+ "structure",
+ listOf(ControlInfo("id", "title", "subtitle", 1))
+ ))
)
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
@@ -334,12 +353,12 @@
`when`(controlsComponent.getVisibility())
.thenReturn(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK)
`when`(controlsUiController.resolveActivity()).thenReturn(ControlsActivity::class.java)
- `when`(controlsController.getPreferredStructure()).thenReturn(
- StructureInfo(
+ `when`(controlsController.getPreferredSelection()).thenReturn(
+ SelectedItem.StructureItem(StructureInfo(
ComponentName("pkg", "cls"),
"structure",
listOf(ControlInfo("id", "title", "subtitle", 1))
- )
+ ))
)
listingCallbackCaptor.value.onServicesUpdated(listOf(serviceInfo))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index cac90a1..a1be2f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -18,6 +18,7 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
import static org.mockito.Mockito.when;
@@ -108,17 +109,20 @@
@Test
public void testQRCodeTileUnavailable() {
- when(mController.isEnabledForQuickSettings()).thenReturn(false);
+ when(mController.isAbleToOpenCameraApp()).thenReturn(false);
QSTile.State state = new QSTile.State();
mTile.handleUpdateState(state, null);
assertEquals(state.state, Tile.STATE_UNAVAILABLE);
+ assertEquals(state.secondaryLabel.toString(),
+ mContext.getString(R.string.qr_code_scanner_updating_secondary_label));
}
@Test
public void testQRCodeTileAvailable() {
- when(mController.isEnabledForQuickSettings()).thenReturn(true);
+ when(mController.isAbleToOpenCameraApp()).thenReturn(true);
QSTile.State state = new QSTile.State();
mTile.handleUpdateState(state, null);
assertEquals(state.state, Tile.STATE_INACTIVE);
+ assertNull(state.secondaryLabel);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index 3131f60..08a90b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -42,27 +42,21 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class UserDetailViewAdapterTest : SysuiTestCase() {
- @Mock
- private lateinit var mUserSwitcherController: UserSwitcherController
- @Mock
- private lateinit var mParent: ViewGroup
- @Mock
- private lateinit var mUserDetailItemView: UserDetailItemView
- @Mock
- private lateinit var mOtherView: View
- @Mock
- private lateinit var mInflatedUserDetailItemView: UserDetailItemView
- @Mock
- private lateinit var mLayoutInflater: LayoutInflater
+ @Mock private lateinit var mUserSwitcherController: UserSwitcherController
+ @Mock private lateinit var mParent: ViewGroup
+ @Mock private lateinit var mUserDetailItemView: UserDetailItemView
+ @Mock private lateinit var mOtherView: View
+ @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
+ @Mock private lateinit var mLayoutInflater: LayoutInflater
private var falsingManagerFake: FalsingManagerFake = FalsingManagerFake()
private lateinit var adapter: UserDetailView.Adapter
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -77,10 +71,13 @@
`when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
.thenReturn(mInflatedUserDetailItemView)
`when`(mParent.context).thenReturn(mContext)
- adapter = UserDetailView.Adapter(
- mContext, mUserSwitcherController, uiEventLogger,
- falsingManagerFake
- )
+ adapter =
+ UserDetailView.Adapter(
+ mContext,
+ mUserSwitcherController,
+ uiEventLogger,
+ falsingManagerFake
+ )
mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 05c1f15..6d2972d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -31,6 +31,7 @@
import android.animation.Animator;
import android.content.Intent;
+import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
@@ -40,6 +41,7 @@
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -58,8 +60,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.UnreleasedFlag;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -85,6 +87,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -161,8 +164,8 @@
private WifiStateWorker mWifiStateWorker;
@Mock
private SignalStrength mSignalStrength;
- @Mock
- private FeatureFlags mFlags;
+
+ private FakeFeatureFlags mFlags = new FakeFeatureFlags();
private TestableResources mTestableResources;
private InternetDialogController mInternetDialogController;
@@ -213,6 +216,7 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mInternetDialogController.mActivityStarter = mActivityStarter;
mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, false);
}
@After
@@ -348,7 +352,7 @@
@Test
public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
fakeAirplaneModeEnabled(false);
when(mWifiStateWorker.isWifiEnabled()).thenReturn(true);
@@ -705,7 +709,7 @@
@Test
public void getSignalStrengthIcon_differentSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
Drawable icons = spyController.getSignalStrengthIcon(SUB_ID, mContext, 1, 1, 0, false);
Drawable icons2 = spyController.getSignalStrengthIcon(SUB_ID2, mContext, 1, 1, 0, false);
@@ -715,7 +719,7 @@
@Test
public void getActiveAutoSwitchNonDdsSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
// active on non-DDS
SubscriptionInfo info = mock(SubscriptionInfo.class);
doReturn(SUB_ID2).when(info).getSubscriptionId();
@@ -751,16 +755,35 @@
@Test
public void getMobileNetworkSummary() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
+ Resources res1 = mock(Resources.class);
+ doReturn("EDGE").when(res1).getString(anyInt());
+ Resources res2 = mock(Resources.class);
+ doReturn("LTE").when(res2).getString(anyInt());
+ when(SubscriptionManager.getResourcesForSubId(any(), eq(SUB_ID))).thenReturn(res1);
+ when(SubscriptionManager.getResourcesForSubId(any(), eq(SUB_ID2))).thenReturn(res2);
+
InternetDialogController spyController = spy(mInternetDialogController);
+ Map<Integer, TelephonyDisplayInfo> mSubIdTelephonyDisplayInfoMap =
+ spyController.mSubIdTelephonyDisplayInfoMap;
+ TelephonyDisplayInfo info1 = new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_EDGE,
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+ TelephonyDisplayInfo info2 = new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+
+ mSubIdTelephonyDisplayInfoMap.put(SUB_ID, info1);
+ mSubIdTelephonyDisplayInfoMap.put(SUB_ID2, info2);
+
doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
doReturn(true).when(spyController).isMobileDataEnabled();
doReturn(true).when(spyController).activeNetworkIsCellular();
String dds = spyController.getMobileNetworkSummary(SUB_ID);
String nonDds = spyController.getMobileNetworkSummary(SUB_ID2);
+ String ddsNetworkType = dds.split("/")[1];
+ String nonDdsNetworkType = nonDds.split("/")[1];
assertThat(dds).contains(mContext.getString(R.string.mobile_data_poor_connection));
- assertThat(dds).isNotEqualTo(nonDds);
+ assertThat(ddsNetworkType).isNotEqualTo(nonDdsNetworkType);
}
@Test
@@ -775,7 +798,7 @@
@Test
public void launchMobileNetworkSettings_validSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
spyController.launchMobileNetworkSettings(mDialogLaunchView);
@@ -786,7 +809,7 @@
@Test
public void launchMobileNetworkSettings_invalidSubId() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
InternetDialogController spyController = spy(mInternetDialogController);
doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
.when(spyController).getActiveAutoSwitchNonDdsSubId();
@@ -798,7 +821,7 @@
@Test
public void setAutoDataSwitchMobileDataPolicy() {
- when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
mInternetDialogController.setAutoDataSwitchMobileDataPolicy(SUB_ID, true);
verify(mTelephonyManager).setMobileDataPolicyEnabled(eq(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
new file mode 100644
index 0000000..ea0e454
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.reardisplay;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import android.hardware.devicestate.DeviceStateManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class RearDisplayDialogControllerTest extends SysuiTestCase {
+
+ @Mock
+ private CommandQueue mCommandQueue;
+
+ private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+
+
+ private static final int CLOSED_BASE_STATE = 0;
+ private static final int OPEN_BASE_STATE = 1;
+
+ @Test
+ public void testClosedDialogIsShown() {
+ RearDisplayDialogController controller = new RearDisplayDialogController(mContext,
+ mCommandQueue, mFakeExecutor);
+ controller.setDeviceStateManagerCallback(new TestDeviceStateManagerCallback());
+ controller.setFoldedStates(new int[]{0});
+ controller.setAnimationRepeatCount(0);
+
+ controller.showRearDisplayDialog(CLOSED_BASE_STATE);
+ assertTrue(controller.mRearDisplayEducationDialog.isShowing());
+ View deviceOpenedWarningTextView = controller.mRearDisplayEducationDialog.findViewById(
+ R.id.rear_display_warning_text_view);
+ assertNull(deviceOpenedWarningTextView);
+ }
+
+ @Test
+ public void testOpenDialogIsShown() {
+ RearDisplayDialogController controller = new RearDisplayDialogController(mContext,
+ mCommandQueue, mFakeExecutor);
+ controller.setDeviceStateManagerCallback(new TestDeviceStateManagerCallback());
+ controller.setFoldedStates(new int[]{0});
+ controller.setAnimationRepeatCount(0);
+
+ controller.showRearDisplayDialog(OPEN_BASE_STATE);
+
+ assertTrue(controller.mRearDisplayEducationDialog.isShowing());
+ View deviceOpenedWarningTextView = controller.mRearDisplayEducationDialog.findViewById(
+ R.id.rear_display_warning_text_view);
+ assertNotNull(deviceOpenedWarningTextView);
+ }
+
+ /**
+ * Empty device state manager callbacks, so we can verify that the correct
+ * dialogs are being created regardless of device state of the test device.
+ */
+ private static class TestDeviceStateManagerCallback implements
+ DeviceStateManager.DeviceStateCallback {
+
+ @Override
+ public void onStateChanged(int state) { }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 013e58e..69f3e987 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -33,6 +33,9 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -49,12 +52,16 @@
*/
public class RecordingControllerTest extends SysuiTestCase {
+ private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@Mock
private RecordingController.RecordingStateChangeCallback mCallback;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
private UserContextProvider mUserContextProvider;
+ @Mock
+ private UserTracker mUserTracker;
private RecordingController mController;
@@ -63,7 +70,8 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mController = new RecordingController(mBroadcastDispatcher, mUserContextProvider);
+ mController = new RecordingController(mMainExecutor, mBroadcastDispatcher,
+ mUserContextProvider, mUserTracker);
mController.addCallback(mCallback);
}
@@ -176,9 +184,7 @@
mController.updateState(true);
// and user is changed
- Intent intent = new Intent(Intent.ACTION_USER_SWITCHED)
- .putExtra(Intent.EXTRA_USER_HANDLE, USER_ID);
- mController.mUserChangeReceiver.onReceive(mContext, intent);
+ mController.mUserChangedCallback.onUserChanged(USER_ID, mContext);
// Ensure that the recording was stopped
verify(mCallback).onRecordingEnd();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 8c9404e..85c8ba7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -184,7 +184,7 @@
ActionTransition::new, mSmartActionsProvider);
Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
- Uri.parse("Screenshot_123.png")).get().action;
+ Uri.parse("Screenshot_123.png"), true).get().action;
Intent intent = shareAction.actionIntent.getIntent();
assertNotNull(intent);
@@ -212,7 +212,7 @@
ActionTransition::new, mSmartActionsProvider);
Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
- Uri.parse("Screenshot_123.png")).get().action;
+ Uri.parse("Screenshot_123.png"), true).get().action;
Intent intent = editAction.actionIntent.getIntent();
assertNotNull(intent);
@@ -241,7 +241,7 @@
Notification.Action deleteAction = task.createDeleteAction(mContext,
mContext.getResources(),
- Uri.parse("Screenshot_123.png"));
+ Uri.parse("Screenshot_123.png"), true);
Intent intent = deleteAction.actionIntent.getIntent();
assertNotNull(intent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt
new file mode 100644
index 0000000..333e634
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/sensorprivacy/SensorUseStartedActivityTest.kt
@@ -0,0 +1,38 @@
+package com.android.systemui.sensorprivacy
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class SensorUseStartedActivityTest : SysuiTestCase() {
+ open class SensorUseStartedActivityTestable :
+ SensorUseStartedActivity(
+ sensorPrivacyController = mock(),
+ keyguardStateController = mock(),
+ keyguardDismissUtil = mock(),
+ bgHandler = mock(),
+ )
+
+ @get:Rule val activityRule = ActivityScenarioRule(SensorUseStartedActivityTestable::class.java)
+
+ @Test
+ fun onBackPressed_doNothing() {
+ activityRule.scenario.onActivity { activity ->
+ assertThat(activity.isFinishing).isFalse()
+
+ activity.onBackPressed()
+
+ assertThat(activity.isFinishing).isFalse()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
deleted file mode 100644
index 1b515c6..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserTrackerTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 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.settings;
-
-import android.content.Intent;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Testing functionality of the current user tracker
- */
-@SmallTest
-public class CurrentUserTrackerTest extends SysuiTestCase {
-
- private CurrentUserTracker mTracker;
- private CurrentUserTracker.UserReceiver mReceiver;
- @Mock
- private BroadcastDispatcher mBroadcastDispatcher;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mReceiver = new CurrentUserTracker.UserReceiver(mBroadcastDispatcher);
- mTracker = new CurrentUserTracker(mReceiver) {
- @Override
- public void onUserSwitched(int newUserId) {
- stopTracking();
- }
- };
- }
-
- @Test
- public void testBroadCastDoesntCrashOnConcurrentModification() {
- mTracker.startTracking();
- CurrentUserTracker secondTracker = new CurrentUserTracker(mReceiver) {
- @Override
- public void onUserSwitched(int newUserId) {
- stopTracking();
- }
- };
- secondTracker.startTracking();
- triggerUserSwitch();
- }
- /**
- * Simulates a user switch event.
- */
- private void triggerUserSwitch() {
- Intent intent = new Intent(Intent.ACTION_USER_SWITCHED);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, 1);
- mReceiver.onReceive(getContext(), intent);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
index 6d9b01e..020a866 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
@@ -50,24 +50,20 @@
lateinit var userFileManager: UserFileManagerImpl
lateinit var backgroundExecutor: FakeExecutor
- @Mock
- lateinit var userManager: UserManager
- @Mock
- lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock lateinit var userManager: UserManager
+ @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
backgroundExecutor = FakeExecutor(FakeSystemClock())
- userFileManager = UserFileManagerImpl(context, userManager,
- broadcastDispatcher, backgroundExecutor)
+ userFileManager =
+ UserFileManagerImpl(context, userManager, broadcastDispatcher, backgroundExecutor)
}
@After
fun end() {
- val dir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID)
+ val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID)
dir.deleteRecursively()
}
@@ -82,13 +78,14 @@
@Test
fun testGetSharedPreferences() {
val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11)
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- UserFileManagerImpl.SHARED_PREFS,
- TEST_FILE_NAME
- )
+ val secondaryUserDir =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ UserFileManagerImpl.SHARED_PREFS,
+ TEST_FILE_NAME
+ )
assertThat(secondarySharedPref).isNotNull()
assertThat(secondaryUserDir.exists())
@@ -101,32 +98,35 @@
val userFileManager = spy(userFileManager)
userFileManager.start()
verify(userFileManager).clearDeletedUserData()
- verify(broadcastDispatcher).registerReceiver(any(BroadcastReceiver::class.java),
- any(IntentFilter::class.java),
- any(Executor::class.java), isNull(), eq(Context.RECEIVER_EXPORTED), isNull())
+ verify(broadcastDispatcher)
+ .registerReceiver(
+ any(BroadcastReceiver::class.java),
+ any(IntentFilter::class.java),
+ any(Executor::class.java),
+ isNull(),
+ eq(Context.RECEIVER_EXPORTED),
+ isNull()
+ )
}
@Test
fun testClearDeletedUserData() {
- val dir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files"
- )
+ val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID, "11", "files")
dir.mkdirs()
- val file = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files",
- TEST_FILE_NAME
- )
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- )
+ val file =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
+ val secondaryUserDir =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ )
file.createNewFile()
assertThat(secondaryUserDir.exists()).isTrue()
assertThat(file.exists()).isTrue()
@@ -139,15 +139,16 @@
@Test
fun testEnsureParentDirExists() {
- val file = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files",
- TEST_FILE_NAME
- )
+ val file =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
assertThat(file.parentFile.exists()).isFalse()
- userFileManager.ensureParentDirExists(file)
+ UserFileManagerImpl.ensureParentDirExists(file)
assertThat(file.parentFile.exists()).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 1130bda..9d1802a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -28,9 +28,10 @@
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -45,7 +46,9 @@
@TestableLooper.RunWithLooper
class BrightnessDialogTest : SysuiTestCase() {
+ @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var brightnessSliderControllerFactory: BrightnessSliderController.Factory
+ @Mock private lateinit var mainExecutor: Executor
@Mock private lateinit var backgroundHandler: Handler
@Mock private lateinit var brightnessSliderController: BrightnessSliderController
@@ -56,8 +59,9 @@
object : SingleActivityFactory<TestDialog>(TestDialog::class.java) {
override fun create(intent: Intent?): TestDialog {
return TestDialog(
- fakeBroadcastDispatcher,
+ userTracker,
brightnessSliderControllerFactory,
+ mainExecutor,
backgroundHandler
)
}
@@ -100,8 +104,15 @@
}
class TestDialog(
- broadcastDispatcher: BroadcastDispatcher,
+ userTracker: UserTracker,
brightnessSliderControllerFactory: BrightnessSliderController.Factory,
+ mainExecutor: Executor,
backgroundHandler: Handler
- ) : BrightnessDialog(broadcastDispatcher, brightnessSliderControllerFactory, backgroundHandler)
+ ) :
+ BrightnessDialog(
+ userTracker,
+ brightnessSliderControllerFactory,
+ mainExecutor,
+ backgroundHandler
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index 0ce9056..9c36be6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -24,6 +24,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
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
@@ -42,7 +43,7 @@
load(context, context.resources.getXml(R.xml.qqs_header))
}
qsConstraint = ConstraintSet().apply {
- load(context, context.resources.getXml(R.xml.qs_header_new))
+ load(context, context.resources.getXml(R.xml.qs_header))
}
largeScreenConstraint = ConstraintSet().apply {
load(context, context.resources.getXml(R.xml.large_screen_shade_header))
@@ -320,6 +321,44 @@
assertThat(changes.largeScreenConstraintsChanges).isNull()
}
+ @Test
+ fun testRelevantViewsAreNotMatchConstraints() {
+ val views = mapOf(
+ R.id.clock to "clock",
+ R.id.date to "date",
+ R.id.statusIcons to "icons",
+ R.id.privacy_container to "privacy",
+ R.id.carrier_group to "carriers",
+ R.id.batteryRemainingIcon to "battery",
+ )
+ views.forEach { (id, name) ->
+ assertWithMessage("$name has 0 height in qqs")
+ .that(qqsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+ assertWithMessage("$name has 0 width in qqs")
+ .that(qqsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+ assertWithMessage("$name has 0 height in qs")
+ .that(qsConstraint.getConstraint(id).layout.mHeight).isNotEqualTo(0)
+ assertWithMessage("$name has 0 width in qs")
+ .that(qsConstraint.getConstraint(id).layout.mWidth).isNotEqualTo(0)
+ }
+ }
+
+ @Test
+ fun testEmptyCutoutDateIconsAreConstrainedWidth() {
+ CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
+
+ assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+ assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+ }
+
+ @Test
+ fun testCenterCutoutDateIconsAreConstrainedWidth() {
+ CombinedShadeHeadersConstraintManagerImpl.centerCutoutConstraints(false, 10)()
+
+ assertThat(qqsConstraint.getConstraint(R.id.date).layout.constrainedWidth).isTrue()
+ assertThat(qqsConstraint.getConstraint(R.id.statusIcons).layout.constrainedWidth).isTrue()
+ }
+
private operator fun ConstraintsChanges.invoke() {
qqsConstraintsChanges?.invoke(qqsConstraint)
qsConstraintsChanges?.invoke(qsConstraint)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index 14a3bc1..858d0e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -35,6 +35,8 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -50,10 +52,12 @@
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.VariableDateView
import com.android.systemui.statusbar.policy.VariableDateViewController
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -104,7 +108,7 @@
@Mock
private lateinit var featureFlags: FeatureFlags
@Mock
- private lateinit var clock: TextView
+ private lateinit var clock: Clock
@Mock
private lateinit var date: VariableDateView
@Mock
@@ -138,6 +142,7 @@
private lateinit var qsConstraints: ConstraintSet
@Mock
private lateinit var largeScreenConstraints: ConstraintSet
+ @Mock private lateinit var demoModeController: DemoModeController
@JvmField @Rule
val mockitoRule = MockitoJUnit.rule()
@@ -146,10 +151,12 @@
private lateinit var controller: LargeScreenShadeHeaderController
private lateinit var carrierIconSlots: List<String>
private val configurationController = FakeConfigurationController()
+ private lateinit var demoModeControllerCapture: ArgumentCaptor<DemoMode>
@Before
fun setUp() {
- whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+ demoModeControllerCapture = argumentCaptor<DemoMode>()
+ whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
whenever(clock.context).thenReturn(mockedContext)
whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
@@ -179,7 +186,6 @@
whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
- whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
setUpDefaultInsets()
setUpMotionLayout(view)
@@ -196,7 +202,8 @@
dumpManager,
featureFlags,
qsCarrierGroupControllerBuilder,
- combinedShadeHeadersConstraintManager
+ combinedShadeHeadersConstraintManager,
+ demoModeController
)
whenever(view.isAttachedToWindow).thenReturn(true)
controller.init()
@@ -212,7 +219,7 @@
assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
verify(qsConstraints).load(eq(context), capture(captor))
- assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header_new)
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header)
verify(largeScreenConstraints).load(eq(context), capture(captor))
assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
@@ -618,6 +625,21 @@
}
@Test
+ fun demoMode_attachDemoMode() {
+ verify(demoModeController).addCallback(capture(demoModeControllerCapture))
+ demoModeControllerCapture.value.onDemoModeStarted()
+ verify(clock).onDemoModeStarted()
+ }
+
+ @Test
+ fun demoMode_detachDemoMode() {
+ controller.simulateViewDetached()
+ verify(demoModeController).removeCallback(capture(demoModeControllerCapture))
+ demoModeControllerCapture.value.onDemoModeFinished()
+ verify(clock).onDemoModeFinished()
+ }
+
+ @Test
fun animateOutOnStartCustomizing() {
val animator = Mockito.mock(ViewPropertyAnimator::class.java, Answers.RETURNS_SELF)
val duration = 1000L
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 90ae693..b4c8f98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -13,6 +13,8 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -22,9 +24,12 @@
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.VariableDateViewController
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -52,7 +57,7 @@
@Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
@Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
@Mock private lateinit var featureFlags: FeatureFlags
- @Mock private lateinit var clock: TextView
+ @Mock private lateinit var clock: Clock
@Mock private lateinit var date: TextView
@Mock private lateinit var carrierGroup: QSCarrierGroup
@Mock private lateinit var batteryMeterView: BatteryMeterView
@@ -66,6 +71,7 @@
CombinedShadeHeadersConstraintManager
@Mock private lateinit var mockedContext: Context
+ @Mock private lateinit var demoModeController: DemoModeController
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
var viewVisibility = View.GONE
@@ -76,7 +82,7 @@
@Before
fun setup() {
- whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+ whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
whenever(clock.context).thenReturn(mockedContext)
whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
whenever(date.context).thenReturn(mockedContext)
@@ -111,8 +117,9 @@
dumpManager,
featureFlags,
qsCarrierGroupControllerBuilder,
- combinedShadeHeadersConstraintManager
- )
+ combinedShadeHeadersConstraintManager,
+ demoModeController
+ )
whenever(view.isAttachedToWindow).thenReturn(true)
mLargeScreenShadeHeaderController.init()
carrierIconSlots = listOf(
@@ -230,4 +237,21 @@
verify(animator).setInterpolator(Interpolators.ALPHA_IN)
verify(animator).start()
}
+
+ @Test
+ fun demoMode_attachDemoMode() {
+ val cb = argumentCaptor<DemoMode>()
+ verify(demoModeController).addCallback(capture(cb))
+ cb.value.onDemoModeStarted()
+ verify(clock).onDemoModeStarted()
+ }
+
+ @Test
+ fun demoMode_detachDemoMode() {
+ mLargeScreenShadeHeaderController.simulateViewDetached()
+ val cb = argumentCaptor<DemoMode>()
+ verify(demoModeController).removeCallback(capture(cb))
+ cb.value.onDemoModeFinished()
+ verify(clock).onDemoModeFinished()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 7d2251e..b6f74f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -131,8 +131,10 @@
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
@@ -198,6 +200,7 @@
@Mock private KeyguardBottomAreaView mQsFrame;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationShelfController mNotificationShelfController;
+ @Mock private NotificationGutsManager mGutsManager;
@Mock private KeyguardStatusBarView mKeyguardStatusBar;
@Mock private KeyguardUserSwitcherView mUserSwitcherView;
@Mock private ViewStub mUserSwitcherStubView;
@@ -381,7 +384,8 @@
mInteractionJankMonitor, mShadeExpansionStateManager),
mKeyguardBypassController,
mDozeParameters,
- mScreenOffAnimationController);
+ mScreenOffAnimationController,
+ mock(NotificationWakeUpCoordinatorLogger.class));
mConfigurationController = new ConfigurationControllerImpl(mContext);
PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
mContext,
@@ -453,6 +457,7 @@
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHierarchyManager,
mStatusBarKeyguardViewManager,
+ mGutsManager,
mNotificationsQSContainerController,
mNotificationStackScrollLayoutController,
mKeyguardStatusViewComponentFactory,
@@ -496,8 +501,18 @@
mDumpManager);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
+ null,
() -> {},
mNotificationShelfController);
+ mNotificationPanelViewController.setTrackingStartedListener(() -> {});
+ mNotificationPanelViewController.setOpenCloseListener(
+ new NotificationPanelViewController.OpenCloseListener() {
+ @Override
+ public void onClosingFinished() {}
+
+ @Override
+ public void onOpenStarted() {}
+ });
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
@@ -754,6 +769,8 @@
@Test
public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
+ mFalsingManager.setIsClassifierEnabled(true);
+ mFalsingManager.setIsFalseTouch(false);
// Start shade collapse with swipe up
onTouchEvent(MotionEvent.obtain(0L /* downTime */,
0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
@@ -826,7 +843,7 @@
public void handleTouchEventFromStatusBar_panelAndViewEnabled_viewReceivesEvent() {
when(mCommandQueue.panelsEnabled()).thenReturn(true);
when(mView.isEnabled()).thenReturn(true);
- MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
+ MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 2f, 0);
mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
@@ -834,6 +851,17 @@
}
@Test
+ public void handleTouchEventFromStatusBar_topEdgeTouch_viewNeverReceivesEvent() {
+ when(mCommandQueue.panelsEnabled()).thenReturn(true);
+ when(mView.isEnabled()).thenReturn(true);
+ MotionEvent event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0);
+
+ mNotificationPanelViewController.getStatusBarTouchEventHandler().handleTouchEvent(event);
+
+ verify(mView, never()).dispatchTouchEvent(event);
+ }
+
+ @Test
public void testA11y_initializeNode() {
AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
@@ -1119,6 +1147,19 @@
}
@Test
+ public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() {
+ enableSplitShade(true);
+ mStatusBarStateController.setState(SHADE);
+ mNotificationPanelViewController.setQsExpanded(true);
+
+ mStatusBarStateController.setState(KEYGUARD);
+
+
+ assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+ }
+
+ @Test
public void testSwitchesToCorrectClockInSinglePaneShade() {
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index db7e017..c3207c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.NotificationInsetsController
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -94,6 +95,8 @@
private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock
private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock
+ private lateinit var notificationInsetsController: NotificationInsetsController
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerContainer: ViewGroup
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -124,6 +127,7 @@
centralSurfaces,
notificationShadeWindowController,
keyguardUnlockAnimationController,
+ notificationInsetsController,
ambientState,
pulsingGestureListener,
featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index a6c80ab6..4bf00c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -91,6 +92,7 @@
@Mock private FeatureFlags mFeatureFlags;
@Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
@Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
+ @Mock private NotificationInsetsController mNotificationInsetsController;
@Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
mInteractionEventHandlerCaptor;
@@ -125,6 +127,7 @@
mCentralSurfaces,
mNotificationShadeWindowController,
mKeyguardUnlockAnimationController,
+ mNotificationInsetsController,
mAmbientState,
mPulsingGestureListener,
mFeatureFlags,
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 28bd26a..4d7741ad 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
@@ -28,7 +28,7 @@
import com.android.systemui.plugins.ClockMetadata
import com.android.systemui.plugins.ClockProviderPlugin
import com.android.systemui.plugins.PluginListener
-import com.android.systemui.shared.plugins.PluginManager
+import com.android.systemui.plugins.PluginManager
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import junit.framework.Assert.assertEquals
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 539a54b..a7588dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -43,6 +43,7 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.notNull
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -139,12 +140,19 @@
}
@Test
- fun defaultClock_events_onFontSettingChanged() {
+ fun defaultSmallClock_events_onFontSettingChanged() {
val clock = provider.createClock(DEFAULT_CLOCK_ID)
- clock.events.onFontSettingChanged()
+ clock.smallClock.events.onFontSettingChanged(100f)
- verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
- verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), anyFloat())
+ verify(mockSmallClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(100f))
+ }
+
+ @Test
+ fun defaultLargeClock_events_onFontSettingChanged() {
+ val clock = provider.createClock(DEFAULT_CLOCK_ID)
+ clock.largeClock.events.onFontSettingChanged(200f)
+
+ verify(mockLargeClockView).setTextSize(eq(TypedValue.COMPLEX_UNIT_PX), eq(200f))
verify(mockLargeClockView).setLayoutParams(any())
}
@@ -171,4 +179,12 @@
verify(mockSmallClockView, times(2)).refreshFormat()
verify(mockLargeClockView, times(2)).refreshFormat()
}
+
+ @Test
+ fun test_aodClock_always_whiteColor() {
+ val clock = provider.createClock(DEFAULT_CLOCK_ID)
+ clock.animations.doze(0.9f) // set AOD mode to active
+ clock.smallClock.events.onRegionDarknessChanged(true)
+ verify((clock.smallClock.view as AnimatableClockView), never()).animateAppearOnLockscreen()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index cf7f8dd..8aaa181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -117,13 +117,6 @@
}
@Test
- public void testCollapsePanels() {
- mCommandQueue.animateCollapsePanels();
- waitForIdleSync();
- verify(mCallbacks).animateCollapsePanels(eq(0), eq(false));
- }
-
- @Test
public void testExpandSettings() {
String panel = "some_panel";
mCommandQueue.animateExpandSettingsPanel(panel);
@@ -519,4 +512,12 @@
waitForIdleSync();
verify(mCallbacks).setNavigationBarLumaSamplingEnabled(eq(1), eq(true));
}
+
+ @Test
+ public void testShowRearDisplayDialog() {
+ final int currentBaseState = 1;
+ mCommandQueue.showRearDisplayDialog(currentBaseState);
+ waitForIdleSync();
+ verify(mCallbacks).showRearDisplayDialog(eq(currentBaseState));
+ }
}
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 797256f9..c280ec8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -32,12 +32,12 @@
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_TURNING_ON;
import static com.google.common.truth.Truth.assertThat;
@@ -87,6 +87,8 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TrustGrantFlags;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -271,7 +273,7 @@
mUserManager, mExecutor, mExecutor, mFalsingManager,
mAuthController, mLockPatternUtils, mScreenLifecycle,
mKeyguardBypassController, mAccessibilityManager,
- mFaceHelpMessageDeferral);
+ mFaceHelpMessageDeferral, mock(KeyguardLogger.class));
mController.init();
mController.setIndicationArea(mIndicationArea);
verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
@@ -828,31 +830,6 @@
}
@Test
- public void updateMonitor_listenerUpdatesIndication() {
- createController();
- String restingIndication = "Resting indication";
- reset(mKeyguardUpdateMonitor);
-
- mController.setVisible(true);
- verifyIndicationMessage(INDICATION_TYPE_USER_LOCKED,
- mContext.getString(com.android.internal.R.string.lockscreen_storage_locked));
-
- reset(mRotateTextViewController);
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
- when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
- mController.setRestingIndication(restingIndication);
- verifyHideIndication(INDICATION_TYPE_USER_LOCKED);
- verifyIndicationMessage(INDICATION_TYPE_RESTING, restingIndication);
-
- reset(mRotateTextViewController);
- reset(mKeyguardUpdateMonitor);
- when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
- mKeyguardStateControllerCallback.onUnlockedChanged();
- verifyIndicationMessage(INDICATION_TYPE_RESTING, restingIndication);
- }
-
- @Test
public void onRefreshBatteryInfo_computesChargingTime() throws RemoteException {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
@@ -1064,7 +1041,8 @@
// GIVEN a trust granted message but trust isn't granted
final String trustGrantedMsg = "testing trust granted message";
- mController.getKeyguardCallback().showTrustGrantedMessage(trustGrantedMsg);
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), trustGrantedMsg);
verifyHideIndication(INDICATION_TYPE_TRUST);
@@ -1088,7 +1066,8 @@
// WHEN the showTrustGranted method is called
final String trustGrantedMsg = "testing trust granted message";
- mController.getKeyguardCallback().showTrustGrantedMessage(trustGrantedMsg);
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), trustGrantedMsg);
// THEN verify the trust granted message shows
verifyIndicationMessage(
@@ -1105,7 +1084,8 @@
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
// WHEN the showTrustGranted method is called with a null message
- mController.getKeyguardCallback().showTrustGrantedMessage(null);
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), null);
// THEN verify the default trust granted message shows
verifyIndicationMessage(
@@ -1122,7 +1102,8 @@
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
// WHEN the showTrustGranted method is called with an EMPTY string
- mController.getKeyguardCallback().showTrustGrantedMessage("");
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), "");
// THEN verify NO trust message is shown
verifyNoMessage(INDICATION_TYPE_TRUST);
@@ -1484,6 +1465,44 @@
}
@Test
+ public void onFpLockoutStateChanged_whenFpIsLockedOut_showsPersistentMessage() {
+ createController();
+ mController.setVisible(true);
+ when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
+
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+
+ verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onFpLockoutStateChanged_whenFpIsNotLockedOut_showsPersistentMessage() {
+ createController();
+ mController.setVisible(true);
+ clearInvocations(mRotateTextViewController);
+ when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(false);
+
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+
+ verifyHideIndication(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE);
+ }
+
+ @Test
+ public void onVisibilityChange_showsPersistentMessage_ifFpIsLockedOut() {
+ createController();
+ mController.setVisible(false);
+ when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+ clearInvocations(mRotateTextViewController);
+
+ mController.setVisible(true);
+
+ verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
public void onBiometricError_whenFaceIsLocked_onMultipleLockOutErrors_showUnavailableMessage() {
createController();
onFaceLockoutError("first lockout");
@@ -1497,6 +1516,44 @@
mContext.getString(R.string.keyguard_face_unlock_unavailable));
}
+ @Test
+ public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsNotAvailable_showsMessage() {
+ createController();
+ screenIsTurningOn();
+ fingerprintUnlockIsNotPossible();
+
+ onFaceLockoutError("lockout error");
+ verifyNoMoreInteractions(mRotateTextViewController);
+
+ mScreenObserver.onScreenTurnedOn();
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ "lockout error");
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
+ createController();
+ screenIsTurningOn();
+ fingerprintUnlockIsPossible();
+
+ onFaceLockoutError("lockout error");
+ verifyNoMoreInteractions(mRotateTextViewController);
+
+ mScreenObserver.onScreenTurnedOn();
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ "lockout error");
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_suggest_fingerprint));
+ }
+
+ private void screenIsTurningOn() {
+ when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON);
+ }
+
private void sendUpdateDisclosureBroadcast() {
mBroadcastReceiver.onReceive(mContext, new Intent());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 5e11858..3412679 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -37,7 +37,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index bdafa48..452606d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar;
-import static android.content.Intent.ACTION_USER_SWITCHED;
-
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -34,7 +32,6 @@
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.os.Handler;
@@ -53,6 +50,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -78,6 +76,8 @@
private NotificationPresenter mPresenter;
@Mock
private UserManager mUserManager;
+ @Mock
+ private UserTracker mUserTracker;
// Dependency mocks:
@Mock
@@ -115,6 +115,7 @@
MockitoAnnotations.initMocks(this);
int currentUserId = ActivityManager.getCurrentUser();
+ when(mUserTracker.getUserId()).thenReturn(currentUserId);
mSettings = new FakeSettings();
mSettings.setUserId(ActivityManager.getCurrentUser());
mCurrentUser = new UserInfo(currentUserId, "", 0);
@@ -289,11 +290,9 @@
}
@Test
- public void testActionUserSwitchedCallsOnUserSwitched() {
- Intent intent = new Intent()
- .setAction(ACTION_USER_SWITCHED)
- .putExtra(Intent.EXTRA_USER_HANDLE, mSecondaryUser.id);
- mLockscreenUserManager.getBaseBroadcastReceiverForTest().onReceive(mContext, intent);
+ public void testUserSwitchedCallsOnUserSwitched() {
+ mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanged(mSecondaryUser.id,
+ mContext);
verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
}
@@ -344,6 +343,7 @@
mBroadcastDispatcher,
mDevicePolicyManager,
mUserManager,
+ mUserTracker,
(() -> mVisibilityProvider),
(() -> mNotifCollection),
mClickNotifier,
@@ -361,6 +361,10 @@
return mBaseBroadcastReceiver;
}
+ public UserTracker.Callback getUserTrackerCallbackForTest() {
+ return mUserChangedCallback;
+ }
+
public ContentObserver getLockscreenSettingsObserverForTest() {
return mLockscreenSettingsObserver;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 77b1e37..beaf300 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -131,7 +131,9 @@
@Test
fun setupListeners() {
- verify(dumpManager).registerDumpable(anyString(), eq(notificationShadeDepthController))
+ verify(dumpManager).registerCriticalDumpable(
+ anyString(), eq(notificationShadeDepthController)
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 9c870b5..faf4592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -71,6 +71,7 @@
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -116,6 +117,7 @@
protected TelephonyManager mMockTm;
protected TelephonyListenerManager mTelephonyListenerManager;
protected BroadcastDispatcher mMockBd;
+ protected UserTracker mUserTracker;
protected Config mConfig;
protected CallbackHandler mCallbackHandler;
protected SubscriptionDefaults mMockSubDefaults;
@@ -172,6 +174,7 @@
mMockSm = mock(SubscriptionManager.class);
mMockCm = mock(ConnectivityManager.class);
mMockBd = mock(BroadcastDispatcher.class);
+ mUserTracker = mock(UserTracker.class);
mMockNsm = mock(NetworkScoreManager.class);
mMockSubDefaults = mock(SubscriptionDefaults.class);
mCarrierConfigTracker = mock(CarrierConfigTracker.class);
@@ -246,6 +249,7 @@
mMockSubDefaults,
mMockProvisionController,
mMockBd,
+ mUserTracker,
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 1d11226..ca75a40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -154,6 +154,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index d5f5105..84c242c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -82,6 +82,7 @@
mMockSubDefaults,
mMockProvisionController,
mMockBd,
+ mUserTracker,
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
@@ -118,6 +119,7 @@
mMockSubDefaults,
mMockProvisionController,
mMockBd,
+ mUserTracker,
mDemoModeController,
mCarrierConfigTracker,
mWifiStatusTrackerFactory,
@@ -152,6 +154,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
@@ -189,6 +192,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
@@ -274,6 +278,7 @@
mMockSubDefaults,
mock(DeviceProvisionedController.class),
mMockBd,
+ mUserTracker,
mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 3b05321..94e3e6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -92,6 +92,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -741,22 +742,24 @@
@Test
public void testGroupChildrenAreDismissedLocallyWhenSummaryIsDismissed() {
// GIVEN a collection with two grouped notifs in it
- CollectionEvent notif0 = postNotif(
+ CollectionEvent groupNotif = postNotif(
buildNotif(TEST_PACKAGE, 0)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
- CollectionEvent notif1 = postNotif(
+ CollectionEvent childNotif = postNotif(
buildNotif(TEST_PACKAGE, 1)
.setGroup(mContext, GROUP_1));
- NotificationEntry entry0 = mCollectionListener.getEntry(notif0.key);
- NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key);
+ NotificationEntry groupEntry = mCollectionListener.getEntry(groupNotif.key);
+ NotificationEntry childEntry = mCollectionListener.getEntry(childNotif.key);
+ ExpandableNotificationRow childRow = mock(ExpandableNotificationRow.class);
+ childEntry.setRow(childRow);
// WHEN the summary is dismissed
- mCollection.dismissNotification(entry0, defaultStats(entry0));
+ mCollection.dismissNotification(groupEntry, defaultStats(groupEntry));
// THEN all members of the group are marked as dismissed locally
- assertEquals(DISMISSED, entry0.getDismissState());
- assertEquals(PARENT_DISMISSED, entry1.getDismissState());
+ assertEquals(DISMISSED, groupEntry.getDismissState());
+ assertEquals(PARENT_DISMISSED, childEntry.getDismissState());
}
@Test
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 7e2e6f6..bdedd24 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
@@ -13,57 +13,55 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.collection.coordinator
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
-import java.util.function.Consumer
-import org.junit.Before
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
class KeyguardCoordinatorTest : SysuiTestCase() {
- private val notifPipeline: NotifPipeline = mock()
+
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val notifPipelineFlags: NotifPipelineFlags = mock()
+ private val notifPipeline: NotifPipeline = mock()
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
private val statusBarStateController: StatusBarStateController = mock()
- private lateinit var onStateChangeListener: Consumer<String>
- private lateinit var keyguardFilter: NotifFilter
-
- @Before
- fun setup() {
- val keyguardCoordinator = KeyguardCoordinator(
- keyguardNotifVisibilityProvider,
- sectionHeaderVisibilityProvider,
- statusBarStateController
- )
- keyguardCoordinator.attach(notifPipeline)
- onStateChangeListener = withArgCaptor {
- verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
- }
- keyguardFilter = withArgCaptor {
- verify(notifPipeline).addFinalizeFilter(capture())
- }
- }
-
@Test
- fun testSetSectionHeadersVisibleInShade() {
+ fun testSetSectionHeadersVisibleInShade() = runKeyguardCoordinatorTest {
clearInvocations(sectionHeaderVisibilityProvider)
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
onStateChangeListener.accept("state change")
@@ -71,10 +69,176 @@
}
@Test
- fun testSetSectionHeadersNotVisibleOnKeyguard() {
+ fun testSetSectionHeadersNotVisibleOnKeyguard() = runKeyguardCoordinatorTest {
clearInvocations(sectionHeaderVisibilityProvider)
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
onStateChangeListener.accept("state change")
verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false)
}
+
+ @Test
+ fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing, and a notification is present
+ keyguardRepository.setKeyguardShowing(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // 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 unseenFilterAllowsNewNotif() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, no notifications present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ // WHEN: A new notification is posted
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // THEN: The notification is recognized as "unseen" and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenFilterSeenGroupSummaryWithUnseenChild() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing, and a notification is present
+ keyguardRepository.setKeyguardShowing(false)
+ runKeyguardCoordinatorTest {
+ // WHEN: A new notification is posted
+ val fakeSummary = NotificationEntryBuilder().build()
+ val fakeChild = NotificationEntryBuilder()
+ .setGroup(context, "group")
+ .setGroupSummary(context, false)
+ .build()
+ GroupEntryBuilder()
+ .setSummary(fakeSummary)
+ .addChild(fakeChild)
+ .build()
+
+ collectionListener.onEntryAdded(fakeSummary)
+ collectionListener.onEntryAdded(fakeChild)
+
+ // WHEN: Keyguard is now showing, both notifications are marked as seen
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // WHEN: The child notification is now unseen
+ collectionListener.onEntryUpdated(fakeChild)
+
+ // THEN: The summary is not filtered out, because the child is unseen
+ assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: Keyguard is no longer showing for 5 seconds
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+ testScheduler.advanceTimeBy(5.seconds.inWholeMilliseconds)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is now recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+ }
+
+ @Test
+ fun unseenNotificationIsNotMarkedAsSeenIfTimeThresholdNotMet() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: Keyguard is no longer showing for <5 seconds
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+ testScheduler.advanceTimeBy(1.seconds.inWholeMilliseconds)
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is not recognized as "seen" and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ private fun runKeyguardCoordinatorTest(
+ testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
+ ) {
+ val testScope = TestScope(UnconfinedTestDispatcher())
+ val keyguardCoordinator =
+ KeyguardCoordinator(
+ keyguardNotifVisibilityProvider,
+ keyguardRepository,
+ notifPipelineFlags,
+ testScope.backgroundScope,
+ sectionHeaderVisibilityProvider,
+ statusBarStateController,
+ )
+ keyguardCoordinator.attach(notifPipeline)
+ KeyguardCoordinatorTestScope(keyguardCoordinator, testScope).run {
+ testScheduler.advanceUntilIdle()
+ testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) { testBlock() }
+ }
+ }
+
+ private inner class KeyguardCoordinatorTestScope(
+ private val keyguardCoordinator: KeyguardCoordinator,
+ private val scope: TestScope,
+ ) : CoroutineScope by scope {
+ val testScheduler: TestCoroutineScheduler
+ get() = scope.testScheduler
+
+ val onStateChangeListener: Consumer<String> =
+ withArgCaptor {
+ verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+ }
+
+ val unseenFilter: NotifFilter
+ get() = keyguardCoordinator.unseenNotifFilter
+
+ // TODO(254647461): Remove lazy once Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD is enabled and
+ // removed
+ val collectionListener: NotifCollectionListener by lazy {
+ withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) }
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index b4a5f5c..e488f39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -38,6 +40,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeStateEvents;
import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -73,6 +76,7 @@
@Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
@Mock private HeadsUpManager mHeadsUpManager;
@Mock private ShadeStateEvents mShadeStateEvents;
+ @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
@Mock private VisualStabilityProvider mVisualStabilityProvider;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@@ -100,6 +104,7 @@
mHeadsUpManager,
mShadeStateEvents,
mStatusBarStateController,
+ mVisibilityLocationProvider,
mVisualStabilityProvider,
mWakefulnessLifecycle);
@@ -355,6 +360,38 @@
}
@Test
+ public void testMovingVisibleHeadsUpNotAllowed() {
+ // GIVEN stability enforcing conditions
+ setPanelExpanded(true);
+ setSleepy(false);
+
+ // WHEN a notification is alerting and visible
+ when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+ .thenReturn(true);
+
+ // VERIFY the notification cannot be reordered
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isFalse();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isFalse();
+ }
+
+ @Test
+ public void testMovingInvisibleHeadsUpAllowed() {
+ // GIVEN stability enforcing conditions
+ setPanelExpanded(true);
+ setSleepy(false);
+
+ // WHEN a notification is alerting but not visible
+ when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+ .thenReturn(false);
+
+ // VERIFY the notification can be reordered
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isTrue();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isTrue();
+ }
+
+ @Test
public void testNeverSuppressedChanges_noInvalidationCalled() {
// GIVEN no notifications are currently being suppressed from grouping nor being sorted
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
index 15cf17d..6167b46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.testing.AndroidTestingRunner
import android.view.View
+import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -26,6 +27,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.matches
+import org.mockito.Mockito.verify
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -124,6 +129,64 @@
Assert.assertNull(controller3.view.parent)
Assert.assertNull(controller4.view.parent)
Assert.assertNull(controller5.view.parent)
+ verifyDetachingChildLogged(controller3, oldParent = controller2)
+ verifyDetachingChildLogged(controller4, oldParent = controller2)
+ verifyDetachingChildLogged(controller5, oldParent = controller2)
+ }
+
+ @Test
+ fun testRemovedGroupsWithKeepInParentAreKeptTogether() {
+ // GIVEN a preexisting tree with a group
+ // AND the group children supports keepInParent
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4), node(controller5))
+ )
+ controller3.supportsKeepInParent = true
+ controller4.supportsKeepInParent = true
+ controller5.supportsKeepInParent = true
+
+ // WHEN the new spec removes the entire group
+ applySpecAndCheck(node(controller1))
+
+ // THEN the group children are still attached to their parent
+ Assert.assertEquals(controller2.view, controller3.view.parent)
+ Assert.assertEquals(controller2.view, controller4.view.parent)
+ Assert.assertEquals(controller2.view, controller5.view.parent)
+ verifySkipDetachingChildLogged(controller3, parent = controller2)
+ verifySkipDetachingChildLogged(controller4, parent = controller2)
+ verifySkipDetachingChildLogged(controller5, parent = controller2)
+ }
+
+ @Test
+ fun testReuseRemovedGroupsWithKeepInParent() {
+ // GIVEN a preexisting tree with a dismissed group
+ // AND the group children supports keepInParent
+ controller3.supportsKeepInParent = true
+ controller4.supportsKeepInParent = true
+ controller5.supportsKeepInParent = true
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2, node(controller3), node(controller4), node(controller5))
+ )
+ applySpecAndCheck(node(controller1))
+
+ // WHEN a new spec is applied which reuses the dismissed views
+ applySpecAndCheck(
+ node(controller1),
+ node(controller2),
+ node(controller3),
+ node(controller4),
+ node(controller5)
+ )
+
+ // THEN the dismissed views can be reused
+ Assert.assertEquals(rootController.view, controller3.view.parent)
+ Assert.assertEquals(rootController.view, controller4.view.parent)
+ Assert.assertEquals(rootController.view, controller5.view.parent)
+ verifyDetachingChildLogged(controller3, oldParent = null)
+ verifyDetachingChildLogged(controller4, oldParent = null)
+ verifyDetachingChildLogged(controller5, oldParent = null)
}
@Test
@@ -184,7 +247,30 @@
}
}
+ private fun verifySkipDetachingChildLogged(child: NodeController, parent: NodeController) {
+ verify(logger)
+ .logSkipDetachingChild(
+ key = matches(child.nodeLabel),
+ parentKey = matches(parent.nodeLabel),
+ anyBoolean(),
+ anyBoolean()
+ )
+ }
+
+ private fun verifyDetachingChildLogged(child: NodeController, oldParent: NodeController?) {
+ verify(logger)
+ .logDetachingChild(
+ key = matches(child.nodeLabel),
+ isTransfer = anyBoolean(),
+ isParentRemoved = anyBoolean(),
+ oldParent = oldParent?.let { matches(it.nodeLabel) } ?: isNull(),
+ newParent = isNull()
+ )
+ }
+
private class FakeController(context: Context, label: String) : NodeController {
+ var supportsKeepInParent: Boolean = false
+
override val view: FrameLayout = FrameLayout(context)
override val nodeLabel: String = label
override fun getChildCount(): Int = view.childCount
@@ -209,6 +295,22 @@
override fun onViewAdded() {}
override fun onViewMoved() {}
override fun onViewRemoved() {}
+ override fun offerToKeepInParentForAnimation(): Boolean {
+ return supportsKeepInParent
+ }
+
+ override fun removeFromParentIfKeptForAnimation(): Boolean {
+ if (supportsKeepInParent) {
+ (view.parent as? ViewGroup)?.removeView(view)
+ return true
+ }
+
+ return false
+ }
+
+ override fun resetKeepInParentForAnimation() {
+ supportsKeepInParent = false
+ }
}
private class SpecBuilder(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 8b7b4de..6bd3f7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -26,22 +26,17 @@
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry;
-import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
@@ -54,10 +49,10 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.CoreStartable;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -97,7 +92,7 @@
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private HighPriorityProvider mHighPriorityProvider;
@Mock private SysuiStatusBarStateController mStatusBarStateController;
- @Mock private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock private UserTracker mUserTracker;
private final FakeSettings mFakeSettings = new FakeSettings();
private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
@@ -117,7 +112,7 @@
mKeyguardUpdateMonitor,
mHighPriorityProvider,
mStatusBarStateController,
- mBroadcastDispatcher,
+ mUserTracker,
mFakeSettings,
mFakeSettings);
mKeyguardNotificationVisibilityProvider = component.getProvider();
@@ -205,23 +200,19 @@
}
@Test
- public void notifyListeners_onReceiveUserSwitchBroadcast() {
- ArgumentCaptor<BroadcastReceiver> callbackCaptor =
- ArgumentCaptor.forClass(BroadcastReceiver.class);
- verify(mBroadcastDispatcher).registerReceiver(
+ public void notifyListeners_onReceiveUserSwitchCallback() {
+ ArgumentCaptor<UserTracker.Callback> callbackCaptor =
+ ArgumentCaptor.forClass(UserTracker.Callback.class);
+ verify(mUserTracker).addCallback(
callbackCaptor.capture(),
- argThat(intentFilter -> intentFilter.hasAction(Intent.ACTION_USER_SWITCHED)),
- isNull(),
- isNull(),
- eq(Context.RECEIVER_EXPORTED),
- isNull());
- BroadcastReceiver callback = callbackCaptor.getValue();
+ any());
+ UserTracker.Callback callback = callbackCaptor.getValue();
Consumer<String> listener = mock(Consumer.class);
mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener);
when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
- callback.onReceive(mContext, new Intent(Intent.ACTION_USER_SWITCHED));
+ callback.onUserChanged(CURR_USER_ID, mContext);
verify(listener).accept(anyString());
}
@@ -619,7 +610,7 @@
@BindsInstance KeyguardUpdateMonitor keyguardUpdateMonitor,
@BindsInstance HighPriorityProvider highPriorityProvider,
@BindsInstance SysuiStatusBarStateController statusBarStateController,
- @BindsInstance BroadcastDispatcher broadcastDispatcher,
+ @BindsInstance UserTracker userTracker,
@BindsInstance SecureSettings secureSettings,
@BindsInstance GlobalSettings globalSettings
);
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 ea311da..21aae00 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
@@ -17,6 +17,7 @@
import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.GROUP_ALERT_SUMMARY;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -33,6 +34,8 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -390,6 +393,127 @@
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
}
+ private long makeWhenHoursAgo(long hoursAgo) {
+ return System.currentTimeMillis() - (1000 * 60 * 60 * hoursAgo);
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_flagDisabled() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(false);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = makeWhenHoursAgo(25);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_whenNow() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_whenRecent() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = makeWhenHoursAgo(13);
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_whenZero() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = 0L;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(0L), anyLong(),
+ eq("when <= 0"));
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_whenNegative() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = -1L;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(-1L), anyLong(),
+ eq("when <= 0"));
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_hasFullScreenIntent() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+ long when = makeWhenHoursAgo(25);
+
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silent= */ false);
+ entry.getSbn().getNotification().when = when;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(when), anyLong(),
+ eq("full-screen intent"));
+ }
+
+ @Test
+ public void testShouldHeadsUp_oldWhen_isForegroundService() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+ long when = makeWhenHoursAgo(25);
+
+ NotificationEntry entry = createFgsNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = when;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+
+ verify(mLogger, never()).logNoHeadsUpOldWhen(any(), anyLong(), anyLong());
+ verify(mLogger).logMaybeHeadsUpDespiteOldWhen(eq(entry), eq(when), anyLong(),
+ eq("foreground service"));
+ }
+
+ @Test
+ public void testShouldNotHeadsUp_oldWhen() throws Exception {
+ ensureStateForHeadsUpWhenAwake();
+ when(mFlags.isNoHunForOldWhenEnabled()).thenReturn(true);
+ long when = makeWhenHoursAgo(25);
+
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ entry.getSbn().getNotification().when = when;
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+
+ verify(mLogger).logNoHeadsUpOldWhen(eq(entry), eq(when), anyLong());
+ verify(mLogger, never()).logMaybeHeadsUpDespiteOldWhen(any(), anyLong(), anyLong(), any());
+ }
+
@Test
public void testShouldNotFullScreen_notPendingIntent_withStrictFlag() throws Exception {
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
@@ -763,6 +887,16 @@
return createNotification(importance, n);
}
+ private NotificationEntry createFgsNotification(int importance) {
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setContentTitle("title")
+ .setContentText("content text")
+ .setFlag(FLAG_FOREGROUND_SERVICE, true)
+ .build();
+
+ return createNotification(importance, n);
+ }
+
private final NotificationInterruptSuppressor
mSuppressAwakeHeadsUp =
new NotificationInterruptSuppressor() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
new file mode 100644
index 0000000..2d23f3c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+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.classifier.FalsingCollector
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.PluginManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.NotificationMediaManager
+import com.android.systemui.statusbar.SmartReplyController
+import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.logging.NotificationLogger
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.SmartReplyConstants
+import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
+import junit.framework.Assert
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ExpandableNotificationRowControllerTest : SysuiTestCase() {
+
+ private val appName = "MyApp"
+ private val notifKey = "MyNotifKey"
+
+ private val view: ExpandableNotificationRow = mock()
+ private val activableNotificationViewController: ActivatableNotificationViewController = mock()
+ private val rivSubComponentFactory: RemoteInputViewSubcomponent.Factory = mock()
+ private val metricsLogger: MetricsLogger = mock()
+ private val logBufferLogger: NotificationRowLogger = mock()
+ private val listContainer: NotificationListContainer = mock()
+ private val mediaManager: NotificationMediaManager = mock()
+ private val smartReplyConstants: SmartReplyConstants = mock()
+ private val smartReplyController: SmartReplyController = mock()
+ private val pluginManager: PluginManager = mock()
+ private val systemClock: SystemClock = mock()
+ private val keyguardBypassController: KeyguardBypassController = mock()
+ private val groupMembershipManager: GroupMembershipManager = mock()
+ private val groupExpansionManager: GroupExpansionManager = mock()
+ private val rowContentBindStage: RowContentBindStage = mock()
+ private val notifLogger: NotificationLogger = mock()
+ private val headsUpManager: HeadsUpManager = mock()
+ private val onExpandClickListener: ExpandableNotificationRow.OnExpandClickListener = mock()
+ private val statusBarStateController: StatusBarStateController = mock()
+ private val gutsManager: NotificationGutsManager = mock()
+ private val onUserInteractionCallback: OnUserInteractionCallback = mock()
+ private val falsingManager: FalsingManager = mock()
+ private val falsingCollector: FalsingCollector = mock()
+ private val featureFlags: FeatureFlags = mock()
+ private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
+ private val bubblesManager: BubblesManager = mock()
+ private val dragController: ExpandableNotificationRowDragController = mock()
+ private lateinit var controller: ExpandableNotificationRowController
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ controller =
+ ExpandableNotificationRowController(
+ view,
+ activableNotificationViewController,
+ rivSubComponentFactory,
+ metricsLogger,
+ logBufferLogger,
+ listContainer,
+ mediaManager,
+ smartReplyConstants,
+ smartReplyController,
+ pluginManager,
+ systemClock,
+ appName,
+ notifKey,
+ keyguardBypassController,
+ groupMembershipManager,
+ groupExpansionManager,
+ rowContentBindStage,
+ notifLogger,
+ headsUpManager,
+ onExpandClickListener,
+ statusBarStateController,
+ gutsManager,
+ /*allowLongPress=*/ false,
+ onUserInteractionCallback,
+ falsingManager,
+ falsingCollector,
+ featureFlags,
+ peopleNotificationIdentifier,
+ Optional.of(bubblesManager),
+ dragController
+ )
+ }
+
+ @After
+ fun tearDown() {
+ disallowTestableLooperAsMainThread()
+ }
+
+ @Test
+ fun offerKeepInParent_parentDismissed() {
+ whenever(featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION))
+ .thenReturn(true)
+ whenever(view.isParentDismissed).thenReturn(true)
+
+ Assert.assertTrue(controller.offerToKeepInParentForAnimation())
+ Mockito.verify(view).setKeepInParentForDismissAnimation(true)
+ }
+
+ @Test
+ fun offerKeepInParent_parentNotDismissed() {
+ whenever(featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_DISMISSAL_ANIMATION))
+ .thenReturn(true)
+
+ Assert.assertFalse(controller.offerToKeepInParentForAnimation())
+ Mockito.verify(view, never()).setKeepInParentForDismissAnimation(anyBoolean())
+ }
+
+ @Test
+ fun removeFromParent_keptForAnimation() {
+ val parentView: ExpandableNotificationRow = mock()
+ whenever(view.notificationParent).thenReturn(parentView)
+ whenever(view.keepInParentForDismissAnimation()).thenReturn(true)
+
+ Assert.assertTrue(controller.removeFromParentIfKeptForAnimation())
+ Mockito.verify(parentView).removeChildNotification(view)
+ }
+
+ @Test
+ fun removeFromParent_notKeptForAnimation() {
+ val parentView: ExpandableNotificationRow = mock()
+ whenever(view.notificationParent).thenReturn(parentView)
+
+ Assert.assertFalse(controller.removeFromParentIfKeptForAnimation())
+ Mockito.verifyNoMoreInteractions(parentView)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index ed2afe7..915924f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -41,7 +41,6 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
-import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 12cc114..ee8db18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -38,6 +38,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Notification;
@@ -458,4 +459,79 @@
verify(mNotificationTestHelper.mOnUserInteractionCallback, never())
.registerFutureDismissal(any(), anyInt());
}
+
+ @Test
+ public void testAddChildNotification() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
+ ExpandableNotificationRow child = mNotificationTestHelper.createRow();
+
+ group.addChildNotification(child);
+
+ Assert.assertEquals(child, group.getChildNotificationAt(0));
+ Assert.assertEquals(group, child.getNotificationParent());
+ Assert.assertTrue(child.isChildInGroup());
+ }
+
+ @Test
+ public void testAddChildNotification_childSkipped() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(0);
+ ExpandableNotificationRow child = mNotificationTestHelper.createRow();
+ child.setKeepInParentForDismissAnimation(true);
+
+ group.addChildNotification(child);
+
+ Assert.assertTrue(group.getAttachedChildren().isEmpty());
+ Assert.assertNotEquals(group, child.getNotificationParent());
+ verify(mNotificationTestHelper.getMockLogger()).logSkipAttachingKeepInParentChild(
+ /*child=*/ child.getEntry(),
+ /*newParent=*/ group.getEntry()
+ );
+ }
+
+ @Test
+ public void testRemoveChildNotification() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+ ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+ child.setKeepInParentForDismissAnimation(true);
+
+ group.removeChildNotification(child);
+
+ Assert.assertNull(child.getParent());
+ Assert.assertNull(child.getNotificationParent());
+ Assert.assertFalse(child.keepInParentForDismissAnimation());
+ verifyNoMoreInteractions(mNotificationTestHelper.getMockLogger());
+ }
+
+ @Test
+ public void testRemoveChildrenWithKeepInParent_removesChildWithKeepInParent() throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+ ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+ child.setKeepInParentForDismissAnimation(true);
+
+ group.removeChildrenWithKeepInParent();
+
+ Assert.assertNull(child.getParent());
+ Assert.assertNull(child.getNotificationParent());
+ Assert.assertFalse(child.keepInParentForDismissAnimation());
+ verify(mNotificationTestHelper.getMockLogger()).logKeepInParentChildDetached(
+ /*child=*/ child.getEntry(),
+ /*oldParent=*/ group.getEntry()
+ );
+ }
+
+ @Test
+ public void testRemoveChildrenWithKeepInParent_skipsChildrenWithoutKeepInParent()
+ throws Exception {
+ ExpandableNotificationRow group = mNotificationTestHelper.createGroup(1);
+ ExpandableNotificationRow child = group.getAttachedChildren().get(0);
+
+ group.removeChildrenWithKeepInParent();
+
+ Assert.assertEquals(group, child.getNotificationParent());
+ Assert.assertFalse(child.keepInParentForDismissAnimation());
+ verify(mNotificationTestHelper.getMockLogger(), never()).logKeepInParentChildDetached(
+ /*child=*/ any(),
+ /*oldParent=*/ any()
+ );
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index ab4ae6a..fb31bef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -73,7 +73,7 @@
import com.android.systemui.statusbar.notification.icon.IconBuilder;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -116,6 +116,7 @@
private final Context mContext;
private final TestableLooper mTestLooper;
private int mId;
+ private final ExpandableNotificationRowLogger mMockLogger;
private final GroupMembershipManager mGroupMembershipManager;
private final GroupExpansionManager mGroupExpansionManager;
private ExpandableNotificationRow mRow;
@@ -139,6 +140,7 @@
dependency.injectMockDependency(NotificationMediaManager.class);
dependency.injectMockDependency(NotificationShadeWindowController.class);
dependency.injectMockDependency(MediaOutputDialogFactory.class);
+ mMockLogger = mock(ExpandableNotificationRowLogger.class);
mStatusBarStateController = mock(StatusBarStateController.class);
mGroupMembershipManager = mock(GroupMembershipManager.class);
mGroupExpansionManager = mock(GroupExpansionManager.class);
@@ -197,6 +199,10 @@
mDefaultInflationFlags = defaultInflationFlags;
}
+ public ExpandableNotificationRowLogger getMockLogger() {
+ return mMockLogger;
+ }
+
/**
* Creates a generic row with rounded border.
*
@@ -295,7 +301,8 @@
public ExpandableNotificationRow createBubble()
throws Exception {
Notification n = createNotification(false /* isGroupSummary */,
- null /* groupKey */, makeBubbleMetadata(null));
+ null /* groupKey */,
+ makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */));
n.flags |= FLAG_BUBBLE;
ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE,
mDefaultInflationFlags, IMPORTANCE_HIGH);
@@ -328,7 +335,8 @@
public ExpandableNotificationRow createBubbleInGroup()
throws Exception {
Notification n = createNotification(false /* isGroupSummary */,
- GROUP_KEY /* groupKey */, makeBubbleMetadata(null));
+ GROUP_KEY /* groupKey */,
+ makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */));
n.flags |= FLAG_BUBBLE;
ExpandableNotificationRow row = generateRow(n, PKG, UID, USER_HANDLE,
mDefaultInflationFlags, IMPORTANCE_HIGH);
@@ -344,7 +352,7 @@
* @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent}
*/
public NotificationEntry createBubble(@Nullable PendingIntent deleteIntent) {
- return createBubble(makeBubbleMetadata(deleteIntent), USER_HANDLE);
+ return createBubble(makeBubbleMetadata(deleteIntent, false /* autoExpand */), USER_HANDLE);
}
/**
@@ -353,7 +361,16 @@
* @param handle the user to associate with this bubble.
*/
public NotificationEntry createBubble(UserHandle handle) {
- return createBubble(makeBubbleMetadata(null), handle);
+ return createBubble(makeBubbleMetadata(null /* deleteIntent */, false /* autoExpand */),
+ handle);
+ }
+
+ /**
+ * Returns an {@link NotificationEntry} that should be shown as a auto-expanded bubble.
+ */
+ public NotificationEntry createAutoExpandedBubble() {
+ return createBubble(makeBubbleMetadata(null /* deleteIntent */, true /* autoExpand */),
+ USER_HANDLE);
}
/**
@@ -527,7 +544,7 @@
mock(RemoteInputViewSubcomponent.Factory.class),
APP_NAME,
entry.getKey(),
- mock(ExpansionLogger.class),
+ mMockLogger,
mock(KeyguardBypassController.class),
mGroupMembershipManager,
mGroupExpansionManager,
@@ -561,7 +578,7 @@
assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
}
- private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent) {
+ private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent, boolean autoExpand) {
Intent target = new Intent(mContext, BubblesTestActivity.class);
PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target,
PendingIntent.FLAG_MUTABLE);
@@ -570,6 +587,7 @@
Icon.createWithResource(mContext, R.drawable.android))
.setDeleteIntent(deleteIntent)
.setDesiredHeight(314)
+ .setAutoExpandBubble(autoExpand)
.build();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 90061b0..026c82e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -61,6 +61,7 @@
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -122,6 +123,7 @@
@Mock private UiEventLogger mUiEventLogger;
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
+ @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
@Mock private ShadeController mShadeController;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private StackStateLogger mStackLogger;
@@ -173,6 +175,7 @@
mShadeTransitionController,
mUiEventLogger,
mRemoteInputManager,
+ mVisibilityLocationProviderDelegator,
mShadeController,
mJankMonitor,
mStackLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 743e7d6..4d9db8c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -14,6 +14,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
@@ -31,10 +32,10 @@
private val hostView = FrameLayout(context)
private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
- private val notificationRow = mock(ExpandableNotificationRow::class.java)
- private val dumpManager = mock(DumpManager::class.java)
- private val mStatusBarKeyguardViewManager = mock(StatusBarKeyguardViewManager::class.java)
- private val notificationShelf = mock(NotificationShelf::class.java)
+ private val notificationRow = mock<ExpandableNotificationRow>()
+ private val dumpManager = mock<DumpManager>()
+ private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
+ private val notificationShelf = mock<NotificationShelf>()
private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
}
@@ -46,7 +47,7 @@
mStatusBarKeyguardViewManager
)
- private val testableResources = mContext.orCreateTestableResources
+ private val testableResources = mContext.getOrCreateTestableResources()
private fun px(@DimenRes id: Int): Float =
testableResources.resources.getDimensionPixelSize(id).toFloat()
@@ -98,7 +99,7 @@
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
val marginBottom =
- context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
+ context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY)
@@ -507,6 +508,192 @@
assertEquals(1f, currentRoundness)
}
+ @Test
+ fun shadeOpened_hunFullyOverlapsQqsPanel_hunShouldHaveFullShadow() {
+ // Given: shade is opened, yTranslation of HUN is 0,
+ // the height of HUN equals to the height of QQS Panel,
+ // and HUN fully overlaps with QQS Panel
+ ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+ px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView = createHunViewMock(
+ isShadeOpen = true,
+ fullyVisible = false,
+ headerVisibleAmount = 1f
+ )
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: full shadow would be applied
+ assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
+ }
+
+ @Test
+ fun shadeOpened_hunPartiallyOverlapsQQS_hunShouldHavePartialShadow() {
+ // Given: shade is opened, yTranslation of HUN is greater than 0,
+ // the height of HUN is equal to the height of QQS Panel,
+ // and HUN partially overlaps with QQS Panel
+ ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+ px(R.dimen.qqs_layout_padding_bottom)
+ val childHunView = createHunViewMock(
+ isShadeOpen = true,
+ fullyVisible = false,
+ headerVisibleAmount = 1f
+ )
+ // Use half of the HUN's height as overlap
+ childHunView.viewState.yTranslation = (childHunView.viewState.height + 1 shr 1).toFloat()
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should have shadow, but not as full size
+ assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
+ assertThat(childHunView.viewState.zTranslation)
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ }
+
+ @Test
+ fun shadeOpened_hunDoesNotOverlapQQS_hunShouldHaveNoShadow() {
+ // Given: shade is opened, yTranslation of HUN is equal to QQS Panel's height,
+ // the height of HUN is equal to the height of QQS Panel,
+ // and HUN doesn't overlap with QQS Panel
+ ambientState.stackTranslation = px(R.dimen.qqs_layout_margin_top) +
+ px(R.dimen.qqs_layout_padding_bottom)
+ // Mock the height of shade
+ ambientState.setLayoutMinHeight(1000)
+ val childHunView = createHunViewMock(
+ isShadeOpen = true,
+ fullyVisible = true,
+ headerVisibleAmount = 1f
+ )
+ // HUN doesn't overlap with QQS Panel
+ childHunView.viewState.yTranslation = ambientState.topPadding +
+ ambientState.stackTranslation
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should not have shadow
+ assertEquals(0f, childHunView.viewState.zTranslation)
+ }
+
+ @Test
+ fun shadeClosed_hunShouldHaveFullShadow() {
+ // Given: shade is closed, ambientState.stackTranslation == -ambientState.topPadding,
+ // the height of HUN is equal to the height of QQS Panel,
+ ambientState.stackTranslation = -ambientState.topPadding
+ // Mock the height of shade
+ ambientState.setLayoutMinHeight(1000)
+ val childHunView = createHunViewMock(
+ isShadeOpen = false,
+ fullyVisible = false,
+ headerVisibleAmount = 0f
+ )
+ childHunView.viewState.yTranslation = 0f
+ // Shade is closed, thus childHunView's headerVisibleAmount is 0
+ childHunView.headerVisibleAmount = 0f
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should have full shadow
+ assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
+ }
+
+ @Test
+ fun draggingHunToOpenShade_hunShouldHavePartialShadow() {
+ // Given: shade is closed when HUN pops up,
+ // now drags down the HUN to open shade
+ ambientState.stackTranslation = -ambientState.topPadding
+ // Mock the height of shade
+ ambientState.setLayoutMinHeight(1000)
+ val childHunView = createHunViewMock(
+ isShadeOpen = false,
+ fullyVisible = false,
+ headerVisibleAmount = 0.5f
+ )
+ childHunView.viewState.yTranslation = 0f
+ // Shade is being opened, thus childHunView's headerVisibleAmount is between 0 and 1
+ // use 0.5 as headerVisibleAmount here
+ childHunView.headerVisibleAmount = 0.5f
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(childHunView)
+
+ // When: updateChildZValue() is called for the top HUN
+ stackScrollAlgorithm.updateChildZValue(
+ /* i= */ 0,
+ /* childrenOnTop= */ 0.0f,
+ /* StackScrollAlgorithmState= */ algorithmState,
+ /* ambientState= */ ambientState,
+ /* shouldElevateHun= */ true
+ )
+
+ // Then: HUN should have shadow, but not as full size
+ assertThat(childHunView.viewState.zTranslation).isGreaterThan(0.0f)
+ assertThat(childHunView.viewState.zTranslation)
+ .isLessThan(px(R.dimen.heads_up_pinned_elevation))
+ }
+
+ private fun createHunViewMock(
+ isShadeOpen: Boolean,
+ fullyVisible: Boolean,
+ headerVisibleAmount: Float
+ ) =
+ mock<ExpandableNotificationRow>().apply {
+ val childViewStateMock = createHunChildViewState(isShadeOpen, fullyVisible)
+ whenever(this.viewState).thenReturn(childViewStateMock)
+
+ whenever(this.mustStayOnScreen()).thenReturn(true)
+ whenever(this.headerVisibleAmount).thenReturn(headerVisibleAmount)
+ }
+
+
+ private fun createHunChildViewState(isShadeOpen: Boolean, fullyVisible: Boolean) =
+ ExpandableViewState().apply {
+ // Mock the HUN's height with ambientState.topPadding +
+ // ambientState.stackTranslation
+ height = (ambientState.topPadding + ambientState.stackTranslation).toInt()
+ if (isShadeOpen && fullyVisible) {
+ yTranslation =
+ ambientState.topPadding + ambientState.stackTranslation
+ } else {
+ yTranslation = 0f
+ }
+ headsUpIsVisible = fullyVisible
+ }
+
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction: Float,
expectedAlpha: Float
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 75a3b21..d1957ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -42,6 +42,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.logging.BiometricUnlockLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dump.DumpManager;
@@ -78,6 +79,8 @@
@Mock
private KeyguardUpdateMonitor mUpdateMonitor;
@Mock
+ private KeyguardUpdateMonitor.StrongAuthTracker mStrongAuthTracker;
+ @Mock
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock
private NotificationShadeWindowController mNotificationShadeWindowController;
@@ -119,6 +122,8 @@
private ScreenOffAnimationController mScreenOffAnimationController;
@Mock
private VibratorHelper mVibratorHelper;
+ @Mock
+ private BiometricUnlockLogger mLogger;
private BiometricUnlockController mBiometricUnlockController;
@Before
@@ -138,12 +143,13 @@
mKeyguardViewMediator, mScrimController, mShadeController,
mNotificationShadeWindowController, mKeyguardStateController, mHandler,
mUpdateMonitor, res.getResources(), mKeyguardBypassController,
- mMetricsLogger, mDumpManager, mPowerManager,
+ mMetricsLogger, mDumpManager, mPowerManager, mLogger,
mNotificationMediaManager, mWakefulnessLifecycle, mScreenLifecycle,
mAuthController, mStatusBarStateController, mKeyguardUnlockAnimationController,
mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
+ when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
}
@Test
@@ -285,6 +291,7 @@
@Test
public void onBiometricAuthenticated_whenFace_andBypass_encrypted_showPrimaryBouncer() {
reset(mUpdateMonitor);
+ when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
@@ -322,6 +329,7 @@
@Test
public void onBiometricAuthenticated_whenFace_noBypass_encrypted_doNothing() {
reset(mUpdateMonitor);
+ when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index bf31eb28..3fccd37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -136,7 +136,7 @@
StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
verify(mCentralSurfaces).updateQsExpansionEnabled();
- verify(mShadeController).animateCollapsePanels();
+ verify(mShadeController).animateCollapseShade();
// Trying to open it does nothing.
mSbcqCallbacks.animateExpandNotificationsPanel();
@@ -154,7 +154,7 @@
mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
StatusBarManager.DISABLE2_NONE, false);
verify(mCentralSurfaces).updateQsExpansionEnabled();
- verify(mShadeController, never()).animateCollapsePanels();
+ verify(mShadeController, never()).animateCollapseShade();
// Can now be opened.
mSbcqCallbacks.animateExpandNotificationsPanel();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 41912f5..ed84e42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -109,6 +109,7 @@
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.PluginDependencyProvider;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -120,7 +121,6 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -355,7 +355,6 @@
when(mStackScrollerController.getView()).thenReturn(mStackScroller);
when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
- when(mNotificationPanelViewController.getView()).thenReturn(mNotificationPanelView);
when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
when(powerManagerService.isInteractive()).thenReturn(true);
when(mStackScroller.getActivatedChild()).thenReturn(null);
@@ -393,10 +392,21 @@
return null;
}).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
- mShadeController = spy(new ShadeControllerImpl(mCommandQueue,
- mStatusBarStateController, mNotificationShadeWindowController,
- mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
- () -> Optional.of(mCentralSurfaces), () -> mAssistManager));
+ mShadeController = spy(new ShadeControllerImpl(
+ mCommandQueue,
+ mKeyguardStateController,
+ mStatusBarStateController,
+ mStatusBarKeyguardViewManager,
+ mStatusBarWindowController,
+ mNotificationShadeWindowController,
+ mContext.getSystemService(WindowManager.class),
+ () -> mAssistManager,
+ () -> mNotificationGutsManager
+ ));
+ mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+ mShadeController.setNotificationShadeWindowViewController(
+ mNotificationShadeWindowViewController);
+ mShadeController.setNotificationPresenter(mNotificationPresenter);
when(mOperatorNameViewControllerFactory.create(any()))
.thenReturn(mOperatorNameViewController);
@@ -493,6 +503,7 @@
return mViewRootImpl;
}
};
+ mCentralSurfaces.initShadeVisibilityListener();
when(mViewRootImpl.getOnBackInvokedDispatcher())
.thenReturn(mOnBackInvokedDispatcher);
when(mKeyguardViewMediator.registerCentralSurfaces(
@@ -808,7 +819,7 @@
when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
mOnBackInvokedCallback.getValue().onBackInvoked();
- verify(mShadeController).animateCollapsePanels();
+ verify(mShadeController).animateCollapseShade();
}
@Test
@@ -1031,7 +1042,7 @@
}
@Test
- public void collapseShade_callsAnimateCollapsePanels_whenExpanded() {
+ public void collapseShade_callsanimateCollapseShade_whenExpanded() {
// GIVEN the shade is expanded
mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1039,12 +1050,12 @@
// WHEN collapseShade is called
mCentralSurfaces.collapseShade();
- // VERIFY that animateCollapsePanels is called
- verify(mShadeController).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is called
+ verify(mShadeController).animateCollapseShade();
}
@Test
- public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() {
+ public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() {
// GIVEN the shade is collapsed
mCentralSurfaces.onShadeExpansionFullyChanged(false);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1052,12 +1063,12 @@
// WHEN collapseShade is called
mCentralSurfaces.collapseShade();
- // VERIFY that animateCollapsePanels is NOT called
- verify(mShadeController, never()).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is NOT called
+ verify(mShadeController, never()).animateCollapseShade();
}
@Test
- public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() {
+ public void collapseShadeForBugReport_callsanimateCollapseShade_whenFlagDisabled() {
// GIVEN the shade is expanded & flag enabled
mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1066,12 +1077,12 @@
// WHEN collapseShadeForBugreport is called
mCentralSurfaces.collapseShadeForBugreport();
- // VERIFY that animateCollapsePanels is called
- verify(mShadeController).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is called
+ verify(mShadeController).animateCollapseShade();
}
@Test
- public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() {
+ public void collapseShadeForBugReport_doesNotCallanimateCollapseShade_whenFlagEnabled() {
// GIVEN the shade is expanded & flag enabled
mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1080,8 +1091,8 @@
// WHEN collapseShadeForBugreport is called
mCentralSurfaces.collapseShadeForBugreport();
- // VERIFY that animateCollapsePanels is called
- verify(mShadeController, never()).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is called
+ verify(mShadeController, never()).animateCollapseShade();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index fee3ccb..038af8f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -14,23 +14,37 @@
package com.android.systemui.statusbar.phone
-import androidx.test.filters.SmallTest
+import android.content.res.Configuration
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
+import android.content.res.Configuration.UI_MODE_NIGHT_NO
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.content.res.Configuration.UI_MODE_TYPE_CAR
+import android.os.LocaleList
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import java.util.Locale
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ConfigurationControllerImplTest : SysuiTestCase() {
- private val mConfigurationController =
- com.android.systemui.statusbar.phone.ConfigurationControllerImpl(mContext)
+ private lateinit var mConfigurationController: ConfigurationControllerImpl
+
+ @Before
+ fun setUp() {
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+ }
@Test
fun testThemeChange() {
@@ -57,4 +71,303 @@
verify(listener).onThemeChanged()
verify(listener2, never()).onThemeChanged()
}
+
+ @Test
+ fun configChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 12
+ config.smallestScreenWidthDp = 240
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the config is updated
+ config.densityDpi = 20
+ config.smallestScreenWidthDp = 300
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.changedConfig?.densityDpi).isEqualTo(20)
+ assertThat(listener.changedConfig?.smallestScreenWidthDp).isEqualTo(300)
+ }
+
+ @Test
+ fun densityChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 12
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the density is updated
+ config.densityDpi = 20
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun fontChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.fontScale = 1.5f
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the font is updated
+ config.fontScale = 1.4f
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun isCarAndUiModeChanged_densityListenerNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_YES
+ // Re-create the controller since we calculate car mode on creation
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun isNotCarAndUiModeChanged_densityListenerNotNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_NIGHT_YES
+ // Re-create the controller since we calculate car mode on creation
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is not notified because it's not car mode
+ assertThat(listener.densityOrFontScaleChanged).isFalse()
+ }
+
+ @Test
+ fun smallestScreenWidthChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.smallestScreenWidthDp = 240
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the width is updated
+ config.smallestScreenWidthDp = 300
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.smallestScreenWidthChanged).isTrue()
+ }
+
+ @Test
+ fun maxBoundsChange_newConfigObject_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN a new configuration object with new bounds is sent
+ val newConfig = Configuration()
+ newConfig.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ mConfigurationController.onConfigurationChanged(newConfig)
+
+ // THEN the listener is notified
+ assertThat(listener.maxBoundsChanged).isTrue()
+ }
+
+ // Regression test for b/245799099
+ @Test
+ fun maxBoundsChange_sameObject_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the existing config is updated with new bounds
+ config.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.maxBoundsChanged).isTrue()
+ }
+
+
+ @Test
+ fun localeListChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.locales = LocaleList(Locale.CANADA, Locale.GERMANY)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the locales are updated
+ config.locales = LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE)
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.localeListChanged).isTrue()
+ }
+
+ @Test
+ fun uiModeChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_NIGHT_YES
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.uiModeChanged).isTrue()
+ }
+
+ @Test
+ fun layoutDirectionUpdated_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.screenLayout = SCREENLAYOUT_LAYOUTDIR_LTR
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the layout is updated
+ config.screenLayout = SCREENLAYOUT_LAYOUTDIR_RTL
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.layoutDirectionChanged).isTrue()
+ }
+
+ @Test
+ fun assetPathsUpdated_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.assetsSeq = 45
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the assets sequence is updated
+ config.assetsSeq = 46
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.themeChanged).isTrue()
+ }
+
+ @Test
+ fun multipleUpdates_listenerNotifiedOfAll() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 14
+ config.windowConfiguration.setMaxBounds(0, 0, 2, 2)
+ config.uiMode = UI_MODE_NIGHT_YES
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN multiple fields are updated
+ config.densityDpi = 20
+ config.windowConfiguration.setMaxBounds(0, 0, 3, 3)
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified of all of them
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ assertThat(listener.maxBoundsChanged).isTrue()
+ assertThat(listener.uiModeChanged).isTrue()
+ }
+
+ @Test
+ fun equivalentConfigObject_listenerNotNotified() {
+ val config = mContext.resources.configuration
+ val listener = createAndAddListener()
+
+ // WHEN we update with the new object that has all the same fields
+ mConfigurationController.onConfigurationChanged(Configuration(config))
+
+ listener.assertNoMethodsCalled()
+ }
+
+ private fun createAndAddListener(): TestListener {
+ val listener = TestListener()
+ mConfigurationController.addCallback(listener)
+ // Adding a listener can trigger some callbacks, so we want to reset the values right
+ // after the listener is added
+ listener.reset()
+ return listener
+ }
+
+ private class TestListener : ConfigurationListener {
+ var changedConfig: Configuration? = null
+ var densityOrFontScaleChanged = false
+ var smallestScreenWidthChanged = false
+ var maxBoundsChanged = false
+ var uiModeChanged = false
+ var themeChanged = false
+ var localeListChanged = false
+ var layoutDirectionChanged = false
+
+ override fun onConfigChanged(newConfig: Configuration?) {
+ changedConfig = newConfig
+ }
+ override fun onDensityOrFontScaleChanged() {
+ densityOrFontScaleChanged = true
+ }
+ override fun onSmallestScreenWidthChanged() {
+ smallestScreenWidthChanged = true
+ }
+ override fun onMaxBoundsChanged() {
+ maxBoundsChanged = true
+ }
+ override fun onUiModeChanged() {
+ uiModeChanged = true
+ }
+ override fun onThemeChanged() {
+ themeChanged = true
+ }
+ override fun onLocaleListChanged() {
+ localeListChanged = true
+ }
+ override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
+ layoutDirectionChanged = true
+ }
+
+ fun assertNoMethodsCalled() {
+ assertThat(densityOrFontScaleChanged).isFalse()
+ assertThat(smallestScreenWidthChanged).isFalse()
+ assertThat(maxBoundsChanged).isFalse()
+ assertThat(uiModeChanged).isFalse()
+ assertThat(themeChanged).isFalse()
+ assertThat(localeListChanged).isFalse()
+ assertThat(layoutDirectionChanged).isFalse()
+ }
+
+ fun reset() {
+ changedConfig = null
+ densityOrFontScaleChanged = false
+ smallestScreenWidthChanged = false
+ maxBoundsChanged = false
+ uiModeChanged = false
+ themeChanged = false
+ localeListChanged = false
+ layoutDirectionChanged = false
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index d3b5418..df7ee43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -39,6 +39,7 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
+import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -398,6 +399,8 @@
@Test
public void testShow_delaysIfFaceAuthIsRunning() {
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+ .thenReturn(true);
when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
mBouncer.show(true /* reset */);
@@ -410,9 +413,10 @@
}
@Test
- public void testShow_doesNotDelaysIfFaceAuthIsLockedOut() {
+ public void testShow_doesNotDelaysIfFaceAuthIsNotAllowed() {
when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
- when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
+ .thenReturn(false);
mBouncer.show(true /* reset */);
verify(mHandler, never()).postDelayed(any(), anyLong());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 6ec5cf8..eb0b9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -56,14 +56,12 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -112,16 +110,12 @@
private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
@Mock
private UserManager mUserManager;
+ @Mock
+ private StatusBarUserChipViewModel mStatusBarUserChipViewModel;
@Captor
private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
- @Mock
- private StatusBarUserSwitcherFeatureController mStatusBarUserSwitcherFeatureController;
- @Mock
- private StatusBarUserSwitcherController mStatusBarUserSwitcherController;
- @Mock
- private StatusBarUserInfoTracker mStatusBarUserInfoTracker;
@Mock private SecureSettings mSecureSettings;
@Mock private CommandQueue mCommandQueue;
@Mock private KeyguardLogger mLogger;
@@ -169,9 +163,7 @@
mStatusBarStateController,
mStatusBarContentInsetsProvider,
mUserManager,
- mStatusBarUserSwitcherFeatureController,
- mStatusBarUserSwitcherController,
- mStatusBarUserInfoTracker,
+ mStatusBarUserChipViewModel,
mSecureSettings,
mCommandQueue,
mFakeExecutor,
@@ -479,8 +471,7 @@
@Test
public void testNewUserSwitcherDisablesAvatar_newUiOn() {
// GIVEN the status bar user switcher chip is enabled
- when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
- .thenReturn(true);
+ when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true);
// WHEN the controller is created
mController = createController();
@@ -492,8 +483,7 @@
@Test
public void testNewUserSwitcherDisablesAvatar_newUiOff() {
// GIVEN the status bar user switcher chip is disabled
- when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
- .thenReturn(false);
+ when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false);
// WHEN the controller is created
mController = createController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index a61fba5..e2843a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -27,11 +27,11 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.shade.NotificationPanelViewController
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
@@ -64,7 +64,7 @@
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
- private lateinit var userSwitcherController: StatusBarUserSwitcherController
+ private lateinit var userChipViewModel: StatusBarUserChipViewModel
@Mock
private lateinit var viewUtil: ViewUtil
@@ -76,17 +76,15 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(notificationPanelViewController.view).thenReturn(panelView)
`when`(sysuiUnfoldComponent.getStatusBarMoveFromCenterAnimationController())
.thenReturn(moveFromCenterAnimation)
- // create the view on main thread as it requires main looper
+ // create the view and controller on main thread as it requires main looper
InstrumentationRegistry.getInstrumentation().runOnMainSync {
val parent = FrameLayout(mContext) // add parent to keep layout params
view = LayoutInflater.from(mContext)
.inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+ controller = createAndInitController(view)
}
-
- controller = createAndInitController(view)
}
@Test
@@ -106,7 +104,10 @@
val view = createViewMock()
val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
unfoldConfig.isEnabled = true
- controller = createAndInitController(view)
+ // create the controller on main thread as it requires main looper
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ controller = createAndInitController(view)
+ }
verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
argumentCaptor.value.onPreDraw()
@@ -126,7 +127,7 @@
return PhoneStatusBarViewController.Factory(
Optional.of(sysuiUnfoldComponent),
Optional.of(progressProvider),
- userSwitcherController,
+ userChipViewModel,
viewUtil,
configurationController
).create(view, touchEventHandler).also {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 5aa7f92..27b1da0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -25,7 +25,6 @@
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -41,9 +40,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- // TODO(b/197137564): Setting up a panel view and its controller feels unnecessary when
- // testing just [PhoneStatusBarView].
- `when`(notificationPanelViewController.view).thenReturn(panelView)
view = PhoneStatusBarView(mContext, null)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 808abc8..e475905 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA;
import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
@@ -59,7 +58,6 @@
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -119,7 +117,6 @@
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock private KeyguardViewMediator mKeyguardViewMediator;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -233,8 +230,7 @@
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager,
- mKeyguardViewMediator);
+ mStatusBarKeyguardViewManager);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -243,8 +239,6 @@
mScrimController.setWallpaperSupportsAmbientMode(false);
mScrimController.transitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
-
- mScrimController.setLaunchingAffordanceWithPreview(false);
}
@After
@@ -858,8 +852,7 @@
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager,
- mKeyguardViewMediator);
+ mStatusBarKeyguardViewManager);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1449,16 +1442,30 @@
@Test
public void testNotificationTransparency_followsTransitionToFullShade() {
+ mScrimController.setClipsQsScrim(true);
+
mScrimController.transitionTo(SHADE_LOCKED);
mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
+
+ assertScrimTinted(Map.of(
+ mScrimInFront, false,
+ mScrimBehind, true,
+ mNotificationsScrim, false
+ ));
+
float shadeLockedAlpha = mNotificationsScrim.getViewAlpha();
mScrimController.transitionTo(ScrimState.KEYGUARD);
mScrimController.setRawPanelExpansionFraction(1.0f);
finishAnimationsImmediately();
float keyguardAlpha = mNotificationsScrim.getViewAlpha();
- mScrimController.setClipsQsScrim(true);
+ assertScrimTinted(Map.of(
+ mScrimInFront, true,
+ mScrimBehind, true,
+ mNotificationsScrim, true
+ ));
+
float progress = 0.5f;
float lsNotifProgress = 0.3f;
mScrimController.setTransitionToFullShadeProgress(progress, lsNotifProgress);
@@ -1638,30 +1645,6 @@
assertScrimAlpha(mScrimBehind, 0);
}
- @Test
- public void keyguardAlpha_whenUnlockedForOcclusion_ifPlayingOcclusionAnimation() {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
-
- when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
-
- mScrimController.transitionTo(ScrimState.UNLOCKED);
- finishAnimationsImmediately();
-
- assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
- }
-
- @Test
- public void keyguardAlpha_whenUnlockedForLaunch_ifLaunchingAffordance() {
- mScrimController.transitionTo(ScrimState.KEYGUARD);
- when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
- mScrimController.setLaunchingAffordanceWithPreview(true);
-
- mScrimController.transitionTo(ScrimState.UNLOCKED);
- finishAnimationsImmediately();
-
- assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
- }
-
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index e86676b..1759fb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -19,9 +19,9 @@
import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
-import android.test.suitebuilder.annotation.SmallTest
import android.view.Display
import android.view.DisplayCutout
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -463,16 +463,10 @@
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java))
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 800, 600),
- displayUniqueId = "2"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
// WHEN: get insets on the second display
val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -487,23 +481,15 @@
// get insets and switch back
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java))
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
val firstDisplayInsetsFirstCall = provider
.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 800, 600),
- displayUniqueId = "2"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
// WHEN: get insets on the first display again
val firstDisplayInsetsSecondCall = provider
@@ -513,9 +499,70 @@
assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall)
}
- private fun givenDisplay(screenBounds: Rect, displayUniqueId: String) {
- `when`(display.uniqueId).thenReturn(displayUniqueId)
- configuration.windowConfiguration.maxBounds = screenBounds
+ // Regression test for b/245799099
+ @Test
+ fun onMaxBoundsChanged_listenerNotified() {
+ // Start out with an existing configuration with bounds
+ configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ configurationController.onConfigurationChanged(configuration)
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ // WHEN the config is updated with new bounds
+ configuration.windowConfiguration.setMaxBounds(0, 0, 456, 789)
+ configurationController.onConfigurationChanged(configuration)
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
+ }
+
+ @Test
+ fun onDensityOrFontScaleChanged_listenerNotified() {
+ configuration.densityDpi = 12
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ // WHEN the config is updated
+ configuration.densityDpi = 20
+ configurationController.onConfigurationChanged(configuration)
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
+ }
+
+ @Test
+ fun onThemeChanged_listenerNotified() {
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ configurationController.notifyThemeChanged()
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
}
private fun assertRects(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 49c3a21..e467d93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -222,9 +222,16 @@
}
@Test
- public void onPanelExpansionChanged_neverHidesScrimmedBouncer() {
+ public void onPanelExpansionChanged_neverHidesFullscreenBouncer() {
when(mPrimaryBouncer.isShowing()).thenReturn(true);
- when(mPrimaryBouncer.isScrimmed()).thenReturn(true);
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.SimPuk);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+ verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
+
+ reset(mPrimaryBouncer);
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.SimPin);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_VISIBLE));
}
@@ -271,13 +278,6 @@
}
@Test
- public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
- mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animate */);
- mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
- }
-
- @Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
when(mBiometricUnlockController.getMode())
.thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
@@ -307,6 +307,34 @@
}
@Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
+ // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+ // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+ // which would mistakenly cause the bouncer to show briefly before its visibility
+ // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+ // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+ when(mBiometricUnlockController.getMode())
+ .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenShadeLocked() {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(
@@ -570,4 +598,40 @@
mStatusBarKeyguardViewManager.hideBouncer(false);
verify(mPrimaryBouncerInteractor, never()).hide();
}
+
+ @Test
+ public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
+ mStatusBarKeyguardViewManager =
+ new StatusBarKeyguardViewManager(
+ getContext(),
+ mViewMediatorCallback,
+ mLockPatternUtils,
+ mStatusBarStateController,
+ mock(ConfigurationController.class),
+ mKeyguardUpdateMonitor,
+ mDreamOverlayStateController,
+ mock(NavigationModeController.class),
+ mock(DockManager.class),
+ mock(NotificationShadeWindowController.class),
+ mKeyguardStateController,
+ mock(NotificationMediaManager.class),
+ mKeyguardBouncerFactory,
+ mKeyguardMessageAreaFactory,
+ Optional.of(mSysUiUnfoldComponent),
+ () -> mShadeController,
+ mLatencyTracker,
+ mKeyguardSecurityModel,
+ mFeatureFlags,
+ mPrimaryBouncerCallbackInteractor,
+ mPrimaryBouncerInteractor,
+ mBouncerView) {
+ @Override
+ public ViewRootImpl getViewRootImpl() {
+ return mViewRootImpl;
+ }
+ };
+
+ // the following call before registering centralSurfaces should NOT throw a NPE:
+ mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index ce54d78..cae414a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -263,7 +263,7 @@
while (!runnables.isEmpty()) runnables.remove(0).run();
// Then
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(),
eq(false) /* animate */, any(), any());
@@ -296,7 +296,7 @@
verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
// This is called regardless, and simply short circuits when there is nothing to do.
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mAssistManager).hideAssist();
@@ -329,7 +329,7 @@
// Then
verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mAssistManager).hideAssist();
@@ -357,7 +357,7 @@
// Then
verify(mBubblesManager).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mAssistManager).hideAssist();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index eba3b04..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-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.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-@SmallTest
-class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() {
- @Mock
- private lateinit var tracker: StatusBarUserInfoTracker
-
- @Mock
- private lateinit var featureController: StatusBarUserSwitcherFeatureController
-
- @Mock
- private lateinit var userSwitcherDialogController: UserSwitchDialogController
-
- @Mock
- private lateinit var featureFlags: FeatureFlags
-
- @Mock
- private lateinit var activityStarter: ActivityStarter
-
- @Mock
- private lateinit var falsingManager: FalsingManager
-
- private lateinit var statusBarUserSwitcherContainer: StatusBarUserSwitcherContainer
- private lateinit var controller: StatusBarUserSwitcherControllerImpl
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- statusBarUserSwitcherContainer = StatusBarUserSwitcherContainer(mContext, null)
- statusBarUserSwitcherContainer
- controller = StatusBarUserSwitcherControllerImpl(
- statusBarUserSwitcherContainer,
- tracker,
- featureController,
- userSwitcherDialogController,
- featureFlags,
- activityStarter,
- falsingManager
- )
- controller.init()
- controller.onViewAttached()
- }
-
- @Test
- fun testFalsingManager() {
- statusBarUserSwitcherContainer.callOnClick()
- verify(falsingManager).isFalseTap(FalsingManager.LOW_PENALTY)
- }
-
- @Test
- fun testStartActivity() {
- `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(false)
- statusBarUserSwitcherContainer.callOnClick()
- verify(userSwitcherDialogController).showDialog(any(), any())
- `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(true)
- statusBarUserSwitcherContainer.callOnClick()
- verify(activityStarter).startActivity(any(Intent::class.java),
- eq(true) /* dismissShade */,
- eq(null) /* animationController */,
- eq(true) /* showOverLockscreenWhenLocked */,
- eq(UserHandle.SYSTEM))
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
index b7a6c01..d35ce76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
@@ -22,7 +22,7 @@
import android.provider.Settings.Global
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -45,7 +45,7 @@
private lateinit var underTest: AirplaneModeRepositoryImpl
- @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var logger: TableLogBuffer
private lateinit var bgHandler: Handler
private lateinit var scope: CoroutineScope
private lateinit var settings: FakeSettings
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
index 76016a1..5a6bb30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
@@ -38,9 +38,9 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-class AirplaneModeViewModelTest : SysuiTestCase() {
+class AirplaneModeViewModelImplTest : SysuiTestCase() {
- private lateinit var underTest: AirplaneModeViewModel
+ private lateinit var underTest: AirplaneModeViewModelImpl
@Mock private lateinit var logger: ConnectivityPipelineLogger
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -57,7 +57,7 @@
scope = CoroutineScope(IMMEDIATE)
underTest =
- AirplaneModeViewModel(
+ AirplaneModeViewModelImpl(
interactor,
logger,
scope,
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/data/model/WifiNetworkModelTest.kt
index 3d29d2b..30fd308 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/data/model/WifiNetworkModelTest.kt
@@ -18,8 +18,10 @@
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.Active.Companion.MIN_VALID_LEVEL
+import com.google.common.truth.Truth.assertThat
import org.junit.Test
@SmallTest
@@ -48,6 +50,125 @@
WifiNetworkModel.Active(NETWORK_ID, level = MAX_VALID_LEVEL + 1)
}
+ // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
+
+ @Test
+ fun logDiffs_inactiveToActive_logsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+ }
+ @Test
+ fun logDiffs_activeToInactive_resetsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ WifiNetworkModel.Inactive.logDiffs(prevVal = activeNetwork, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+ }
+
+ @Test
+ fun logDiffs_carrierMergedToActive_logsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ activeNetwork.logDiffs(prevVal = WifiNetworkModel.CarrierMerged, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, "5"))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+ }
+ @Test
+ fun logDiffs_activeToCarrierMerged_resetsAllActiveFields() {
+ val logger = TestLogger()
+ val activeNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+
+ WifiNetworkModel.CarrierMerged.logDiffs(prevVal = activeNetwork, logger)
+
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED))
+ assertThat(logger.changes).contains(Pair(COL_NETWORK_ID, NETWORK_ID_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
+ assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
+ assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+ }
+
+ @Test
+ fun logDiffs_activeChangesLevel_onlyLevelLogged() {
+ val logger = TestLogger()
+ val prevActiveNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 3,
+ ssid = "Test SSID"
+ )
+ val newActiveNetwork =
+ WifiNetworkModel.Active(
+ networkId = 5,
+ isValidated = true,
+ level = 2,
+ ssid = "Test SSID"
+ )
+
+ newActiveNetwork.logDiffs(prevActiveNetwork, logger)
+
+ assertThat(logger.changes).isEqualTo(listOf(Pair(COL_LEVEL, "2")))
+ }
+
+ private class TestLogger : TableRowLogger {
+ val changes = mutableListOf<Pair<String, String>>()
+
+ override fun logChange(columnName: String, value: String?) {
+ changes.add(Pair(columnName, value.toString()))
+ }
+
+ override fun logChange(columnName: String, value: Int) {
+ changes.add(Pair(columnName, value.toString()))
+ }
+
+ override fun logChange(columnName: String, value: Boolean) {
+ changes.add(Pair(columnName, value.toString()))
+ }
+ }
+
companion object {
private const val NETWORK_ID = 2
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index a64a4bd..800f3c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -29,6 +29,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.log.table.TableLogBuffer
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.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
@@ -69,6 +70,7 @@
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogger: TableLogBuffer
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var wifiManager: WifiManager
private lateinit var executor: Executor
@@ -804,6 +806,7 @@
broadcastDispatcher,
connectivityManager,
logger,
+ tableLogger,
executor,
scope,
wifiManagerToUse,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index 71b8bab..b38497a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -35,8 +35,9 @@
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@SmallTest
-class WifiInteractorTest : SysuiTestCase() {
+class WifiInteractorImplTest : SysuiTestCase() {
private lateinit var underTest: WifiInteractor
@@ -47,7 +48,7 @@
fun setUp() {
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
- underTest = WifiInteractor(connectivityRepository, wifiRepository)
+ underTest = WifiInteractorImpl(connectivityRepository, wifiRepository)
}
@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 37457b3..5c16e129 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
@@ -32,12 +32,14 @@
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.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
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.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
@@ -86,9 +88,9 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractor(connectivityRepository, wifiRepository)
+ interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(Dispatchers.Unconfined)
- airplaneModeViewModel = AirplaneModeViewModel(
+ airplaneModeViewModel = AirplaneModeViewModelImpl(
AirplaneModeInteractor(
airplaneModeRepository,
connectivityRepository,
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 a1afcd7..3001b81 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
@@ -30,6 +30,7 @@
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.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
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.ConnectivitySlot
@@ -37,6 +38,7 @@
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.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
import com.google.common.truth.Truth.assertThat
@@ -81,10 +83,10 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractor(connectivityRepository, wifiRepository)
+ interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
airplaneModeViewModel =
- AirplaneModeViewModel(
+ AirplaneModeViewModelImpl(
AirplaneModeInteractor(
airplaneModeRepository,
connectivityRepository,
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 7d2c560..6a6b2a8 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
@@ -23,6 +23,7 @@
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.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
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.ConnectivitySlot
@@ -30,6 +31,7 @@
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.WifiActivityModel
import com.google.common.truth.Truth.assertThat
@@ -73,9 +75,9 @@
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
wifiRepository.setIsWifiEnabled(true)
- interactor = WifiInteractor(connectivityRepository, wifiRepository)
+ interactor = WifiInteractorImpl(connectivityRepository, wifiRepository)
scope = CoroutineScope(IMMEDIATE)
- airplaneModeViewModel = AirplaneModeViewModel(
+ airplaneModeViewModel = AirplaneModeViewModelImpl(
AirplaneModeInteractor(
airplaneModeRepository,
connectivityRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
index f304647..0a3da0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt
@@ -237,7 +237,7 @@
fun refresh() {
underTest.refresh()
- verify(controller).refreshUsers(UserHandle.USER_NULL)
+ verify(controller).refreshUsers()
}
private fun createUserRecord(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 43d0fe9..1eee08c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -221,4 +221,33 @@
Assert.assertFalse(mBatteryController.isChargingSourceDock());
}
+
+ @Test
+ public void batteryStateChanged_healthNotOverheated_outputsFalse() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isOverheated());
+ }
+
+ @Test
+ public void batteryStateChanged_healthOverheated_outputsTrue() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertTrue(mBatteryController.isOverheated());
+ }
+
+ @Test
+ public void batteryStateChanged_noHealthGiven_outputsFalse() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isOverheated());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index d0391ac..833cabb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bluetooth.BluetoothLogger;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import org.junit.Before;
import org.junit.Test;
@@ -56,6 +57,7 @@
@SmallTest
public class BluetoothControllerImplTest extends SysuiTestCase {
+ private UserTracker mUserTracker;
private LocalBluetoothManager mMockBluetoothManager;
private CachedBluetoothDeviceManager mMockDeviceManager;
private LocalBluetoothAdapter mMockAdapter;
@@ -70,6 +72,7 @@
mTestableLooper = TestableLooper.get(this);
mMockBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager.class);
mDevices = new ArrayList<>();
+ mUserTracker = mock(UserTracker.class);
mMockDeviceManager = mock(CachedBluetoothDeviceManager.class);
when(mMockDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
when(mMockBluetoothManager.getCachedDeviceManager()).thenReturn(mMockDeviceManager);
@@ -81,6 +84,7 @@
mMockDumpManager = mock(DumpManager.class);
mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
+ mUserTracker,
mMockDumpManager,
mock(BluetoothLogger.class),
mTestableLooper.getLooper(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
index 14cc032..71ac7c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java
@@ -33,7 +33,7 @@
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import com.android.systemui.statusbar.policy.ExtensionController.TunerFactory;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 26df03f..dc08aba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import org.junit.Before;
import org.junit.Test;
@@ -61,6 +62,8 @@
public class HotspotControllerImplTest extends SysuiTestCase {
@Mock
+ private UserTracker mUserTracker;
+ @Mock
private DumpManager mDumpManager;
@Mock
private TetheringManager mTetheringManager;
@@ -104,7 +107,8 @@
Handler handler = new Handler(mLooper.getLooper());
- mController = new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+ mController = new HotspotControllerImpl(mContext, mUserTracker, handler, handler,
+ mDumpManager);
verify(mTetheringManager)
.registerTetheringEventCallback(any(), mTetheringCallbackCaptor.capture());
}
@@ -191,7 +195,7 @@
Handler handler = new Handler(mLooper.getLooper());
HotspotController controller =
- new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+ new HotspotControllerImpl(mContext, mUserTracker, handler, handler, mDumpManager);
verifyNoMoreInteractions(mTetheringManager);
assertFalse(controller.isHotspotSupported());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index d44cdb2..15235b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -50,6 +50,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -72,10 +73,12 @@
private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
private final IKeyChainService.Stub mKeyChainService = mock(IKeyChainService.Stub.class);
private final UserManager mUserManager = mock(UserManager.class);
+ private final UserTracker mUserTracker = mock(UserTracker.class);
private final BroadcastDispatcher mBroadcastDispatcher = mock(BroadcastDispatcher.class);
private final Handler mHandler = mock(Handler.class);
private SecurityControllerImpl mSecurityController;
private ConnectivityManager mConnectivityManager = mock(ConnectivityManager.class);
+ private FakeExecutor mMainExecutor;
private FakeExecutor mBgExecutor;
private BroadcastReceiver mBroadcastReceiver;
@@ -102,11 +105,14 @@
ArgumentCaptor<BroadcastReceiver> brCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
+ mMainExecutor = new FakeExecutor(new FakeSystemClock());
mBgExecutor = new FakeExecutor(new FakeSystemClock());
mSecurityController = new SecurityControllerImpl(
mContext,
+ mUserTracker,
mHandler,
mBroadcastDispatcher,
+ mMainExecutor,
mBgExecutor,
Mockito.mock(DumpManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index 169f4fb..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,727 +0,0 @@
-/*
- * Copyright (C) 2021 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 android.app.IActivityManager
-import android.app.NotificationManager
-import android.app.admin.DevicePolicyManager
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.DialogInterface
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.hardware.face.FaceManager
-import android.hardware.fingerprint.FingerprintManager
-import android.os.Handler
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.ThreadedRenderer
-import androidx.test.filters.SmallTest
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.util.LatencyTracker
-import com.android.internal.util.UserIcons
-import com.android.systemui.GuestResetOrExitSessionReceiver
-import com.android.systemui.GuestResumeSessionReceiver
-import com.android.systemui.GuestSessionNotification
-import com.android.systemui.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.DialogCuj
-import com.android.systemui.animation.DialogLaunchAnimator
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.QSUserSwitcherEvent
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.NotificationShadeWindowView
-import com.android.systemui.telephony.TelephonyListenerManager
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.nullable
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNotNull
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-class UserSwitcherControllerOldImplTest : SysuiTestCase() {
- @Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var activityManager: IActivityManager
- @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
- @Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var handler: Handler
- @Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var userManager: UserManager
- @Mock private lateinit var activityStarter: ActivityStarter
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock private lateinit var broadcastSender: BroadcastSender
- @Mock private lateinit var telephonyListenerManager: TelephonyListenerManager
- @Mock private lateinit var secureSettings: SecureSettings
- @Mock private lateinit var falsingManager: FalsingManager
- @Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
- @Mock private lateinit var latencyTracker: LatencyTracker
- @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
- @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
- @Mock private lateinit var threadedRenderer: ThreadedRenderer
- @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
- @Mock private lateinit var globalSettings: GlobalSettings
- @Mock private lateinit var guestSessionNotification: GuestSessionNotification
- @Mock private lateinit var guestResetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
- private lateinit var resetSessionDialogFactory:
- GuestResumeSessionReceiver.ResetSessionDialog.Factory
- private lateinit var guestResumeSessionReceiver: GuestResumeSessionReceiver
- private lateinit var testableLooper: TestableLooper
- private lateinit var bgExecutor: FakeExecutor
- private lateinit var longRunningExecutor: FakeExecutor
- private lateinit var uiExecutor: FakeExecutor
- private lateinit var uiEventLogger: UiEventLoggerFake
- private lateinit var userSwitcherController: UserSwitcherControllerOldImpl
- private lateinit var picture: Bitmap
- private val ownerId = UserHandle.USER_SYSTEM
- private val ownerInfo = UserInfo(ownerId, "Owner", null,
- UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or
- UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_ADMIN,
- UserManager.USER_TYPE_FULL_SYSTEM)
- private val guestId = 1234
- private val guestInfo = UserInfo(guestId, "Guest", null,
- UserInfo.FLAG_FULL or UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST)
- private val secondaryUser =
- UserInfo(10, "Secondary", null, 0, UserManager.USER_TYPE_FULL_SECONDARY)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- testableLooper = TestableLooper.get(this)
- bgExecutor = FakeExecutor(FakeSystemClock())
- longRunningExecutor = FakeExecutor(FakeSystemClock())
- uiExecutor = FakeExecutor(FakeSystemClock())
- uiEventLogger = UiEventLoggerFake()
-
- mContext.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_guestUserAutoCreated, false)
-
- mContext.addMockSystemService(Context.FACE_SERVICE, mock(FaceManager::class.java))
- mContext.addMockSystemService(Context.NOTIFICATION_SERVICE,
- mock(NotificationManager::class.java))
- mContext.addMockSystemService(Context.FINGERPRINT_SERVICE,
- mock(FingerprintManager::class.java))
-
- resetSessionDialogFactory = object : GuestResumeSessionReceiver.ResetSessionDialog.Factory {
- override fun create(userId: Int): GuestResumeSessionReceiver.ResetSessionDialog {
- return GuestResumeSessionReceiver.ResetSessionDialog(
- mContext,
- mock(UserSwitcherController::class.java),
- uiEventLogger,
- userId
- )
- }
- }
-
- guestResumeSessionReceiver = GuestResumeSessionReceiver(userTracker,
- secureSettings,
- broadcastDispatcher,
- guestSessionNotification,
- resetSessionDialogFactory)
-
- `when`(userManager.canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY)))
- .thenReturn(true)
- `when`(notificationShadeWindowView.context).thenReturn(context)
-
- // Since userSwitcherController involves InteractionJankMonitor.
- // Let's fulfill the dependencies.
- val mockedContext = mock(Context::class.java)
- doReturn(mockedContext).`when`(notificationShadeWindowView).context
- doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow
- doNothing().`when`(threadedRenderer).addObserver(any())
- doNothing().`when`(threadedRenderer).removeObserver(any())
- doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer
-
- picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
-
- // Create defaults for the current user
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
-
- setupController()
- }
-
- private fun setupController() {
- userSwitcherController =
- UserSwitcherControllerOldImpl(
- mContext,
- activityManager,
- userManager,
- userTracker,
- keyguardStateController,
- deviceProvisionedController,
- devicePolicyManager,
- handler,
- activityStarter,
- broadcastDispatcher,
- broadcastSender,
- uiEventLogger,
- falsingManager,
- telephonyListenerManager,
- secureSettings,
- globalSettings,
- bgExecutor,
- longRunningExecutor,
- uiExecutor,
- interactionJankMonitor,
- latencyTracker,
- dumpManager,
- dialogLaunchAnimator,
- guestResumeSessionReceiver,
- guestResetOrExitSessionReceiver
- )
- userSwitcherController.init(notificationShadeWindowView)
- }
-
- @Test
- fun testSwitchUser_parentDialogDismissed() {
- val otherUserRecord = UserRecord(
- secondaryUser,
- picture,
- false /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- userSwitcherController.onUserListItemClicked(otherUserRecord, dialogShower)
- testableLooper.processAllMessages()
-
- verify(dialogShower).dismiss()
- }
-
- @Test
- fun testAddGuest_okButtonPressed() {
- val emptyGuestUserRecord =
- UserRecord(
- null,
- null,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
- bgExecutor.runAllReady()
- uiExecutor.runAllReady()
- testableLooper.processAllMessages()
- verify(interactionJankMonitor).begin(any())
- verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
- verify(activityManager).switchUser(guestInfo.id)
- assertEquals(1, uiEventLogger.numLogs())
- assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0))
- }
-
- @Test
- fun testAddGuest_parentDialogDismissed() {
- val emptyGuestUserRecord =
- UserRecord(
- null,
- null,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- `when`(userManager.createGuest(any())).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
- bgExecutor.runAllReady()
- uiExecutor.runAllReady()
- testableLooper.processAllMessages()
- verify(dialogShower).dismiss()
- }
-
- @Test
- fun testRemoveGuest_removeButtonPressed_isLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestInfo.id)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- userSwitcherController.mExitGuestDialog
- .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
- testableLooper.processAllMessages()
- assertEquals(1, uiEventLogger.numLogs())
- assertTrue(
- QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id == uiEventLogger.eventId(0) ||
- QSUserSwitcherEvent.QS_USER_SWITCH.id == uiEventLogger.eventId(0)
- )
- }
-
- @Test
- fun testRemoveGuest_removeButtonPressed_dialogDismissed() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestInfo.id)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- userSwitcherController.mExitGuestDialog
- .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
- testableLooper.processAllMessages()
- assertFalse(userSwitcherController.mExitGuestDialog.isShowing)
- }
-
- @Test
- fun testRemoveGuest_dialogShowerUsed() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestInfo.id)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, dialogShower)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- testableLooper.processAllMessages()
- verify(dialogShower)
- .showDialog(
- userSwitcherController.mExitGuestDialog,
- DialogCuj(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, "exit_guest_mode"))
- }
-
- @Test
- fun testRemoveGuest_cancelButtonPressed_isNotLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- true /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestId)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
- assertNotNull(userSwitcherController.mExitGuestDialog)
- userSwitcherController.mExitGuestDialog
- .getButton(DialogInterface.BUTTON_NEUTRAL).performClick()
- testableLooper.processAllMessages()
- assertEquals(0, uiEventLogger.numLogs())
- }
-
- @Test
- fun testWipeGuest_startOverButtonPressed_isLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestId)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- // Simulate that guest user has already logged in
- `when`(secureSettings.getIntForUser(
- eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
- .thenReturn(1)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
- // Simulate a user switch event
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
- userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
- userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
- .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_WIPE).performClick()
- testableLooper.processAllMessages()
- assertEquals(1, uiEventLogger.numLogs())
- assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_WIPE.id, uiEventLogger.eventId(0))
- }
-
- @Test
- fun testWipeGuest_continueButtonPressed_isLogged() {
- val currentGuestUserRecord =
- UserRecord(
- guestInfo,
- picture,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
- `when`(userTracker.userId).thenReturn(guestId)
- `when`(userTracker.userInfo).thenReturn(guestInfo)
-
- // Simulate that guest user has already logged in
- `when`(secureSettings.getIntForUser(
- eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
- .thenReturn(1)
-
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
-
- // Simulate a user switch event
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
- userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
-
- assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
- userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
- .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_DONTWIPE)
- .performClick()
- testableLooper.processAllMessages()
- assertEquals(1, uiEventLogger.numLogs())
- assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_CONTINUE.id, uiEventLogger.eventId(0))
- }
-
- @Test
- fun test_getCurrentUserName_shouldReturnNameOfTheCurrentUser() {
- fun addUser(id: Int, name: String, isCurrent: Boolean) {
- userSwitcherController.users.add(
- UserRecord(
- UserInfo(id, name, 0),
- null, false, isCurrent, false,
- false, false, false
- )
- )
- }
- val bgUserName = "background_user"
- val fgUserName = "foreground_user"
-
- addUser(1, bgUserName, false)
- addUser(2, fgUserName, true)
-
- assertEquals(fgUserName, userSwitcherController.currentUserName)
- }
-
- @Test
- fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() {
- `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM)
- assertEquals(true, userSwitcherController.isSystemUser)
- }
-
- @Test
- fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() {
- `when`(userTracker.userId).thenReturn(1)
- assertEquals(false, userSwitcherController.isSystemUser)
- }
-
- @Test
- fun testCanCreateSupervisedUserWithConfiguredPackage() {
- // GIVEN the supervised user creation package is configured
- `when`(context.getString(
- com.android.internal.R.string.config_supervisedUserCreationPackage))
- .thenReturn("some_pkg")
-
- // AND the current user is allowed to create new users
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- // WHEN the controller is started with the above config
- setupController()
- testableLooper.processAllMessages()
-
- // THEN a supervised user can be constructed
- assertTrue(userSwitcherController.canCreateSupervisedUser())
- }
-
- @Test
- fun testCannotCreateSupervisedUserWithConfiguredPackage() {
- // GIVEN the supervised user creation package is NOT configured
- `when`(context.getString(
- com.android.internal.R.string.config_supervisedUserCreationPackage))
- .thenReturn(null)
-
- // AND the current user is allowed to create new users
- `when`(userTracker.userId).thenReturn(ownerId)
- `when`(userTracker.userInfo).thenReturn(ownerInfo)
-
- // WHEN the controller is started with the above config
- setupController()
- testableLooper.processAllMessages()
-
- // THEN a supervised user can NOT be constructed
- assertFalse(userSwitcherController.canCreateSupervisedUser())
- }
-
- @Test
- fun testCannotCreateUserWhenUserSwitcherDisabled() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
- setupController()
- assertFalse(userSwitcherController.canCreateUser())
- }
-
- @Test
- fun testCannotCreateGuestUserWhenUserSwitcherDisabled() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
- setupController()
- assertFalse(userSwitcherController.canCreateGuest(false))
- }
-
- @Test
- fun testCannotCreateSupervisedUserWhenUserSwitcherDisabled() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
- setupController()
- assertFalse(userSwitcherController.canCreateSupervisedUser())
- }
-
- @Test
- fun testCanManageUser_userSwitcherEnabled_addUserWhenLocked() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
- setupController()
- assertTrue(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun testCanManageUser_userSwitcherDisabled_addUserWhenLocked() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
-
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.ADD_USERS_WHEN_LOCKED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
- setupController()
- assertFalse(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun testCanManageUser_userSwitcherEnabled_isAdmin() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(1)
-
- setupController()
- assertTrue(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun testCanManageUser_userSwitcherDisabled_isAdmin() {
- `when`(
- globalSettings.getIntForUser(
- eq(Settings.Global.USER_SWITCHER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_SYSTEM)
- )
- ).thenReturn(0)
-
- setupController()
- assertFalse(userSwitcherController.canManageUsers())
- }
-
- @Test
- fun addUserSwitchCallback() {
- val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
- verify(broadcastDispatcher).registerReceiver(
- capture(broadcastReceiverCaptor),
- any(),
- nullable(), nullable(), anyInt(), nullable())
-
- val cb = mock(UserSwitcherController.UserSwitchCallback::class.java)
- userSwitcherController.addUserSwitchCallback(cb)
-
- val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
- broadcastReceiverCaptor.value.onReceive(context, intent)
- verify(cb).onUserSwitched()
- }
-
- @Test
- fun onUserItemClicked_guest_runsOnBgThread() {
- val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
- val guestUserRecord = UserRecord(
- null,
- picture,
- true /* guest */,
- false /* current */,
- false /* isAddUser */,
- false /* isRestricted */,
- true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */
- )
-
- userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
- assertTrue(bgExecutor.numPending() > 0)
- verify(userManager, never()).createGuest(context)
- bgExecutor.runAllReady()
- verify(userManager).createGuest(context)
- }
-
- @Test
- fun onUserItemClicked_manageUsers() {
- val manageUserRecord = LegacyUserDataHelper.createRecord(
- mContext,
- ownerId,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- isRestricted = false,
- isSwitchToEnabled = true
- )
-
- userSwitcherController.onUserListItemClicked(manageUserRecord, null)
- val intentCaptor = kotlinArgumentCaptor<Intent>()
- verify(activityStarter).startActivity(intentCaptor.capture(),
- eq(true)
- )
- Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 3fe1a9f..c06dbdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -35,6 +35,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.ZenModeController.Callback;
import com.android.systemui.util.settings.FakeSettings;
@@ -58,6 +59,8 @@
BroadcastDispatcher mBroadcastDispatcher;
@Mock
DumpManager mDumpManager;
+ @Mock
+ UserTracker mUserTracker;
private ZenModeControllerImpl mController;
@@ -72,7 +75,8 @@
Handler.createAsync(Looper.myLooper()),
mBroadcastDispatcher,
mDumpManager,
- new FakeSettings());
+ new FakeSettings(),
+ mUserTracker);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
index 05512e5..0d19ab1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleControllerTest.kt
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.Color
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
new file mode 100644
index 0000000..2024d53
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/MultiRippleViewTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.ripple
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MultiRippleViewTest : SysuiTestCase() {
+ private val fakeSystemClock = FakeSystemClock()
+ // FakeExecutor is needed to run animator.
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Test
+ fun onRippleFinishes_triggersRippleFinished() {
+ val multiRippleView = MultiRippleView(context, null)
+ val multiRippleController = MultiRippleController(multiRippleView)
+ val rippleAnimationConfig = RippleAnimationConfig(duration = 1000L)
+
+ var isTriggered = false
+ val listener =
+ object : MultiRippleView.Companion.RipplesFinishedListener {
+ override fun onRipplesFinish() {
+ isTriggered = true
+ }
+ }
+ multiRippleView.addRipplesFinishedListener(listener)
+
+ fakeExecutor.execute {
+ val rippleAnimation = RippleAnimation(rippleAnimationConfig)
+ multiRippleController.play(rippleAnimation)
+
+ fakeSystemClock.advanceTime(rippleAnimationConfig.duration)
+
+ assertThat(isTriggered).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
index 7662282..756397a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.graphics.Color
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
index 2d2f4cc..1e5ab7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleViewTest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.ripple
+package com.android.systemui.surfaceeffects.ripple
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
@@ -21,12 +21,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
@SmallTest
@RunWith(AndroidTestingRunner::class)
class RippleViewTest : SysuiTestCase() {
- @Mock
private lateinit var rippleView: RippleView
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
new file mode 100644
index 0000000..d25c8c1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseControllerTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TurbulenceNoiseControllerTest : SysuiTestCase() {
+ private val fakeSystemClock = FakeSystemClock()
+ // FakeExecutor is needed to run animator.
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Test
+ fun play_playsTurbulenceNoise() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+ fakeExecutor.execute {
+ turbulenceNoiseController.play(config)
+
+ assertThat(turbulenceNoiseView.isPlaying).isTrue()
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(turbulenceNoiseView.isPlaying).isFalse()
+ }
+ }
+
+ @Test
+ fun updateColor_updatesCorrectColor() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f, color = Color.WHITE)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+ val expectedColor = Color.RED
+
+ val turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+ fakeExecutor.execute {
+ turbulenceNoiseController.play(config)
+
+ turbulenceNoiseView.updateColor(expectedColor)
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(config.color).isEqualTo(expectedColor)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
new file mode 100644
index 0000000..633aac0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseViewTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.surfaceeffects.turbulencenoise
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TurbulenceNoiseViewTest : SysuiTestCase() {
+
+ private val fakeSystemClock = FakeSystemClock()
+ // FakeExecutor is needed to run animator.
+ private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+ @Test
+ fun play_viewHasCorrectVisibility() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+
+ fakeExecutor.execute {
+ turbulenceNoiseView.play(config)
+
+ assertThat(turbulenceNoiseView.visibility).isEqualTo(View.VISIBLE)
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+ }
+ }
+
+ @Test
+ fun play_playsAnimation() {
+ val config = TurbulenceNoiseAnimationConfig(duration = 1000f)
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ fakeExecutor.execute {
+ turbulenceNoiseView.play(config)
+
+ assertThat(turbulenceNoiseView.isPlaying).isTrue()
+ }
+ }
+
+ @Test
+ fun play_onEnd_triggersOnAnimationEnd() {
+ var animationEnd = false
+ val config =
+ TurbulenceNoiseAnimationConfig(
+ duration = 1000f,
+ onAnimationEnd = { animationEnd = true }
+ )
+ val turbulenceNoiseView = TurbulenceNoiseView(context, null)
+
+ fakeExecutor.execute {
+ turbulenceNoiseView.play(config)
+
+ assertThat(turbulenceNoiseView.isPlaying).isTrue()
+
+ fakeSystemClock.advanceTime(config.duration.toLong())
+
+ assertThat(animationEnd).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 8572478..09f0d4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -119,7 +119,7 @@
)
)
- verify(logger).logViewAddition("Fake Window Title")
+ verify(logger).logViewAddition("id", "Fake Window Title")
}
@Test
@@ -153,7 +153,7 @@
underTest.displayView(getState())
assertThat(fakeWakeLock.isHeld).isTrue()
- underTest.removeView("test reason")
+ underTest.removeView("id", "test reason")
assertThat(fakeWakeLock.isHeld).isFalse()
}
@@ -263,21 +263,143 @@
}
@Test
+ fun multipleViewsWithDifferentIds_recentActiveViewIsDisplayed() {
+ underTest.displayView(ViewInfo("First name", id = "id1"))
+
+ verify(windowManager).addView(any(), any())
+
+ reset(windowManager)
+ underTest.displayView(ViewInfo("Second name", id = "id2"))
+ underTest.removeView("id2", "test reason")
+
+ verify(windowManager).removeView(any())
+
+ fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+ assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
+ assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
+
+ reset(windowManager)
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ verify(windowManager).removeView(any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun multipleViewsWithDifferentIds_oldViewRemoved_recentViewIsDisplayed() {
+ underTest.displayView(ViewInfo("First name", id = "id1"))
+
+ verify(windowManager).addView(any(), any())
+
+ reset(windowManager)
+ underTest.displayView(ViewInfo("Second name", id = "id2"))
+ underTest.removeView("id1", "test reason")
+
+ verify(windowManager, never()).removeView(any())
+ assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
+ assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ verify(windowManager).removeView(any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun multipleViewsWithDifferentIds_threeDifferentViews_recentActiveViewIsDisplayed() {
+ underTest.displayView(ViewInfo("First name", id = "id1"))
+ underTest.displayView(ViewInfo("Second name", id = "id2"))
+ underTest.displayView(ViewInfo("Third name", id = "id3"))
+
+ verify(windowManager).addView(any(), any())
+
+ reset(windowManager)
+ underTest.removeView("id3", "test reason")
+
+ verify(windowManager).removeView(any())
+
+ fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+ assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id2")
+ assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("Second name")
+
+ reset(windowManager)
+ underTest.removeView("id2", "test reason")
+
+ verify(windowManager).removeView(any())
+
+ fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+ assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
+ assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("First name")
+
+ reset(windowManager)
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ verify(windowManager).removeView(any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun multipleViewsWithDifferentIds_oneViewStateChanged_stackHasRecentState() {
+ underTest.displayView(ViewInfo("First name", id = "id1"))
+ underTest.displayView(ViewInfo("New name", id = "id1"))
+
+ verify(windowManager).addView(any(), any())
+
+ reset(windowManager)
+ underTest.displayView(ViewInfo("Second name", id = "id2"))
+ underTest.removeView("id2", "test reason")
+
+ verify(windowManager).removeView(any())
+
+ fakeClock.advanceTime(DISPLAY_VIEW_DELAY + 1)
+
+ assertThat(underTest.mostRecentViewInfo?.id).isEqualTo("id1")
+ assertThat(underTest.mostRecentViewInfo?.name).isEqualTo("New name")
+ assertThat(underTest.activeViews[0].second.name).isEqualTo("New name")
+
+ reset(windowManager)
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ verify(windowManager).removeView(any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
+ fun multipleViewsWithDifferentIds_viewsTimeouts_noViewLeftToDisplay() {
+ underTest.displayView(ViewInfo("First name", id = "id1"))
+ fakeClock.advanceTime(TIMEOUT_MS / 3)
+ underTest.displayView(ViewInfo("Second name", id = "id2"))
+ fakeClock.advanceTime(TIMEOUT_MS / 3)
+ underTest.displayView(ViewInfo("Third name", id = "id3"))
+
+ reset(windowManager)
+ fakeClock.advanceTime(TIMEOUT_MS + 1)
+
+ verify(windowManager).removeView(any())
+ verify(windowManager, never()).addView(any(), any())
+ assertThat(underTest.activeViews.size).isEqualTo(0)
+ }
+
+ @Test
fun removeView_viewRemovedAndRemovalLogged() {
// First, add the view
underTest.displayView(getState())
// Then, remove it
val reason = "test reason"
- underTest.removeView(reason)
+ val deviceId = "id"
+ underTest.removeView(deviceId, reason)
verify(windowManager).removeView(any())
- verify(logger).logViewRemoval(reason)
+ verify(logger).logViewRemoval(deviceId, reason)
}
@Test
fun removeView_noAdd_viewNotRemoved() {
- underTest.removeView("reason")
+ underTest.removeView("id", "reason")
verify(windowManager, never()).removeView(any())
}
@@ -329,7 +451,8 @@
val name: String,
override val windowTitle: String = "Window Title",
override val wakeReason: String = "WAKE_REASON",
- override val timeoutMs: Int = 1
+ override val timeoutMs: Int = 1,
+ override val id: String = "id",
) : TemporaryViewInfo()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index d155050..116b8fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -44,7 +44,7 @@
@Test
fun logViewAddition_bufferHasLog() {
- logger.logViewAddition("Test Window Title")
+ logger.logViewAddition("test id", "Test Window Title")
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -57,7 +57,8 @@
@Test
fun logViewRemoval_bufferHasTagAndReason() {
val reason = "test reason"
- logger.logViewRemoval(reason)
+ val deviceId = "test id"
+ logger.logViewRemoval(deviceId, reason)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -65,6 +66,7 @@
assertThat(actualString).contains(TAG)
assertThat(actualString).contains(reason)
+ assertThat(actualString).contains(deviceId)
}
}
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 8e37aa2..47c84ab 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
@@ -377,6 +377,7 @@
windowTitle = WINDOW_TITLE,
wakeReason = WAKE_REASON,
timeoutMs = TIMEOUT,
+ id = DEVICE_ID,
)
}
@@ -401,3 +402,4 @@
private const val TIMEOUT = 10000
private const val WINDOW_TITLE = "Test Chipbar Window Title"
private const val WAKE_REASON = "TEST_CHIPBAR_WAKE_REASON"
+private const val DEVICE_ID = "id"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 797f86a..27957ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -62,7 +62,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.Before;
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 8645298..89402de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -88,6 +88,7 @@
deviceStates = FoldableTestUtils.findDeviceStates(context)
+ // TODO(b/254878364): remove this call to NPVC.getView()
whenever(notificationPanelViewController.view).thenReturn(viewGroup)
whenever(viewGroup.viewTreeObserver).thenReturn(viewTreeObserver)
whenever(wakefulnessLifecycle.lastSleepReason)
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 5509a6ca..03fd624 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
@@ -124,8 +124,11 @@
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
{ foldStateProvider.sendHingeAngleUpdate(10f) },
{ foldStateProvider.sendHingeAngleUpdate(90f) },
- { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
- { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING) },
+ {
+ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+ // Start closing immediately after we opened, before the animation ended
+ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING)
+ },
{ foldStateProvider.sendHingeAngleUpdate(60f) },
{ foldStateProvider.sendHingeAngleUpdate(10f) },
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index fc2a78a..e1e54a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -15,14 +15,13 @@
*/
package com.android.systemui.unfold.util
-import android.animation.ValueAnimator
import android.content.ContentResolver
import android.database.ContentObserver
+import android.provider.Settings
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.util.mockito.any
import org.junit.Before
@@ -30,6 +29,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
+import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
@@ -38,30 +38,25 @@
@SmallTest
class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
- @Mock
- lateinit var contentResolver: ContentResolver
-
- @Mock
- lateinit var sinkProvider: TransitionProgressListener
+ @Mock lateinit var sinkProvider: TransitionProgressListener
private val sourceProvider = TestUnfoldTransitionProvider()
- lateinit var progressProvider: ScaleAwareTransitionProgressProvider
+ private lateinit var contentResolver: ContentResolver
+ private lateinit var progressProvider: ScaleAwareTransitionProgressProvider
private val animatorDurationScaleListenerCaptor =
- ArgumentCaptor.forClass(ContentObserver::class.java)
+ ArgumentCaptor.forClass(ContentObserver::class.java)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ contentResolver = spy(context.contentResolver)
- progressProvider = ScaleAwareTransitionProgressProvider(
- sourceProvider,
- contentResolver
- )
+ progressProvider = ScaleAwareTransitionProgressProvider(sourceProvider, contentResolver)
- verify(contentResolver).registerContentObserver(any(), any(),
- animatorDurationScaleListenerCaptor.capture())
+ verify(contentResolver)
+ .registerContentObserver(any(), any(), animatorDurationScaleListenerCaptor.capture())
progressProvider.addCallback(sinkProvider)
}
@@ -121,12 +116,20 @@
}
private fun setAnimationsEnabled(enabled: Boolean) {
- val durationScale = if (enabled) {
- 1f
- } else {
- 0f
- }
- ValueAnimator.setDurationScale(durationScale)
+ val durationScale =
+ if (enabled) {
+ 1f
+ } else {
+ 0f
+ }
+
+ // It uses [TestableSettingsProvider] and it will be cleared after the test
+ Settings.Global.putString(
+ contentResolver,
+ Settings.Global.ANIMATOR_DURATION_SCALE,
+ durationScale.toString()
+ )
+
animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
deleted file mode 100644
index 7c7f0e1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.user.data.repository
-
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
-import android.provider.Settings
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() {
-
- @Before
- fun setUp() {
- super.setUp(isRefactored = true)
- }
-
- @Test
- fun userSwitcherSettings() = runSelfCancelingTest {
- setUpGlobalSettings(
- isSimpleUserSwitcher = true,
- isAddUsersFromLockscreen = true,
- isUserSwitcherEnabled = true,
- )
- underTest = create(this)
-
- var value: UserSwitcherSettingsModel? = null
- underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
-
- assertUserSwitcherSettings(
- model = value,
- expectedSimpleUserSwitcher = true,
- expectedAddUsersFromLockscreen = true,
- expectedUserSwitcherEnabled = true,
- )
-
- setUpGlobalSettings(
- isSimpleUserSwitcher = false,
- isAddUsersFromLockscreen = true,
- isUserSwitcherEnabled = true,
- )
- assertUserSwitcherSettings(
- model = value,
- expectedSimpleUserSwitcher = false,
- expectedAddUsersFromLockscreen = true,
- expectedUserSwitcherEnabled = true,
- )
- }
-
- @Test
- fun refreshUsers() = runSelfCancelingTest {
- underTest = create(this)
- val initialExpectedValue =
- setUpUsers(
- count = 3,
- selectedIndex = 0,
- )
- var userInfos: List<UserInfo>? = null
- var selectedUserInfo: UserInfo? = null
- underTest.userInfos.onEach { userInfos = it }.launchIn(this)
- underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
-
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(initialExpectedValue)
- assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
- assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
- val secondExpectedValue =
- setUpUsers(
- count = 4,
- selectedIndex = 1,
- )
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(secondExpectedValue)
- assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
- assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
-
- val selectedNonGuestUserId = selectedUserInfo?.id
- val thirdExpectedValue =
- setUpUsers(
- count = 2,
- isLastGuestUser = true,
- selectedIndex = 1,
- )
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(thirdExpectedValue)
- assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
- assertThat(selectedUserInfo?.isGuest).isTrue()
- assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
- }
-
- @Test
- fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
- underTest = create(this)
- val unsortedUsers =
- setUpUsers(
- count = 3,
- selectedIndex = 0,
- isLastGuestUser = true,
- )
- unsortedUsers[0].creationTime = 999
- unsortedUsers[1].creationTime = 900
- unsortedUsers[2].creationTime = 950
- val expectedUsers =
- listOf(
- unsortedUsers[1],
- unsortedUsers[0],
- unsortedUsers[2], // last because this is the guest
- )
- var userInfos: List<UserInfo>? = null
- underTest.userInfos.onEach { userInfos = it }.launchIn(this)
-
- underTest.refreshUsers()
- assertThat(userInfos).isEqualTo(expectedUsers)
- }
-
- @Test
- fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
- underTest = create(this)
- var selectedUserInfo: UserInfo? = null
- underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
- setUpUsers(
- count = 2,
- selectedIndex = 0,
- )
- tracker.onProfileChanged()
- assertThat(selectedUserInfo?.id == 0)
- setUpUsers(
- count = 2,
- selectedIndex = 1,
- )
- tracker.onProfileChanged()
- assertThat(selectedUserInfo?.id == 1)
- }
-
- private fun setUpUsers(
- count: Int,
- isLastGuestUser: Boolean = false,
- selectedIndex: Int = 0,
- ): List<UserInfo> {
- val userInfos =
- (0 until count).map { index ->
- createUserInfo(
- index,
- isGuest = isLastGuestUser && index == count - 1,
- )
- }
- whenever(manager.aliveUsers).thenReturn(userInfos)
- tracker.set(userInfos, selectedIndex)
- return userInfos
- }
-
- private fun createUserInfo(
- id: Int,
- isGuest: Boolean,
- ): UserInfo {
- val flags = 0
- return UserInfo(
- id,
- "user_$id",
- /* iconPath= */ "",
- flags,
- if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
- )
- }
-
- private fun setUpGlobalSettings(
- isSimpleUserSwitcher: Boolean = false,
- isAddUsersFromLockscreen: Boolean = false,
- isUserSwitcherEnabled: Boolean = true,
- ) {
- context.orCreateTestableResources.addOverride(
- com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
- true,
- )
- globalSettings.putIntForUser(
- UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
- if (isSimpleUserSwitcher) 1 else 0,
- UserHandle.USER_SYSTEM,
- )
- globalSettings.putIntForUser(
- Settings.Global.ADD_USERS_WHEN_LOCKED,
- if (isAddUsersFromLockscreen) 1 else 0,
- UserHandle.USER_SYSTEM,
- )
- globalSettings.putIntForUser(
- Settings.Global.USER_SWITCHER_ENABLED,
- if (isUserSwitcherEnabled) 1 else 0,
- UserHandle.USER_SYSTEM,
- )
- }
-
- private fun assertUserSwitcherSettings(
- model: UserSwitcherSettingsModel?,
- expectedSimpleUserSwitcher: Boolean,
- expectedAddUsersFromLockscreen: Boolean,
- expectedUserSwitcherEnabled: Boolean,
- ) {
- checkNotNull(model)
- assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
- assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
- assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
- }
-
- /**
- * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
- * is then automatically canceled and cleaned-up.
- */
- private fun runSelfCancelingTest(
- block: suspend CoroutineScope.() -> Unit,
- ) =
- runBlocking(Dispatchers.Main.immediate) {
- val scope = CoroutineScope(coroutineContext + Job())
- block(scope)
- scope.cancel()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index dcea83a..034c618 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,54 +17,281 @@
package com.android.systemui.user.data.repository
+import android.content.pm.UserInfo
+import android.os.UserHandle
import android.os.UserManager
+import android.provider.Settings
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
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.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineScope
+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
-abstract class UserRepositoryImplTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserRepositoryImplTest : SysuiTestCase() {
- @Mock protected lateinit var manager: UserManager
- @Mock protected lateinit var controller: UserSwitcherController
+ @Mock private lateinit var manager: UserManager
- protected lateinit var underTest: UserRepositoryImpl
+ private lateinit var underTest: UserRepositoryImpl
- protected lateinit var globalSettings: FakeSettings
- protected lateinit var tracker: FakeUserTracker
- protected lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var globalSettings: FakeSettings
+ private lateinit var tracker: FakeUserTracker
- protected fun setUp(isRefactored: Boolean) {
+ @Before
+ fun setUp() {
MockitoAnnotations.initMocks(this)
globalSettings = FakeSettings()
tracker = FakeUserTracker()
- featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored)
}
- protected fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+ @Test
+ fun userSwitcherSettings() = runSelfCancelingTest {
+ setUpGlobalSettings(
+ isSimpleUserSwitcher = true,
+ isAddUsersFromLockscreen = true,
+ isUserSwitcherEnabled = true,
+ )
+ underTest = create(this)
+
+ var value: UserSwitcherSettingsModel? = null
+ underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+
+ assertUserSwitcherSettings(
+ model = value,
+ expectedSimpleUserSwitcher = true,
+ expectedAddUsersFromLockscreen = true,
+ expectedUserSwitcherEnabled = true,
+ )
+
+ setUpGlobalSettings(
+ isSimpleUserSwitcher = false,
+ isAddUsersFromLockscreen = true,
+ isUserSwitcherEnabled = true,
+ )
+ assertUserSwitcherSettings(
+ model = value,
+ expectedSimpleUserSwitcher = false,
+ expectedAddUsersFromLockscreen = true,
+ expectedUserSwitcherEnabled = true,
+ )
+ }
+
+ @Test
+ fun userSwitcherSettings_isUserSwitcherEnabled_notInitialized() = runSelfCancelingTest {
+ underTest = create(this)
+
+ var value: UserSwitcherSettingsModel? = null
+ underTest.userSwitcherSettings.onEach { value = it }.launchIn(this)
+
+ assertUserSwitcherSettings(
+ model = value,
+ expectedSimpleUserSwitcher = false,
+ expectedAddUsersFromLockscreen = false,
+ expectedUserSwitcherEnabled =
+ context.resources.getBoolean(
+ com.android.internal.R.bool.config_showUserSwitcherByDefault
+ ),
+ )
+ }
+
+ @Test
+ fun refreshUsers() = runSelfCancelingTest {
+ underTest = create(this)
+ val initialExpectedValue =
+ setUpUsers(
+ count = 3,
+ selectedIndex = 0,
+ )
+ var userInfos: List<UserInfo>? = null
+ var selectedUserInfo: UserInfo? = null
+ underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+ underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(initialExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0])
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+ val secondExpectedValue =
+ setUpUsers(
+ count = 4,
+ selectedIndex = 1,
+ )
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(secondExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1])
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id)
+
+ val selectedNonGuestUserId = selectedUserInfo?.id
+ val thirdExpectedValue =
+ setUpUsers(
+ count = 2,
+ isLastGuestUser = true,
+ selectedIndex = 1,
+ )
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(thirdExpectedValue)
+ assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1])
+ assertThat(selectedUserInfo?.isGuest).isTrue()
+ assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId)
+ }
+
+ @Test
+ fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest {
+ underTest = create(this)
+ val unsortedUsers =
+ setUpUsers(
+ count = 3,
+ selectedIndex = 0,
+ isLastGuestUser = true,
+ )
+ unsortedUsers[0].creationTime = 999
+ unsortedUsers[1].creationTime = 900
+ unsortedUsers[2].creationTime = 950
+ val expectedUsers =
+ listOf(
+ unsortedUsers[1],
+ unsortedUsers[0],
+ unsortedUsers[2], // last because this is the guest
+ )
+ var userInfos: List<UserInfo>? = null
+ underTest.userInfos.onEach { userInfos = it }.launchIn(this)
+
+ underTest.refreshUsers()
+ assertThat(userInfos).isEqualTo(expectedUsers)
+ }
+
+ private fun setUpUsers(
+ count: Int,
+ isLastGuestUser: Boolean = false,
+ selectedIndex: Int = 0,
+ ): List<UserInfo> {
+ val userInfos =
+ (0 until count).map { index ->
+ createUserInfo(
+ index,
+ isGuest = isLastGuestUser && index == count - 1,
+ )
+ }
+ whenever(manager.aliveUsers).thenReturn(userInfos)
+ tracker.set(userInfos, selectedIndex)
+ return userInfos
+ }
+ @Test
+ fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest {
+ underTest = create(this)
+ var selectedUserInfo: UserInfo? = null
+ underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this)
+ setUpUsers(
+ count = 2,
+ selectedIndex = 0,
+ )
+ tracker.onProfileChanged()
+ assertThat(selectedUserInfo?.id).isEqualTo(0)
+ setUpUsers(
+ count = 2,
+ selectedIndex = 1,
+ )
+ tracker.onProfileChanged()
+ assertThat(selectedUserInfo?.id).isEqualTo(1)
+ }
+
+ private fun createUserInfo(
+ id: Int,
+ isGuest: Boolean,
+ ): UserInfo {
+ val flags = 0
+ return UserInfo(
+ id,
+ "user_$id",
+ /* iconPath= */ "",
+ flags,
+ if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
+ )
+ }
+
+ private fun setUpGlobalSettings(
+ isSimpleUserSwitcher: Boolean = false,
+ isAddUsersFromLockscreen: Boolean = false,
+ isUserSwitcherEnabled: Boolean = true,
+ ) {
+ context.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_expandLockScreenUserSwitcher,
+ true,
+ )
+ globalSettings.putIntForUser(
+ UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER,
+ if (isSimpleUserSwitcher) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ globalSettings.putIntForUser(
+ Settings.Global.ADD_USERS_WHEN_LOCKED,
+ if (isAddUsersFromLockscreen) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ globalSettings.putIntForUser(
+ Settings.Global.USER_SWITCHER_ENABLED,
+ if (isUserSwitcherEnabled) 1 else 0,
+ UserHandle.USER_SYSTEM,
+ )
+ }
+
+ private fun assertUserSwitcherSettings(
+ model: UserSwitcherSettingsModel?,
+ expectedSimpleUserSwitcher: Boolean,
+ expectedAddUsersFromLockscreen: Boolean,
+ expectedUserSwitcherEnabled: Boolean,
+ ) {
+ checkNotNull(model)
+ assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher)
+ assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen)
+ assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled)
+ }
+
+ /**
+ * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which
+ * is then automatically canceled and cleaned-up.
+ */
+ private fun runSelfCancelingTest(
+ block: suspend CoroutineScope.() -> Unit,
+ ) =
+ runBlocking(Dispatchers.Main.immediate) {
+ val scope = CoroutineScope(coroutineContext + Job())
+ block(scope)
+ scope.cancel()
+ }
+
+ private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
return UserRepositoryImpl(
appContext = context,
manager = manager,
- controller = controller,
applicationScope = scope,
mainDispatcher = IMMEDIATE,
backgroundDispatcher = IMMEDIATE,
globalSettings = globalSettings,
tracker = tracker,
- featureFlags = featureFlags,
)
}
companion object {
- @JvmStatic protected val IMMEDIATE = Dispatchers.Main.immediate
+ @JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
deleted file mode 100644
index a363a03..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.user.data.repository
-
-import android.content.pm.UserInfo
-import androidx.test.filters.SmallTest
-import com.android.systemui.statusbar.policy.UserSwitcherController
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-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.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() {
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
-
- @Captor
- private lateinit var userSwitchCallbackCaptor:
- ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
-
- @Before
- fun setUp() {
- super.setUp(isRefactored = false)
-
- whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false))
- whenever(controller.isGuestUserAutoCreated).thenReturn(false)
- whenever(controller.isGuestUserResetting).thenReturn(false)
-
- underTest = create()
- }
-
- @Test
- fun `users - registers for updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.users.onEach {}.launchIn(this)
-
- verify(controller).addUserSwitchCallback(any())
-
- job.cancel()
- }
-
- @Test
- fun `users - unregisters from updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.users.onEach {}.launchIn(this)
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
- job.cancel()
-
- verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
- }
-
- @Test
- fun `users - does not include actions`() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createActionRecord(UserActionModel.ADD_USER),
- createUserRecord(1),
- createUserRecord(2),
- createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
- createActionRecord(UserActionModel.ENTER_GUEST_MODE),
- createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
- )
- )
- var models: List<UserModel>? = null
- val job = underTest.users.onEach { models = it }.launchIn(this)
-
- assertThat(models).hasSize(3)
- assertThat(models?.get(0)?.id).isEqualTo(0)
- assertThat(models?.get(0)?.isSelected).isTrue()
- assertThat(models?.get(1)?.id).isEqualTo(1)
- assertThat(models?.get(1)?.isSelected).isFalse()
- assertThat(models?.get(2)?.id).isEqualTo(2)
- assertThat(models?.get(2)?.isSelected).isFalse()
- job.cancel()
- }
-
- @Test
- fun selectedUser() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createUserRecord(1),
- createUserRecord(2),
- )
- )
- var id: Int? = null
- val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
-
- assertThat(id).isEqualTo(0)
-
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0),
- createUserRecord(1),
- createUserRecord(2, isSelected = true),
- )
- )
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
- userSwitchCallbackCaptor.value.onUserSwitched()
- assertThat(id).isEqualTo(2)
-
- job.cancel()
- }
-
- @Test
- fun `actions - unregisters from updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.actions.onEach {}.launchIn(this)
- verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
-
- job.cancel()
-
- verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
- }
-
- @Test
- fun `actions - registers for updates`() =
- runBlocking(IMMEDIATE) {
- val job = underTest.actions.onEach {}.launchIn(this)
-
- verify(controller).addUserSwitchCallback(any())
-
- job.cancel()
- }
-
- @Test
- fun `actions - does not include users`() =
- runBlocking(IMMEDIATE) {
- whenever(controller.users)
- .thenReturn(
- arrayListOf(
- createUserRecord(0, isSelected = true),
- createActionRecord(UserActionModel.ADD_USER),
- createUserRecord(1),
- createUserRecord(2),
- createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
- createActionRecord(UserActionModel.ENTER_GUEST_MODE),
- createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT),
- )
- )
- var models: List<UserActionModel>? = null
- val job = underTest.actions.onEach { models = it }.launchIn(this)
-
- assertThat(models).hasSize(4)
- assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
- assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
- assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
- assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
- job.cancel()
- }
-
- private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
- return UserRecord(
- info = UserInfo(id, "name$id", 0),
- isCurrent = isSelected,
- )
- }
-
- private fun createActionRecord(action: UserActionModel): UserRecord {
- return UserRecord(
- isAddUser = action == UserActionModel.ADD_USER,
- isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
- isGuest = action == UserActionModel.ENTER_GUEST_MODE,
- isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
deleted file mode 100644
index f682e31..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ /dev/null
@@ -1,740 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.user.domain.interactor
-
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.graphics.drawable.Drawable
-import android.os.UserHandle
-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.systemui.R
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.data.source.UserRecord
-import com.android.systemui.user.domain.model.ShowDialogRequestModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.advanceUntilIdle
-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.Mockito.never
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-class UserInteractorRefactoredTest : UserInteractorTest() {
-
- override fun isRefactored(): Boolean {
- return true
- }
-
- @Before
- override fun setUp() {
- super.setUp()
-
- overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
- overrideResource(R.dimen.max_avatar_size, 10)
- overrideResource(
- com.android.internal.R.string.config_supervisedUserCreationPackage,
- SUPERVISED_USER_CREATION_APP_PACKAGE,
- )
- whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
- whenever(manager.canAddMoreUsers(any())).thenReturn(true)
- }
-
- @Test
- fun `onRecordSelected - user`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
-
- verify(dialogShower).dismiss()
- verify(activityManager).switchUser(userInfos[1].id)
- Unit
- }
-
- @Test
- fun `onRecordSelected - switch to guest user`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- underTest.onRecordSelected(UserRecord(info = userInfos.last()))
-
- verify(activityManager).switchUser(userInfos.last().id)
- Unit
- }
-
- @Test
- fun `onRecordSelected - enter guest mode`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
- whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
-
- underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
-
- verify(dialogShower).dismiss()
- verify(manager).createGuest(any())
- Unit
- }
-
- @Test
- fun `onRecordSelected - action`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
-
- verify(dialogShower, never()).dismiss()
- verify(activityStarter).startActivity(any(), anyBoolean())
- }
-
- @Test
- fun `users - switcher enabled`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- assertUsers(models = value, count = 3, includeGuest = true)
-
- job.cancel()
- }
-
- @Test
- fun `users - switches to second user`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- userRepository.setSelectedUserInfo(userInfos[1])
-
- assertUsers(models = value, count = 2, selectedIndex = 1)
- job.cancel()
- }
-
- @Test
- fun `users - switcher not enabled`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
-
- var value: List<UserModel>? = null
- val job = underTest.users.onEach { value = it }.launchIn(this)
- assertUsers(models = value, count = 1)
-
- job.cancel()
- }
-
- @Test
- fun selectedUser() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
-
- var value: UserModel? = null
- val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
- assertUser(value, id = 0, isSelected = true)
-
- userRepository.setSelectedUserInfo(userInfos[1])
- assertUser(value, id = 1, isSelected = true)
-
- job.cancel()
- }
-
- @Test
- fun `actions - device unlocked`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
-
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `actions - device unlocked user not primary - empty list`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[1])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
- job.cancel()
- }
-
- @Test
- fun `actions - device unlocked user is guest - empty list`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = true)
- assertThat(userInfos[1].isGuest).isTrue()
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[1])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value).isEqualTo(emptyList<UserActionModel>())
-
- job.cancel()
- }
-
- @Test
- fun `actions - device locked add from lockscreen set - full list`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(
- UserSwitcherSettingsModel(
- isUserSwitcherEnabled = true,
- isAddUsersFromLockscreen = true,
- )
- )
- keyguardRepository.setKeyguardShowing(false)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `actions - device locked - only guest action and manage user is shown`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- keyguardRepository.setKeyguardShowing(true)
- var value: List<UserActionModel>? = null
- val job = underTest.actions.onEach { value = it }.launchIn(this)
-
- assertThat(value)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `executeAction - add user - dialog shown`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- keyguardRepository.setKeyguardShowing(false)
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
- val dialogShower: UserSwitchDialogController.DialogShower = mock()
-
- underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
- assertThat(dialogRequest)
- .isEqualTo(
- ShowDialogRequestModel.ShowAddUserDialog(
- userHandle = userInfos[0].userHandle,
- isKeyguardShowing = false,
- showEphemeralMessage = false,
- dialogShower = dialogShower,
- )
- )
-
- underTest.onDialogShown()
- assertThat(dialogRequest).isNull()
-
- job.cancel()
- }
-
- @Test
- fun `executeAction - add supervised user - starts activity`() =
- runBlocking(IMMEDIATE) {
- underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
- val intentCaptor = kotlinArgumentCaptor<Intent>()
- verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
- assertThat(intentCaptor.value.action)
- .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
- assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
- }
-
- @Test
- fun `executeAction - navigate to manage users`() =
- runBlocking(IMMEDIATE) {
- underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
- val intentCaptor = kotlinArgumentCaptor<Intent>()
- verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
- assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
- }
-
- @Test
- fun `executeAction - guest mode`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
- whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
- val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
- val showDialogsJob =
- underTest.dialogShowRequests
- .onEach {
- dialogRequests.add(it)
- if (it != null) {
- underTest.onDialogShown()
- }
- }
- .launchIn(this)
- val dismissDialogsJob =
- underTest.dialogDismissRequests
- .onEach {
- if (it != null) {
- underTest.onDialogDismissed()
- }
- }
- .launchIn(this)
-
- underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
- assertThat(dialogRequests)
- .contains(
- ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
- )
- verify(activityManager).switchUser(guestUserInfo.id)
-
- showDialogsJob.cancel()
- dismissDialogsJob.cancel()
- }
-
- @Test
- fun `selectUser - already selected guest re-selected - exit guest dialog`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = true)
- val guestUserInfo = userInfos[1]
- assertThat(guestUserInfo.isGuest).isTrue()
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(guestUserInfo)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
- underTest.selectUser(
- newlySelectedUserId = guestUserInfo.id,
- dialogShower = dialogShower,
- )
-
- assertThat(dialogRequest)
- .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
- verify(dialogShower, never()).dismiss()
- job.cancel()
- }
-
- @Test
- fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = true)
- val guestUserInfo = userInfos[1]
- assertThat(guestUserInfo.isGuest).isTrue()
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(guestUserInfo)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
- underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
-
- assertThat(dialogRequest)
- .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
- verify(dialogShower, never()).dismiss()
- job.cancel()
- }
-
- @Test
- fun `selectUser - not currently guest - switches users`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- var dialogRequest: ShowDialogRequestModel? = null
- val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
-
- underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
-
- assertThat(dialogRequest).isNull()
- verify(activityManager).switchUser(userInfos[1].id)
- verify(dialogShower).dismiss()
- job.cancel()
- }
-
- @Test
- fun `Telephony call state changes - refreshes users`() =
- runBlocking(IMMEDIATE) {
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- telephonyRepository.setCallState(1)
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `User switched broadcast`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- val callback1: UserInteractor.UserCallback = mock()
- val callback2: UserInteractor.UserCallback = mock()
- underTest.addCallback(callback1)
- underTest.addCallback(callback2)
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- userRepository.setSelectedUserInfo(userInfos[1])
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_SWITCHED)
- .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
- )
- }
-
- verify(callback1).onUserStateChanged()
- verify(callback2).onUserStateChanged()
- assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `User info changed broadcast`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_INFO_CHANGED),
- )
- }
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `System user unlocked broadcast - refresh users`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_UNLOCKED)
- .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
- )
- }
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
- }
-
- @Test
- fun `Non-system user unlocked broadcast - do not refresh users`() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 2, includeGuest = false)
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- val refreshUsersCallCount = userRepository.refreshUsersCallCount
-
- fakeBroadcastDispatcher.registeredReceivers.forEach {
- it.onReceive(
- context,
- Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
- )
- }
-
- assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
- }
-
- @Test
- fun userRecords() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = false)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- keyguardRepository.setKeyguardShowing(false)
-
- testCoroutineScope.advanceUntilIdle()
-
- assertRecords(
- records = underTest.userRecords.value,
- userIds = listOf(0, 1, 2),
- selectedUserIndex = 0,
- includeGuest = false,
- expectedActions =
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- ),
- )
- }
-
- @Test
- fun selectedUserRecord() =
- runBlocking(IMMEDIATE) {
- val userInfos = createUserInfos(count = 3, includeGuest = true)
- userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
- userRepository.setUserInfos(userInfos)
- userRepository.setSelectedUserInfo(userInfos[0])
- keyguardRepository.setKeyguardShowing(false)
-
- assertRecordForUser(
- record = underTest.selectedUserRecord.value,
- id = 0,
- hasPicture = true,
- isCurrent = true,
- isSwitchToEnabled = true,
- )
- }
-
- private fun assertUsers(
- models: List<UserModel>?,
- count: Int,
- selectedIndex: Int = 0,
- includeGuest: Boolean = false,
- ) {
- checkNotNull(models)
- assertThat(models.size).isEqualTo(count)
- models.forEachIndexed { index, model ->
- assertUser(
- model = model,
- id = index,
- isSelected = index == selectedIndex,
- isGuest = includeGuest && index == count - 1
- )
- }
- }
-
- private fun assertUser(
- model: UserModel?,
- id: Int,
- isSelected: Boolean = false,
- isGuest: Boolean = false,
- ) {
- checkNotNull(model)
- assertThat(model.id).isEqualTo(id)
- assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
- assertThat(model.isSelected).isEqualTo(isSelected)
- assertThat(model.isSelectable).isTrue()
- assertThat(model.isGuest).isEqualTo(isGuest)
- }
-
- private fun assertRecords(
- records: List<UserRecord>,
- userIds: List<Int>,
- selectedUserIndex: Int = 0,
- includeGuest: Boolean = false,
- expectedActions: List<UserActionModel> = emptyList(),
- ) {
- assertThat(records.size >= userIds.size).isTrue()
- userIds.indices.forEach { userIndex ->
- val record = records[userIndex]
- assertThat(record.info).isNotNull()
- val isGuest = includeGuest && userIndex == userIds.size - 1
- assertRecordForUser(
- record = record,
- id = userIds[userIndex],
- hasPicture = !isGuest,
- isCurrent = userIndex == selectedUserIndex,
- isGuest = isGuest,
- isSwitchToEnabled = true,
- )
- }
-
- assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
- (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
- val record = records[actionIndex]
- assertThat(record.info).isNull()
- assertRecordForAction(
- record = record,
- type = expectedActions[actionIndex - userIds.size],
- )
- }
- }
-
- private fun assertRecordForUser(
- record: UserRecord?,
- id: Int? = null,
- hasPicture: Boolean = false,
- isCurrent: Boolean = false,
- isGuest: Boolean = false,
- isSwitchToEnabled: Boolean = false,
- ) {
- checkNotNull(record)
- assertThat(record.info?.id).isEqualTo(id)
- assertThat(record.picture != null).isEqualTo(hasPicture)
- assertThat(record.isCurrent).isEqualTo(isCurrent)
- assertThat(record.isGuest).isEqualTo(isGuest)
- assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
- }
-
- private fun assertRecordForAction(
- record: UserRecord,
- type: UserActionModel,
- ) {
- assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
- assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
- assertThat(record.isAddSupervisedUser)
- .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
- }
-
- private fun createUserInfos(
- count: Int,
- includeGuest: Boolean,
- ): List<UserInfo> {
- return (0 until count).map { index ->
- val isGuest = includeGuest && index == count - 1
- createUserInfo(
- id = index,
- name =
- if (isGuest) {
- "guest"
- } else {
- "user_$index"
- },
- isPrimary = !isGuest && index == 0,
- isGuest = isGuest,
- )
- }
- }
-
- private fun createUserInfo(
- id: Int,
- name: String,
- isPrimary: Boolean = false,
- isGuest: Boolean = false,
- ): UserInfo {
- return UserInfo(
- id,
- name,
- /* iconPath= */ "",
- /* flags= */ if (isPrimary) {
- UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN
- } else {
- 0
- },
- if (isGuest) {
- UserManager.USER_TYPE_FULL_GUEST
- } else {
- UserManager.USER_TYPE_FULL_SYSTEM
- },
- )
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
- private val GUEST_ICON: Drawable = mock()
- private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
- }
-}
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 58f5531..78b0cbe 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
@@ -19,11 +19,23 @@
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
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
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Text
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -31,40 +43,77 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.source.UserRecord
+import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.shared.model.UserActionModel
+import com.android.systemui.user.shared.model.UserModel
+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.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.advanceUntilIdle
+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
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-abstract class UserInteractorTest : SysuiTestCase() {
+@SmallTest
+@RunWith(JUnit4::class)
+class UserInteractorTest : SysuiTestCase() {
- @Mock protected lateinit var controller: UserSwitcherController
- @Mock protected lateinit var activityStarter: ActivityStarter
- @Mock protected lateinit var manager: UserManager
- @Mock protected lateinit var activityManager: ActivityManager
- @Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController
- @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
- @Mock protected lateinit var uiEventLogger: UiEventLogger
- @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var manager: UserManager
+ @Mock private lateinit var activityManager: ActivityManager
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
- protected lateinit var underTest: UserInteractor
+ private lateinit var underTest: UserInteractor
- protected lateinit var testCoroutineScope: TestCoroutineScope
- protected lateinit var userRepository: FakeUserRepository
- protected lateinit var keyguardRepository: FakeKeyguardRepository
- protected lateinit var telephonyRepository: FakeTelephonyRepository
+ private lateinit var testCoroutineScope: TestCoroutineScope
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var telephonyRepository: FakeTelephonyRepository
+ private lateinit var featureFlags: FakeFeatureFlags
- abstract fun isRefactored(): Boolean
-
- open fun setUp() {
+ @Before
+ fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
+ whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+ overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
+ overrideResource(R.dimen.max_avatar_size, 10)
+ overrideResource(
+ com.android.internal.R.string.config_supervisedUserCreationPackage,
+ SUPERVISED_USER_CREATION_APP_PACKAGE,
+ )
+
+ featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
userRepository = FakeUserRepository()
keyguardRepository = FakeKeyguardRepository()
telephonyRepository = FakeTelephonyRepository()
@@ -79,16 +128,11 @@
UserInteractor(
applicationContext = context,
repository = userRepository,
- controller = controller,
activityStarter = activityStarter,
keyguardInteractor =
KeyguardInteractor(
repository = keyguardRepository,
),
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored())
- },
manager = manager,
applicationScope = testCoroutineScope,
telephonyInteractor =
@@ -113,11 +157,869 @@
uiEventLogger = uiEventLogger,
resumeSessionReceiver = resumeSessionReceiver,
resetOrExitSessionReceiver = resetOrExitSessionReceiver,
- )
+ ),
+ featureFlags = featureFlags,
)
}
+ @Test
+ fun `onRecordSelected - user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
+
+ verify(dialogShower).dismiss()
+ verify(activityManager).switchUser(userInfos[1].id)
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - switch to guest user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+
+ verify(activityManager).switchUser(userInfos.last().id)
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - enter guest mode`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+ whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+
+ underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
+
+ verify(dialogShower).dismiss()
+ verify(manager).createGuest(any())
+ Unit
+ }
+
+ @Test
+ fun `onRecordSelected - action`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
+
+ verify(dialogShower, never()).dismiss()
+ verify(activityStarter).startActivity(any(), anyBoolean())
+ }
+
+ @Test
+ fun `users - switcher enabled`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ assertUsers(models = value, count = 3, includeGuest = true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `users - switches to second user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ userRepository.setSelectedUserInfo(userInfos[1])
+
+ assertUsers(models = value, count = 2, selectedIndex = 1)
+ job.cancel()
+ }
+
+ @Test
+ fun `users - switcher not enabled`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+
+ var value: List<UserModel>? = null
+ val job = underTest.users.onEach { value = it }.launchIn(this)
+ assertUsers(models = value, count = 1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun selectedUser() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var value: UserModel? = null
+ val job = underTest.selectedUser.onEach { value = it }.launchIn(this)
+ assertUser(value, id = 0, isSelected = true)
+
+ userRepository.setSelectedUserInfo(userInfos[1])
+ assertUser(value, id = 1, isSelected = true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked - full screen`() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked user not primary - empty list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device unlocked user is guest - empty list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ assertThat(userInfos[1].isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value).isEqualTo(emptyList<UserActionModel>())
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device locked add from lockscreen set - full list`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ isAddUsersFromLockscreen = true,
+ )
+ )
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device locked add from lockscreen set - full list - full screen`() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ isAddUsersFromLockscreen = true,
+ )
+ )
+ keyguardRepository.setKeyguardShowing(false)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value)
+ .isEqualTo(
+ listOf(
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ )
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `actions - device locked - only manage user is shown`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ keyguardRepository.setKeyguardShowing(true)
+ var value: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { value = it }.launchIn(this)
+
+ assertThat(value).isEqualTo(listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT))
+
+ job.cancel()
+ }
+
+ @Test
+ fun `executeAction - add user - dialog shown`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+ val dialogShower: UserSwitchDialogController.DialogShower = mock()
+
+ underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
+ assertThat(dialogRequest)
+ .isEqualTo(
+ ShowDialogRequestModel.ShowAddUserDialog(
+ userHandle = userInfos[0].userHandle,
+ isKeyguardShowing = false,
+ showEphemeralMessage = false,
+ dialogShower = dialogShower,
+ )
+ )
+
+ underTest.onDialogShown()
+ assertThat(dialogRequest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `executeAction - add supervised user - starts activity`() =
+ runBlocking(IMMEDIATE) {
+ underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
+
+ val intentCaptor = kotlinArgumentCaptor<Intent>()
+ verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+ assertThat(intentCaptor.value.action)
+ .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER)
+ assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE)
+ }
+
+ @Test
+ fun `executeAction - navigate to manage users`() =
+ runBlocking(IMMEDIATE) {
+ underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
+
+ val intentCaptor = kotlinArgumentCaptor<Intent>()
+ verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
+ assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
+ }
+
+ @Test
+ fun `executeAction - guest mode`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true)
+ whenever(manager.createGuest(any())).thenReturn(guestUserInfo)
+ val dialogRequests = mutableListOf<ShowDialogRequestModel?>()
+ val showDialogsJob =
+ underTest.dialogShowRequests
+ .onEach {
+ dialogRequests.add(it)
+ if (it != null) {
+ underTest.onDialogShown()
+ }
+ }
+ .launchIn(this)
+ val dismissDialogsJob =
+ underTest.dialogDismissRequests
+ .onEach {
+ if (it != null) {
+ underTest.onDialogDismissed()
+ }
+ }
+ .launchIn(this)
+
+ underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
+
+ assertThat(dialogRequests)
+ .contains(
+ ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
+ )
+ verify(activityManager).switchUser(guestUserInfo.id)
+
+ showDialogsJob.cancel()
+ dismissDialogsJob.cancel()
+ }
+
+ @Test
+ fun `selectUser - already selected guest re-selected - exit guest dialog`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ val guestUserInfo = userInfos[1]
+ assertThat(guestUserInfo.isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(guestUserInfo)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(
+ newlySelectedUserId = guestUserInfo.id,
+ dialogShower = dialogShower,
+ )
+
+ assertThat(dialogRequest)
+ .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+ verify(dialogShower, never()).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `selectUser - currently guest non-guest selected - exit guest dialog`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = true)
+ val guestUserInfo = userInfos[1]
+ assertThat(guestUserInfo.isGuest).isTrue()
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(guestUserInfo)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower)
+
+ assertThat(dialogRequest)
+ .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
+ verify(dialogShower, never()).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `selectUser - not currently guest - switches users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ var dialogRequest: ShowDialogRequestModel? = null
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower)
+
+ assertThat(dialogRequest).isNull()
+ verify(activityManager).switchUser(userInfos[1].id)
+ verify(dialogShower).dismiss()
+ job.cancel()
+ }
+
+ @Test
+ fun `Telephony call state changes - refreshes users`() =
+ runBlocking(IMMEDIATE) {
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ telephonyRepository.setCallState(1)
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `User switched broadcast`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ val callback1: UserInteractor.UserCallback = mock()
+ val callback2: UserInteractor.UserCallback = mock()
+ underTest.addCallback(callback1)
+ underTest.addCallback(callback2)
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ userRepository.setSelectedUserInfo(userInfos[1])
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_SWITCHED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id),
+ )
+ }
+
+ verify(callback1).onUserStateChanged()
+ verify(callback2).onUserStateChanged()
+ assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id)
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `User info changed broadcast`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_INFO_CHANGED),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `System user unlocked broadcast - refresh users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_UNLOCKED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
+ }
+
+ @Test
+ fun `Non-system user unlocked broadcast - do not refresh users`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ val refreshUsersCallCount = userRepository.refreshUsersCallCount
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach {
+ it.onReceive(
+ context,
+ Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337),
+ )
+ }
+
+ assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
+ }
+
+ @Test
+ fun userRecords() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+
+ testCoroutineScope.advanceUntilIdle()
+
+ assertRecords(
+ records = underTest.userRecords.value,
+ userIds = listOf(0, 1, 2),
+ selectedUserIndex = 0,
+ includeGuest = false,
+ expectedActions =
+ listOf(
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ ),
+ )
+ }
+
+ @Test
+ fun userRecordsFullScreen() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ val userInfos = createUserInfos(count = 3, includeGuest = false)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+
+ testCoroutineScope.advanceUntilIdle()
+
+ assertRecords(
+ records = underTest.userRecords.value,
+ userIds = listOf(0, 1, 2),
+ selectedUserIndex = 0,
+ includeGuest = false,
+ expectedActions =
+ listOf(
+ UserActionModel.ADD_USER,
+ UserActionModel.ADD_SUPERVISED_USER,
+ UserActionModel.ENTER_GUEST_MODE,
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
+ ),
+ )
+ }
+
+ @Test
+ fun selectedUserRecord() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertRecordForUser(
+ record = underTest.selectedUserRecord.value,
+ id = 0,
+ hasPicture = true,
+ isCurrent = true,
+ isSwitchToEnabled = true,
+ )
+ }
+
+ @Test
+ fun `users - secondary user - guest user can be switched to`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserModel>? = null
+ val job = underTest.users.onEach { res = it }.launchIn(this)
+ assertThat(res?.size == 3).isTrue()
+ assertThat(res?.find { it.isGuest }).isNotNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `users - secondary user - no guest action`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { res = it }.launchIn(this)
+ assertThat(res?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `users - secondary user - no guest user record`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserRecord>? = null
+ val job = underTest.userRecords.onEach { res = it }.launchIn(this)
+ assertThat(res?.find { it.isGuest }).isNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `show user switcher - full screen disabled - shows dialog switcher`() =
+ runBlocking(IMMEDIATE) {
+ var dialogRequest: ShowDialogRequestModel? = null
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
+
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ // Dialog is shown.
+ assertThat(dialogRequest).isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog)
+
+ underTest.onDialogShown()
+ assertThat(dialogRequest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `show user switcher - full screen enabled - launches activity`() {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
+
+ // Dialog is shown.
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(activityStarter)
+ .startActivity(
+ intentCaptor.capture(),
+ /* dismissShade= */ eq(true),
+ /* ActivityLaunchAnimator.Controller= */ nullable(),
+ /* showOverLockscreenWhenLocked= */ eq(true),
+ eq(UserHandle.SYSTEM),
+ )
+ assertThat(intentCaptor.value.component)
+ .isEqualTo(
+ ComponentName(
+ context,
+ UserSwitcherActivity::class.java,
+ )
+ )
+ }
+
+ @Test
+ fun `users - secondary user - managed profile is not included`() =
+ runBlocking(IMMEDIATE) {
+ var userInfos = createUserInfos(count = 3, includeGuest = false).toMutableList()
+ userInfos.add(
+ UserInfo(
+ 50,
+ "Work Profile",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_MANAGED_PROFILE
+ )
+ )
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserModel>? = null
+ val job = underTest.users.onEach { res = it }.launchIn(this)
+ assertThat(res?.size == 3).isTrue()
+ job.cancel()
+ }
+
+ @Test
+ fun `current user is not primary and user switcher is disabled`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 2, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+ var selectedUser: UserModel? = null
+ val job = underTest.selectedUser.onEach { selectedUser = it }.launchIn(this)
+ assertThat(selectedUser).isNotNull()
+ job.cancel()
+ }
+
+ private fun assertUsers(
+ models: List<UserModel>?,
+ count: Int,
+ selectedIndex: Int = 0,
+ includeGuest: Boolean = false,
+ ) {
+ checkNotNull(models)
+ assertThat(models.size).isEqualTo(count)
+ models.forEachIndexed { index, model ->
+ assertUser(
+ model = model,
+ id = index,
+ isSelected = index == selectedIndex,
+ isGuest = includeGuest && index == count - 1
+ )
+ }
+ }
+
+ private fun assertUser(
+ model: UserModel?,
+ id: Int,
+ isSelected: Boolean = false,
+ isGuest: Boolean = false,
+ ) {
+ checkNotNull(model)
+ assertThat(model.id).isEqualTo(id)
+ assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id"))
+ assertThat(model.isSelected).isEqualTo(isSelected)
+ assertThat(model.isSelectable).isTrue()
+ assertThat(model.isGuest).isEqualTo(isGuest)
+ }
+
+ private fun assertRecords(
+ records: List<UserRecord>,
+ userIds: List<Int>,
+ selectedUserIndex: Int = 0,
+ includeGuest: Boolean = false,
+ expectedActions: List<UserActionModel> = emptyList(),
+ ) {
+ assertThat(records.size >= userIds.size).isTrue()
+ userIds.indices.forEach { userIndex ->
+ val record = records[userIndex]
+ assertThat(record.info).isNotNull()
+ val isGuest = includeGuest && userIndex == userIds.size - 1
+ assertRecordForUser(
+ record = record,
+ id = userIds[userIndex],
+ hasPicture = !isGuest,
+ isCurrent = userIndex == selectedUserIndex,
+ isGuest = isGuest,
+ isSwitchToEnabled = true,
+ )
+ }
+
+ assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
+ (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
+ val record = records[actionIndex]
+ assertThat(record.info).isNull()
+ assertRecordForAction(
+ record = record,
+ type = expectedActions[actionIndex - userIds.size],
+ )
+ }
+ }
+
+ private fun assertRecordForUser(
+ record: UserRecord?,
+ id: Int? = null,
+ hasPicture: Boolean = false,
+ isCurrent: Boolean = false,
+ isGuest: Boolean = false,
+ isSwitchToEnabled: Boolean = false,
+ ) {
+ checkNotNull(record)
+ assertThat(record.info?.id).isEqualTo(id)
+ assertThat(record.picture != null).isEqualTo(hasPicture)
+ assertThat(record.isCurrent).isEqualTo(isCurrent)
+ assertThat(record.isGuest).isEqualTo(isGuest)
+ assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
+ }
+
+ private fun assertRecordForAction(
+ record: UserRecord,
+ type: UserActionModel,
+ ) {
+ assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
+ assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
+ assertThat(record.isAddSupervisedUser)
+ .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
+ }
+
+ private fun createUserInfos(
+ count: Int,
+ includeGuest: Boolean,
+ ): List<UserInfo> {
+ return (0 until count).map { index ->
+ val isGuest = includeGuest && index == count - 1
+ createUserInfo(
+ id = index,
+ name =
+ if (isGuest) {
+ "guest"
+ } else {
+ "user_$index"
+ },
+ isPrimary = !isGuest && index == 0,
+ isGuest = isGuest,
+ )
+ }
+ }
+
+ private fun createUserInfo(
+ id: Int,
+ name: String,
+ isPrimary: Boolean = false,
+ isGuest: Boolean = false,
+ ): UserInfo {
+ return UserInfo(
+ id,
+ name,
+ /* iconPath= */ "",
+ /* flags= */ if (isPrimary) {
+ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL
+ } else {
+ UserInfo.FLAG_FULL
+ },
+ if (isGuest) {
+ UserManager.USER_TYPE_FULL_GUEST
+ } else {
+ UserManager.USER_TYPE_FULL_SYSTEM
+ },
+ )
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
+ private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val GUEST_ICON: Drawable = mock()
+ private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
deleted file mode 100644
index 6a17c8d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.user.domain.interactor
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.nullable
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.verify
-
-@SmallTest
-@RunWith(JUnit4::class)
-open class UserInteractorUnrefactoredTest : UserInteractorTest() {
-
- override fun isRefactored(): Boolean {
- return false
- }
-
- @Before
- override fun setUp() {
- super.setUp()
- }
-
- @Test
- fun `actions - not actionable when locked and locked - no actions`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
- userRepository.setActionableWhenLocked(false)
- keyguardRepository.setKeyguardShowing(true)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions).isEmpty()
- job.cancel()
- }
-
- @Test
- fun `actions - not actionable when locked and not locked`() =
- runBlocking(IMMEDIATE) {
- setActions()
- userRepository.setActionableWhenLocked(false)
- keyguardRepository.setKeyguardShowing(false)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
- job.cancel()
- }
-
- @Test
- fun `actions - actionable when locked and not locked`() =
- runBlocking(IMMEDIATE) {
- setActions()
- userRepository.setActionableWhenLocked(true)
- keyguardRepository.setKeyguardShowing(false)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
- job.cancel()
- }
-
- @Test
- fun `actions - actionable when locked and locked`() =
- runBlocking(IMMEDIATE) {
- setActions()
- userRepository.setActionableWhenLocked(true)
- keyguardRepository.setKeyguardShowing(true)
-
- var actions: List<UserActionModel>? = null
- val job = underTest.actions.onEach { actions = it }.launchIn(this)
-
- assertThat(actions)
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE,
- UserActionModel.ADD_USER,
- UserActionModel.ADD_SUPERVISED_USER,
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
- )
- )
- job.cancel()
- }
-
- @Test
- fun selectUser() {
- val userId = 3
-
- underTest.selectUser(userId)
-
- verify(controller).onUserSelected(eq(userId), nullable())
- }
-
- @Test
- fun `executeAction - guest`() {
- underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
-
- verify(controller).createAndSwitchToGuestUser(nullable())
- }
-
- @Test
- fun `executeAction - add user`() {
- underTest.executeAction(UserActionModel.ADD_USER)
-
- verify(controller).showAddUserDialog(nullable())
- }
-
- @Test
- fun `executeAction - add supervised user`() {
- underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
-
- verify(controller).startSupervisedUserActivity()
- }
-
- @Test
- fun `executeAction - manage users`() {
- underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
-
- verify(activityStarter).startActivity(any(), anyBoolean())
- }
-
- private fun setActions() {
- userRepository.setActions(UserActionModel.values().toList())
- }
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
-}
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
new file mode 100644
index 0000000..108fa62
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.ui.viewmodel
+
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Text
+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.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+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.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.yield
+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.Mock
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class StatusBarUserChipViewModelTest : SysuiTestCase() {
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var activityManager: ActivityManager
+ @Mock private lateinit var manager: UserManager
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+
+ private lateinit var underTest: StatusBarUserChipViewModel
+
+ private val userRepository = FakeUserRepository()
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val featureFlags = FakeFeatureFlags()
+ private lateinit var guestUserInteractor: GuestUserInteractor
+ private lateinit var refreshUsersScheduler: RefreshUsersScheduler
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ doAnswer { invocation ->
+ val userId = invocation.arguments[0] as Int
+ when (userId) {
+ USER_ID_0 -> return@doAnswer USER_IMAGE_0
+ USER_ID_1 -> return@doAnswer USER_IMAGE_1
+ USER_ID_2 -> return@doAnswer USER_IMAGE_2
+ else -> return@doAnswer mock<Bitmap>()
+ }
+ }
+ .`when`(manager)
+ .getUserIcon(anyInt())
+
+ userRepository.isStatusBarUserChipEnabled = true
+
+ refreshUsersScheduler =
+ RefreshUsersScheduler(
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ repository = userRepository,
+ )
+ guestUserInteractor =
+ GuestUserInteractor(
+ applicationContext = context,
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
+ manager = manager,
+ repository = userRepository,
+ deviceProvisionedController = deviceProvisionedController,
+ devicePolicyManager = devicePolicyManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
+ )
+
+ underTest = viewModel()
+ }
+
+ @Test
+ fun `config is false - chip is disabled`() {
+ // the enabled bit is set at SystemUI startup, so recreate the view model here
+ userRepository.isStatusBarUserChipEnabled = false
+ underTest = viewModel()
+
+ assertThat(underTest.chipEnabled).isFalse()
+ }
+
+ @Test
+ fun `config is true - chip is enabled`() {
+ // the enabled bit is set at SystemUI startup, so recreate the view model here
+ userRepository.isStatusBarUserChipEnabled = true
+ underTest = viewModel()
+
+ assertThat(underTest.chipEnabled).isTrue()
+ }
+
+ @Test
+ fun `should show chip criteria - single user`() =
+ testScope.runTest {
+ userRepository.setUserInfos(listOf(USER_0))
+ userRepository.setSelectedUserInfo(USER_0)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ val values = mutableListOf<Boolean>()
+
+ val job = launch { underTest.isChipVisible.toList(values) }
+ advanceUntilIdle()
+
+ assertThat(values).containsExactly(false)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `should show chip criteria - multiple users`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ var latest: Boolean? = null
+ val job = underTest.isChipVisible.onEach { latest = it }.launchIn(this)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `user chip name - shows selected user info`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ var latest: Text? = null
+ val job = underTest.userName.onEach { latest = it }.launchIn(this)
+
+ userRepository.setSelectedUserInfo(USER_0)
+ assertThat(latest).isEqualTo(USER_NAME_0)
+
+ userRepository.setSelectedUserInfo(USER_1)
+ assertThat(latest).isEqualTo(USER_NAME_1)
+
+ userRepository.setSelectedUserInfo(USER_2)
+ assertThat(latest).isEqualTo(USER_NAME_2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `user chip avatar - shows selected user info`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ // A little hacky. System server passes us bitmaps and we wrap them in the interactor.
+ // Unwrap them to make sure we're always tracking the current user's bitmap
+ var latest: Bitmap? = null
+ val job =
+ underTest.userAvatar
+ .onEach {
+ if (it !is BitmapDrawable) {
+ latest = null
+ }
+
+ latest = (it as BitmapDrawable).bitmap
+ }
+ .launchIn(this)
+
+ userRepository.setSelectedUserInfo(USER_0)
+ assertThat(latest).isEqualTo(USER_IMAGE_0)
+
+ userRepository.setSelectedUserInfo(USER_1)
+ assertThat(latest).isEqualTo(USER_IMAGE_1)
+
+ userRepository.setSelectedUserInfo(USER_2)
+ assertThat(latest).isEqualTo(USER_IMAGE_2)
+
+ job.cancel()
+ }
+
+ private fun viewModel(): StatusBarUserChipViewModel {
+ return StatusBarUserChipViewModel(
+ context = context,
+ interactor =
+ UserInteractor(
+ applicationContext = context,
+ repository = userRepository,
+ activityStarter = activityStarter,
+ keyguardInteractor =
+ KeyguardInteractor(
+ repository = keyguardRepository,
+ ),
+ featureFlags =
+ FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) },
+ manager = manager,
+ applicationScope = testScope.backgroundScope,
+ telephonyInteractor =
+ TelephonyInteractor(
+ repository = FakeTelephonyRepository(),
+ ),
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ backgroundDispatcher = testDispatcher,
+ activityManager = activityManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ guestUserInteractor = guestUserInteractor,
+ )
+ )
+ }
+
+ private suspend fun setMultipleUsers() {
+ userRepository.setUserInfos(listOf(USER_0, USER_1, USER_2))
+ userRepository.setSelectedUserInfo(USER_0)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ }
+
+ companion object {
+ private const val USER_ID_0 = 0
+ private val USER_IMAGE_0 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_0 = Text.Loaded("zero")
+
+ private const val USER_ID_1 = 1
+ private val USER_IMAGE_1 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_1 = Text.Loaded("one")
+
+ private const val USER_ID_2 = 2
+ private val USER_IMAGE_2 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_2 = Text.Loaded("two")
+
+ private val USER_0 =
+ UserInfo(
+ USER_ID_0,
+ USER_NAME_0.text!!,
+ /* iconPath */ "",
+ /* flags */ UserInfo.FLAG_FULL,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+
+ private val USER_1 =
+ UserInfo(
+ USER_ID_1,
+ USER_NAME_1.text!!,
+ /* iconPath */ "",
+ /* flags */ UserInfo.FLAG_FULL,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+
+ private val USER_2 =
+ UserInfo(
+ USER_ID_2,
+ USER_NAME_2.text!!,
+ /* iconPath */ "",
+ /* flags */ UserInfo.FLAG_FULL,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+ }
+}
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 116023a..4b6bdac 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
@@ -19,7 +19,7 @@
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
-import android.graphics.drawable.Drawable
+import android.content.pm.UserInfo
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
@@ -35,24 +35,31 @@
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+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.RefreshUsersScheduler
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.yield
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -60,11 +67,11 @@
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class UserSwitcherViewModelTest : SysuiTestCase() {
- @Mock private lateinit var controller: UserSwitcherController
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var activityManager: ActivityManager
@Mock private lateinit var manager: UserManager
@@ -80,28 +87,47 @@
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var powerRepository: FakePowerRepository
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var testScope: TestScope
+ private lateinit var injectedScope: CoroutineScope
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+ whenever(manager.getUserSwitchability(any()))
+ .thenReturn(UserManager.SWITCHABILITY_STATUS_OK)
+ overrideResource(
+ com.android.internal.R.string.config_supervisedUserCreationPackage,
+ SUPERVISED_USER_CREATION_PACKAGE,
+ )
+ testDispatcher = UnconfinedTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ injectedScope = CoroutineScope(testScope.coroutineContext + SupervisorJob())
userRepository = FakeUserRepository()
+ runBlocking {
+ userRepository.setSettings(
+ UserSwitcherSettingsModel(
+ isUserSwitcherEnabled = true,
+ )
+ )
+ }
+
keyguardRepository = FakeKeyguardRepository()
powerRepository = FakePowerRepository()
- val featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, true)
- val scope = TestCoroutineScope()
val refreshUsersScheduler =
RefreshUsersScheduler(
- applicationScope = scope,
- mainDispatcher = IMMEDIATE,
+ applicationScope = injectedScope,
+ mainDispatcher = testDispatcher,
repository = userRepository,
)
val guestUserInteractor =
GuestUserInteractor(
applicationContext = context,
- applicationScope = scope,
- mainDispatcher = IMMEDIATE,
- backgroundDispatcher = IMMEDIATE,
+ applicationScope = injectedScope,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
manager = manager,
repository = userRepository,
deviceProvisionedController = deviceProvisionedController,
@@ -118,21 +144,23 @@
UserInteractor(
applicationContext = context,
repository = userRepository,
- controller = controller,
activityStarter = activityStarter,
keyguardInteractor =
KeyguardInteractor(
repository = keyguardRepository,
),
- featureFlags = featureFlags,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ },
manager = manager,
- applicationScope = scope,
+ applicationScope = injectedScope,
telephonyInteractor =
TelephonyInteractor(
repository = FakeTelephonyRepository(),
),
broadcastDispatcher = fakeBroadcastDispatcher,
- backgroundDispatcher = IMMEDIATE,
+ backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor = guestUserInteractor,
@@ -141,222 +169,216 @@
PowerInteractor(
repository = powerRepository,
),
- featureFlags = featureFlags,
guestUserInteractor = guestUserInteractor,
)
.create(UserSwitcherViewModel::class.java)
}
@Test
- fun users() =
- runBlocking(IMMEDIATE) {
- userRepository.setUsers(
+ fun users() = selfCancelingTest {
+ val userInfos =
+ listOf(
+ UserInfo(
+ /* id= */ 0,
+ /* name= */ "zero",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 1,
+ /* name= */ "one",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ UserInfo(
+ /* id= */ 2,
+ /* name= */ "two",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ )
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+
+ val userViewModels = mutableListOf<List<UserViewModel>>()
+ val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
+
+ assertThat(userViewModels.last()).hasSize(3)
+ assertUserViewModel(
+ viewModel = userViewModels.last()[0],
+ viewKey = 0,
+ name = "zero",
+ isSelectionMarkerVisible = true,
+ )
+ assertUserViewModel(
+ viewModel = userViewModels.last()[1],
+ viewKey = 1,
+ name = "one",
+ isSelectionMarkerVisible = false,
+ )
+ assertUserViewModel(
+ viewModel = userViewModels.last()[2],
+ viewKey = 2,
+ name = "two",
+ isSelectionMarkerVisible = false,
+ )
+ job.cancel()
+ }
+
+ @Test
+ fun `maximumUserColumns - few users`() = selfCancelingTest {
+ setUsers(count = 2)
+ val values = mutableListOf<Int>()
+ val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+ assertThat(values.last()).isEqualTo(4)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `maximumUserColumns - many users`() = selfCancelingTest {
+ setUsers(count = 5)
+ val values = mutableListOf<Int>()
+ val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
+
+ assertThat(values.last()).isEqualTo(3)
+ job.cancel()
+ }
+
+ @Test
+ fun `isOpenMenuButtonVisible - has actions - true`() = selfCancelingTest {
+ setUsers(2)
+
+ val isVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+ assertThat(isVisible.last()).isTrue()
+ job.cancel()
+ }
+
+ @Test
+ fun `isOpenMenuButtonVisible - no actions - false`() = selfCancelingTest {
+ val userInfos = setUsers(2)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ keyguardRepository.setKeyguardShowing(true)
+ whenever(manager.canAddMoreUsers(any())).thenReturn(false)
+
+ val isVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
+
+ assertThat(isVisible.last()).isFalse()
+ job.cancel()
+ }
+
+ @Test
+ fun menu() = selfCancelingTest {
+ val isMenuVisible = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) }
+ assertThat(isMenuVisible.last()).isFalse()
+
+ underTest.onOpenMenuButtonClicked()
+ assertThat(isMenuVisible.last()).isTrue()
+
+ underTest.onMenuClosed()
+ assertThat(isMenuVisible.last()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `menu actions`() = selfCancelingTest {
+ setUsers(2)
+ val actions = mutableListOf<List<UserActionViewModel>>()
+ val job = launch(testDispatcher) { underTest.menu.toList(actions) }
+
+ assertThat(actions.last().map { it.viewKey })
+ .isEqualTo(
listOf(
- UserModel(
- id = 0,
- name = Text.Loaded("zero"),
- image = USER_IMAGE,
- isSelected = true,
- isSelectable = true,
- isGuest = false,
- ),
- UserModel(
- id = 1,
- name = Text.Loaded("one"),
- image = USER_IMAGE,
- isSelected = false,
- isSelectable = true,
- isGuest = false,
- ),
- UserModel(
- id = 2,
- name = Text.Loaded("two"),
- image = USER_IMAGE,
- isSelected = false,
- isSelectable = false,
- isGuest = false,
- ),
+ UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
+ UserActionModel.ADD_USER.ordinal.toLong(),
+ UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
+ UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
)
)
- var userViewModels: List<UserViewModel>? = null
- val job = underTest.users.onEach { userViewModels = it }.launchIn(this)
-
- assertThat(userViewModels).hasSize(3)
- assertUserViewModel(
- viewModel = userViewModels?.get(0),
- viewKey = 0,
- name = "zero",
- isSelectionMarkerVisible = true,
- alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
- isClickable = true,
- )
- assertUserViewModel(
- viewModel = userViewModels?.get(1),
- viewKey = 1,
- name = "one",
- isSelectionMarkerVisible = false,
- alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA,
- isClickable = true,
- )
- assertUserViewModel(
- viewModel = userViewModels?.get(2),
- viewKey = 2,
- name = "two",
- isSelectionMarkerVisible = false,
- alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA,
- isClickable = false,
- )
- job.cancel()
- }
+ job.cancel()
+ }
@Test
- fun `maximumUserColumns - few users`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- var value: Int? = null
- val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
+ fun `isFinishRequested - finishes when user is switched`() = selfCancelingTest {
+ val userInfos = setUsers(count = 2)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
- assertThat(value).isEqualTo(4)
- job.cancel()
- }
+ userRepository.setSelectedUserInfo(userInfos[1])
+
+ assertThat(isFinishRequested.last()).isTrue()
+
+ job.cancel()
+ }
@Test
- fun `maximumUserColumns - many users`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 5)
- var value: Int? = null
- val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this)
+ fun `isFinishRequested - finishes when the screen turns off`() = selfCancelingTest {
+ setUsers(count = 2)
+ powerRepository.setInteractive(true)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
- assertThat(value).isEqualTo(3)
- job.cancel()
- }
+ powerRepository.setInteractive(false)
+
+ assertThat(isFinishRequested.last()).isTrue()
+
+ job.cancel()
+ }
@Test
- fun `isOpenMenuButtonVisible - has actions - true`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
+ fun `isFinishRequested - finishes when cancel button is clicked`() = selfCancelingTest {
+ setUsers(count = 2)
+ powerRepository.setInteractive(true)
+ val isFinishRequested = mutableListOf<Boolean>()
+ val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ assertThat(isFinishRequested.last()).isFalse()
- var isVisible: Boolean? = null
- val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
+ underTest.onCancelButtonClicked()
- assertThat(isVisible).isTrue()
- job.cancel()
- }
+ assertThat(isFinishRequested.last()).isTrue()
- @Test
- fun `isOpenMenuButtonVisible - no actions - false`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(emptyList())
+ underTest.onFinished()
- var isVisible: Boolean? = null
- val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this)
+ assertThat(isFinishRequested.last()).isFalse()
- assertThat(isVisible).isFalse()
- job.cancel()
- }
+ job.cancel()
+ }
- @Test
- fun menu() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
- var isMenuVisible: Boolean? = null
- val job = underTest.isMenuVisible.onEach { isMenuVisible = it }.launchIn(this)
- assertThat(isMenuVisible).isFalse()
-
- underTest.onOpenMenuButtonClicked()
- assertThat(isMenuVisible).isTrue()
-
- underTest.onMenuClosed()
- assertThat(isMenuVisible).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun `menu actions`() =
- runBlocking(IMMEDIATE) {
- userRepository.setActions(UserActionModel.values().toList())
- var actions: List<UserActionViewModel>? = null
- val job = underTest.menu.onEach { actions = it }.launchIn(this)
-
- assertThat(actions?.map { it.viewKey })
- .isEqualTo(
- listOf(
- UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
- UserActionModel.ADD_USER.ordinal.toLong(),
- UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
- UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
- )
- )
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when user is switched`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- var isFinishRequested: Boolean? = null
- val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
- assertThat(isFinishRequested).isFalse()
-
- userRepository.setSelectedUser(1)
- yield()
- assertThat(isFinishRequested).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when the screen turns off`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- powerRepository.setInteractive(true)
- var isFinishRequested: Boolean? = null
- val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
- assertThat(isFinishRequested).isFalse()
-
- powerRepository.setInteractive(false)
- yield()
- assertThat(isFinishRequested).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when cancel button is clicked`() =
- runBlocking(IMMEDIATE) {
- setUsers(count = 2)
- powerRepository.setInteractive(true)
- var isFinishRequested: Boolean? = null
- val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this)
- assertThat(isFinishRequested).isFalse()
-
- underTest.onCancelButtonClicked()
- yield()
- assertThat(isFinishRequested).isTrue()
-
- underTest.onFinished()
- yield()
- assertThat(isFinishRequested).isFalse()
-
- job.cancel()
- }
-
- private suspend fun setUsers(count: Int) {
- userRepository.setUsers(
+ private suspend fun setUsers(count: Int): List<UserInfo> {
+ val userInfos =
(0 until count).map { index ->
- UserModel(
- id = index,
- name = Text.Loaded("$index"),
- image = USER_IMAGE,
- isSelected = index == 0,
- isSelectable = true,
- isGuest = false,
+ UserInfo(
+ /* id= */ index,
+ /* name= */ "$index",
+ /* iconPath= */ "",
+ /* flags= */ if (index == 0) {
+ // This is the primary user.
+ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL
+ } else {
+ // This isn't the primary user.
+ UserInfo.FLAG_FULL
+ },
+ UserManager.USER_TYPE_FULL_SYSTEM,
)
}
- )
+ userRepository.setUserInfos(userInfos)
+
+ if (userInfos.isNotEmpty()) {
+ userRepository.setSelectedUserInfo(userInfos[0])
+ }
+ return userInfos
}
private fun assertUserViewModel(
@@ -364,19 +386,25 @@
viewKey: Int,
name: String,
isSelectionMarkerVisible: Boolean,
- alpha: Float,
- isClickable: Boolean,
) {
checkNotNull(viewModel)
assertThat(viewModel.viewKey).isEqualTo(viewKey)
assertThat(viewModel.name).isEqualTo(Text.Loaded(name))
assertThat(viewModel.isSelectionMarkerVisible).isEqualTo(isSelectionMarkerVisible)
- assertThat(viewModel.alpha).isEqualTo(alpha)
- assertThat(viewModel.onClicked != null).isEqualTo(isClickable)
+ assertThat(viewModel.alpha)
+ .isEqualTo(LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA)
+ assertThat(viewModel.onClicked).isNotNull()
}
+ private fun selfCancelingTest(
+ block: suspend TestScope.() -> Unit,
+ ): TestResult =
+ testScope.runTest {
+ block()
+ injectedScope.coroutineContext[Job.Key]?.cancelAndJoin()
+ }
+
companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- private val USER_IMAGE = mock<Drawable>()
+ private const val SUPERVISED_USER_CREATION_PACKAGE = "com.some.package"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
index 0b53133..2878864 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
@@ -141,4 +141,158 @@
mCondition.clearCondition();
assertThat(mCondition.isConditionSet()).isFalse();
}
+
+ @Test
+ public void combineConditionsWithOr_allFalse_reportsNotMet() {
+ mCondition.fakeUpdateCondition(false);
+
+ final Condition combinedCondition = mCondition.or(
+ new FakeCondition(/* initialValue= */ false));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithOr_allTrue_reportsMet() {
+ mCondition.fakeUpdateCondition(true);
+
+ final Condition combinedCondition = mCondition.or(
+ new FakeCondition(/* initialValue= */ true));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isTrue();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithOr_singleTrue_reportsMet() {
+ mCondition.fakeUpdateCondition(false);
+
+ final Condition combinedCondition = mCondition.or(
+ new FakeCondition(/* initialValue= */ true));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isTrue();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithOr_unknownAndTrue_reportsMet() {
+ mCondition.fakeUpdateCondition(true);
+
+ // Combine with an unset condition.
+ final Condition combinedCondition = mCondition.or(
+ new FakeCondition(/* initialValue= */ null));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isTrue();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithOr_unknownAndFalse_reportsNotMet() {
+ mCondition.fakeUpdateCondition(false);
+
+ // Combine with an unset condition.
+ final Condition combinedCondition = mCondition.or(
+ new FakeCondition(/* initialValue= */ null));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isFalse();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, never()).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithAnd_allFalse_reportsNotMet() {
+ mCondition.fakeUpdateCondition(false);
+
+ final Condition combinedCondition = mCondition.and(
+ new FakeCondition(/* initialValue= */ false));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithAnd_allTrue_reportsMet() {
+ mCondition.fakeUpdateCondition(true);
+
+ final Condition combinedCondition = mCondition.and(
+ new FakeCondition(/* initialValue= */ true));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isTrue();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithAnd_singleTrue_reportsNotMet() {
+ mCondition.fakeUpdateCondition(true);
+
+ final Condition combinedCondition = mCondition.and(
+ new FakeCondition(/* initialValue= */ false));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithAnd_unknownAndTrue_reportsNotMet() {
+ mCondition.fakeUpdateCondition(true);
+
+ // Combine with an unset condition.
+ final Condition combinedCondition = mCondition.and(
+ new FakeCondition(/* initialValue= */ null));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isFalse();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, never()).onConditionChanged(combinedCondition);
+ }
+
+ @Test
+ public void combineConditionsWithAnd_unknownAndFalse_reportsMet() {
+ mCondition.fakeUpdateCondition(false);
+
+ // Combine with an unset condition.
+ final Condition combinedCondition = mCondition.and(
+ new FakeCondition(/* initialValue= */ null));
+
+ final Condition.Callback callback = mock(Condition.Callback.class);
+ combinedCondition.addCallback(callback);
+
+ assertThat(combinedCondition.isConditionSet()).isTrue();
+ assertThat(combinedCondition.isConditionMet()).isFalse();
+ verify(callback, times(1)).onConditionChanged(combinedCondition);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
index 7df7077..6bfc2f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt
@@ -51,15 +51,11 @@
)
}
- @Test
- fun notEnough() = runBlocking {
- assertThatFlow(flowOf(1).pairwise()).emitsNothing()
- }
+ @Test fun notEnough() = runBlocking { assertThatFlow(flowOf(1).pairwise()).emitsNothing() }
@Test
fun withInit() = runBlocking {
- assertThatFlow(flowOf(2).pairwise(initialValue = 1))
- .emitsExactly(WithPrev(1, 2))
+ assertThatFlow(flowOf(2).pairwise(initialValue = 1)).emitsExactly(WithPrev(1, 2))
}
@Test
@@ -68,25 +64,78 @@
}
@Test
- fun withStateFlow() = runBlocking(Dispatchers.Main.immediate) {
- val state = MutableStateFlow(1)
- val stop = MutableSharedFlow<Unit>()
-
- val stoppable = merge(state, stop)
- .takeWhile { it is Int }
- .filterIsInstance<Int>()
-
- val job1 = launch {
- assertThatFlow(stoppable.pairwise()).emitsExactly(WithPrev(1, 2))
- }
- state.value = 2
- val job2 = launch { assertThatFlow(stoppable.pairwise()).emitsNothing() }
-
- stop.emit(Unit)
-
- assertThatJob(job1).isCompleted()
- assertThatJob(job2).isCompleted()
+ fun withTransform() = runBlocking {
+ assertThatFlow(
+ flowOf("val1", "val2", "val3").pairwiseBy { prev: String, next: String ->
+ "$prev|$next"
+ }
+ )
+ .emitsExactly("val1|val2", "val2|val3")
}
+
+ @Test
+ fun withGetInit() = runBlocking {
+ var initRun = false
+ assertThatFlow(
+ flowOf("val1", "val2").pairwiseBy(
+ getInitialValue = {
+ initRun = true
+ "initial"
+ }
+ ) { prev: String, next: String -> "$prev|$next" }
+ )
+ .emitsExactly("initial|val1", "val1|val2")
+ assertThat(initRun).isTrue()
+ }
+
+ @Test
+ fun notEnoughWithGetInit() = runBlocking {
+ var initRun = false
+ assertThatFlow(
+ emptyFlow<String>().pairwiseBy(
+ getInitialValue = {
+ initRun = true
+ "initial"
+ }
+ ) { prev: String, next: String -> "$prev|$next" }
+ )
+ .emitsNothing()
+ // Even though the flow will not emit anything, the initial value function should still get
+ // run.
+ assertThat(initRun).isTrue()
+ }
+
+ @Test
+ fun getInitNotRunWhenFlowNotCollected() = runBlocking {
+ var initRun = false
+ flowOf("val1", "val2").pairwiseBy(
+ getInitialValue = {
+ initRun = true
+ "initial"
+ }
+ ) { prev: String, next: String -> "$prev|$next" }
+
+ // Since the flow isn't collected, ensure [initialValueFun] isn't run.
+ assertThat(initRun).isFalse()
+ }
+
+ @Test
+ fun withStateFlow() =
+ runBlocking(Dispatchers.Main.immediate) {
+ val state = MutableStateFlow(1)
+ val stop = MutableSharedFlow<Unit>()
+
+ val stoppable = merge(state, stop).takeWhile { it is Int }.filterIsInstance<Int>()
+
+ val job1 = launch { assertThatFlow(stoppable.pairwise()).emitsExactly(WithPrev(1, 2)) }
+ state.value = 2
+ val job2 = launch { assertThatFlow(stoppable.pairwise()).emitsNothing() }
+
+ stop.emit(Unit)
+
+ assertThatJob(job1).isCompleted()
+ assertThatJob(job2).isCompleted()
+ }
}
@SmallTest
@@ -94,18 +143,17 @@
class SetChangesFlowTest : SysuiTestCase() {
@Test
fun simple() = runBlocking {
- assertThatFlow(
- flowOf(setOf(1, 2, 3), setOf(2, 3, 4)).setChanges()
- ).emitsExactly(
- SetChanges(
- added = setOf(1, 2, 3),
- removed = emptySet(),
- ),
- SetChanges(
- added = setOf(4),
- removed = setOf(1),
- ),
- )
+ assertThatFlow(flowOf(setOf(1, 2, 3), setOf(2, 3, 4)).setChanges())
+ .emitsExactly(
+ SetChanges(
+ added = setOf(1, 2, 3),
+ removed = emptySet(),
+ ),
+ SetChanges(
+ added = setOf(4),
+ removed = setOf(1),
+ ),
+ )
}
@Test
@@ -147,14 +195,19 @@
class SampleFlowTest : SysuiTestCase() {
@Test
fun simple() = runBlocking {
- assertThatFlow(flow { yield(); emit(1) }.sample(flowOf(2)) { a, b -> a to b })
+ assertThatFlow(
+ flow {
+ yield()
+ emit(1)
+ }
+ .sample(flowOf(2)) { a, b -> a to b }
+ )
.emitsExactly(1 to 2)
}
@Test
fun otherFlowNoValueYet() = runBlocking {
- assertThatFlow(flowOf(1).sample(emptyFlow<Unit>()))
- .emitsNothing()
+ assertThatFlow(flowOf(1).sample(emptyFlow<Unit>())).emitsNothing()
}
@Test
@@ -178,13 +231,14 @@
}
}
-private fun <T> assertThatFlow(flow: Flow<T>) = object {
- suspend fun emitsExactly(vararg emissions: T) =
- assertThat(flow.toList()).containsExactly(*emissions).inOrder()
- suspend fun emitsNothing() =
- assertThat(flow.toList()).isEmpty()
-}
+private fun <T> assertThatFlow(flow: Flow<T>) =
+ object {
+ suspend fun emitsExactly(vararg emissions: T) =
+ assertThat(flow.toList()).containsExactly(*emissions).inOrder()
+ suspend fun emitsNothing() = assertThat(flow.toList()).isEmpty()
+ }
-private fun assertThatJob(job: Job) = object {
- fun isCompleted() = assertThat(job.isCompleted).isTrue()
-}
+private fun assertThatJob(job: Job) =
+ object {
+ fun isCompleted() = assertThat(job.isCompleted).isTrue()
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
index 0d8dd2c..df08efa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/AsyncSensorManagerTest.java
@@ -28,8 +28,8 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.SensorManagerPlugin;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 2e74bf5..a0b4eab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -18,6 +18,7 @@
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -28,6 +29,7 @@
import android.app.KeyguardManager;
import android.media.AudioManager;
import android.os.SystemClock;
+import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.InputDevice;
@@ -38,6 +40,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -49,6 +52,9 @@
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -71,6 +77,8 @@
View mDrawerVibrate;
View mDrawerMute;
View mDrawerNormal;
+ private DeviceConfigProxyFake mDeviceConfigProxy;
+ private FakeExecutor mExecutor;
@Mock
VolumeDialogController mVolumeDialogController;
@@ -97,6 +105,9 @@
getContext().addMockSystemService(KeyguardManager.class, mKeyguard);
+ mDeviceConfigProxy = new DeviceConfigProxyFake();
+ mExecutor = new FakeExecutor(new FakeSystemClock());
+
mDialog = new VolumeDialogImpl(
getContext(),
mVolumeDialogController,
@@ -106,7 +117,9 @@
mMediaOutputDialogFactory,
mVolumePanelFactory,
mActivityStarter,
- mInteractionJankMonitor);
+ mInteractionJankMonitor,
+ mDeviceConfigProxy,
+ mExecutor);
mDialog.init(0, null);
State state = createShellState();
mDialog.onStateChangedH(state);
@@ -123,6 +136,9 @@
VolumePrefs.SHOW_RINGER_TOAST_COUNT + 1);
Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false);
+
+ mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
}
private State createShellState() {
@@ -292,6 +308,35 @@
AudioManager.RINGER_MODE_NORMAL, false);
}
+ /**
+ * Ideally we would look at the ringer ImageView and check its assigned drawable id, but that
+ * API does not exist. So we do the next best thing; we check the cached icon id.
+ */
+ @Test
+ public void notificationVolumeSeparated_theRingerIconChanges() {
+ mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
+
+ mExecutor.runAllReady(); // for the config change to take effect
+
+ // assert icon is new based on res id
+ assertEquals(mDialog.mVolumeRingerIconDrawableId,
+ R.drawable.ic_speaker_on);
+ assertEquals(mDialog.mVolumeRingerMuteIconDrawableId,
+ R.drawable.ic_speaker_mute);
+ }
+
+ @Test
+ public void notificationVolumeNotSeparated_theRingerIconRemainsTheSame() {
+ mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
+
+ mExecutor.runAllReady();
+
+ assertEquals(mDialog.mVolumeRingerIconDrawableId, R.drawable.ic_volume_ringer);
+ assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute);
+ }
+
/*
@Test
public void testContentDescriptions() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index bee882d..b9dfc27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -77,6 +77,7 @@
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
@@ -157,6 +158,7 @@
import java.util.List;
import java.util.Optional;
+@FlakyTest
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -1421,13 +1423,43 @@
assertStackCollapsed();
// Post status bar state change update with the same value
mBubbleController.onStatusBarStateChanged(false);
- // Stack should remain collapsedb
+ // Stack should remain collapsed
assertStackCollapsed();
// Post status bar state change which should trigger bubble to expand
mBubbleController.onStatusBarStateChanged(true);
assertStackExpanded();
}
+ /**
+ * Test to verify behavior for the following scenario:
+ * <ol>
+ * <li>device is locked with keyguard on, status bar shade state updates to
+ * <code>false</code></li>
+ * <li>notification entry is marked to be a bubble and it is set to auto-expand</li>
+ * <li>device unlock starts, status bar shade state receives another update to
+ * <code>false</code></li>
+ * <li>device is unlocked and status bar shade state is set to <code>true</code></li>
+ * <li>bubble should be expanded</li>
+ * </ol>
+ */
+ @Test
+ public void testOnStatusBarStateChanged_newAutoExpandedBubbleRemainsExpanded() {
+ // Set device as locked
+ mBubbleController.onStatusBarStateChanged(false);
+
+ // Create a auto-expanded bubble
+ NotificationEntry entry = mNotificationTestHelper.createAutoExpandedBubble();
+ mEntryListener.onEntryAdded(entry);
+
+ // When unlocking, we may receive duplicate updates with shade=false, ensure they don't
+ // clear the expanded state
+ mBubbleController.onStatusBarStateChanged(false);
+ mBubbleController.onStatusBarStateChanged(true);
+
+ // After unlocking, stack should be expanded
+ assertStackExpanded();
+ }
+
@Test
public void testSetShouldAutoExpand_notifiesFlagChanged() {
mBubbleController.updateBubble(mBubbleEntry);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index fa3cc99..bf2235a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -136,6 +136,8 @@
InstrumentationRegistry.getArguments());
if (TestableLooper.get(this) != null) {
TestableLooper.get(this).processAllMessages();
+ // Must remove static reference to this test object to prevent leak (b/261039202)
+ TestableLooper.remove(this);
}
disallowTestableLooperAsMainThread();
mContext.cleanUpReceivers(this.getClass().getSimpleName());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
new file mode 100644
index 0000000..8176dd0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.udfps
+
+import android.graphics.Rect
+
+class FakeOverlapDetector : OverlapDetector {
+ var shouldReturn: Boolean = false
+
+ override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
+ return shouldReturn
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
new file mode 100644
index 0000000..b7a8d2e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.coroutines
+
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+
+/** Collect [flow] in a new [Job] and return a getter for the last collected value. */
+fun <T> TestScope.collectLastValue(
+ flow: Flow<T>,
+ context: CoroutineContext = EmptyCoroutineContext,
+ start: CoroutineStart = CoroutineStart.DEFAULT,
+): () -> T? {
+ var lastValue: T? = null
+ backgroundScope.launch(context, start) { flow.collect { lastValue = it } }
+ return {
+ runCurrent()
+ lastValue
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt
new file mode 100644
index 0000000..d85dd2e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient
+
+class FakeKeyguardQuickAffordanceProviderClientFactory(
+ private val userTracker: UserTracker,
+ private val callback: (Int) -> KeyguardQuickAffordanceProviderClient = {
+ FakeKeyguardQuickAffordanceProviderClient()
+ },
+) : KeyguardQuickAffordanceProviderClientFactory {
+
+ override fun create(): KeyguardQuickAffordanceProviderClient {
+ return callback(userTracker.userId)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index a798f40..3601667 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -19,6 +19,7 @@
import com.android.systemui.common.shared.model.Position
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import kotlinx.coroutines.flow.Flow
@@ -53,6 +54,9 @@
private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
override val statusBarState: Flow<StatusBarState> = _statusBarState
+ private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel())
+ override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel
+
private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 325da4e..1a893f8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -28,8 +28,6 @@
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -43,7 +41,6 @@
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.security.data.repository.SecurityRepository
import com.android.systemui.security.data.repository.SecurityRepositoryImpl
import com.android.systemui.settings.FakeUserTracker
@@ -54,11 +51,13 @@
import com.android.systemui.statusbar.policy.SecurityController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.GlobalSettings
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
/**
* Util class to create real implementations of the FooterActions repositories, viewModel and
@@ -67,6 +66,7 @@
class FooterActionsTestUtils(
private val context: Context,
private val testableLooper: TestableLooper,
+ private val scheduler: TestCoroutineScheduler,
) {
/** Enable or disable the user switcher in the settings. */
fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
@@ -97,28 +97,26 @@
/** Create a [FooterActionsInteractor] to be used in tests. */
fun footerActionsInteractor(
activityStarter: ActivityStarter = mock(),
- featureFlags: FeatureFlags = FakeFeatureFlags(),
metricsLogger: MetricsLogger = FakeMetricsLogger(),
uiEventLogger: UiEventLogger = UiEventLoggerFake(),
deviceProvisionedController: DeviceProvisionedController = mock(),
qsSecurityFooterUtils: QSSecurityFooterUtils = mock(),
fgsManagerController: FgsManagerController = mock(),
- userSwitchDialogController: UserSwitchDialogController = mock(),
+ userInteractor: UserInteractor = mock(),
securityRepository: SecurityRepository = securityRepository(),
foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
broadcastDispatcher: BroadcastDispatcher = mock(),
- bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+ bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
): FooterActionsInteractor {
return FooterActionsInteractorImpl(
activityStarter,
- featureFlags,
metricsLogger,
uiEventLogger,
deviceProvisionedController,
qsSecurityFooterUtils,
fgsManagerController,
- userSwitchDialogController,
+ userInteractor,
securityRepository,
foregroundServicesRepository,
userSwitcherRepository,
@@ -130,7 +128,7 @@
/** Create a [SecurityRepository] to be used in tests. */
fun securityRepository(
securityController: SecurityController = FakeSecurityController(),
- bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+ bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
): SecurityRepository {
return SecurityRepositoryImpl(
securityController,
@@ -149,7 +147,7 @@
fun userSwitcherRepository(
@Application context: Context = this.context.applicationContext,
bgHandler: Handler = Handler(testableLooper.looper),
- bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+ bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
userManager: UserManager = mock(),
userTracker: UserTracker = FakeUserTracker(),
userSwitcherController: UserSwitcherController = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index a7eadba..0dd1fc7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -66,7 +66,8 @@
_userId = _userInfo.id
_userHandle = UserHandle.of(_userId)
- callbacks.forEach { it.onUserChanged(_userId, userContext) }
+ val copy = callbacks.toList()
+ copy.forEach { it.onUserChanged(_userId, userContext) }
}
fun onProfileChanged() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 4df8aa4..ea5a302 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -20,26 +20,15 @@
import android.content.pm.UserInfo
import android.os.UserHandle
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
-import com.android.systemui.user.shared.model.UserActionModel
-import com.android.systemui.user.shared.model.UserModel
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.yield
class FakeUserRepository : UserRepository {
- private val _users = MutableStateFlow<List<UserModel>>(emptyList())
- override val users: Flow<List<UserModel>> = _users.asStateFlow()
- override val selectedUser: Flow<UserModel> =
- users.map { models -> models.first { model -> model.isSelected } }
-
- private val _actions = MutableStateFlow<List<UserActionModel>>(emptyList())
- override val actions: Flow<List<UserActionModel>> = _actions.asStateFlow()
-
private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
_userSwitcherSettings.asStateFlow()
@@ -52,9 +41,6 @@
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
- private val _isActionableWhenLocked = MutableStateFlow(false)
- override val isActionableWhenLocked: Flow<Boolean> = _isActionableWhenLocked.asStateFlow()
-
private var _isGuestUserAutoCreated: Boolean = false
override val isGuestUserAutoCreated: Boolean
get() = _isGuestUserAutoCreated
@@ -63,6 +49,8 @@
override val isGuestUserCreationScheduled = AtomicBoolean()
+ override var isStatusBarUserChipEnabled: Boolean = false
+
override var secondaryUserId: Int = UserHandle.USER_NULL
override var isRefreshUsersPaused: Boolean = false
@@ -100,35 +88,6 @@
yield()
}
- fun setUsers(models: List<UserModel>) {
- _users.value = models
- }
-
- suspend fun setSelectedUser(userId: Int) {
- check(_users.value.find { it.id == userId } != null) {
- "Cannot select a user with ID $userId - no user with that ID found!"
- }
-
- setUsers(
- _users.value.map { model ->
- when {
- model.isSelected && model.id != userId -> model.copy(isSelected = false)
- !model.isSelected && model.id == userId -> model.copy(isSelected = true)
- else -> model
- }
- }
- )
- yield()
- }
-
- fun setActions(models: List<UserActionModel>) {
- _actions.value = models
- }
-
- fun setActionableWhenLocked(value: Boolean) {
- _isActionableWhenLocked.value = value
- }
-
fun setGuestUserAutoCreated(value: Boolean) {
_isGuestUserAutoCreated = value
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
index 1353ad2..07ed110 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
@@ -25,15 +25,21 @@
super();
}
- FakeCondition(Boolean initialValue, Boolean overriding) {
+ FakeCondition(Boolean initialValue) {
+ super(initialValue, false);
+ }
+
+ FakeCondition(Boolean initialValue, boolean overriding) {
super(initialValue, overriding);
}
@Override
- public void start() {}
+ public void start() {
+ }
@Override
- public void stop() {}
+ public void stop() {
+ }
public void fakeUpdateCondition(boolean isConditionMet) {
updateCondition(isConditionMet);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
index f6fd2cb..f68baf5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -16,32 +16,71 @@
import android.testing.LeakCheck;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+import java.util.ArrayList;
+import java.util.List;
+
public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener>
implements FlashlightController {
+
+ private final List<FlashlightListener> callbacks = new ArrayList<>();
+
+ @VisibleForTesting
+ public boolean isAvailable;
+ @VisibleForTesting
+ public boolean isEnabled;
+ @VisibleForTesting
+ public boolean hasFlashlight;
+
public FakeFlashlightController(LeakCheck test) {
super(test, "flashlight");
}
+ @VisibleForTesting
+ public void onFlashlightAvailabilityChanged(boolean newValue) {
+ callbacks.forEach(
+ flashlightListener -> flashlightListener.onFlashlightAvailabilityChanged(newValue)
+ );
+ }
+
+ @VisibleForTesting
+ public void onFlashlightError() {
+ callbacks.forEach(FlashlightListener::onFlashlightError);
+ }
+
@Override
public boolean hasFlashlight() {
- return false;
+ return hasFlashlight;
}
@Override
public void setFlashlight(boolean newState) {
-
+ callbacks.forEach(flashlightListener -> flashlightListener.onFlashlightChanged(newState));
}
@Override
public boolean isAvailable() {
- return false;
+ return isAvailable;
}
@Override
public boolean isEnabled() {
- return false;
+ return isEnabled;
+ }
+
+ @Override
+ public void addCallback(FlashlightListener listener) {
+ super.addCallback(listener);
+ callbacks.add(listener);
+ }
+
+ @Override
+ public void removeCallback(FlashlightListener listener) {
+ super.removeCallback(listener);
+ callbacks.remove(listener);
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
index d245c72..63756c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakePluginManager.java
@@ -18,7 +18,7 @@
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
public class FakePluginManager implements PluginManager {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
index dc6a8fb..ec1f352 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -18,7 +18,7 @@
import android.util.ArrayMap;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
index 5c92b34..06ca153 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -14,7 +14,6 @@
*/
package com.android.systemui.unfold.util
-import android.animation.ValueAnimator
import android.content.ContentResolver
import android.database.ContentObserver
import android.provider.Settings
@@ -46,13 +45,15 @@
contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
/* notifyForDescendants= */ false,
- animatorDurationScaleObserver)
+ animatorDurationScaleObserver
+ )
onAnimatorScaleChanged()
}
private fun onAnimatorScaleChanged() {
- val animationsEnabled = ValueAnimator.areAnimatorsEnabled()
- scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(animationsEnabled)
+ scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(
+ contentResolver.areAnimationsEnabled()
+ )
}
override fun addCallback(listener: TransitionProgressListener) {
@@ -74,4 +75,18 @@
progressProvider: UnfoldTransitionProgressProvider
): ScaleAwareTransitionProgressProvider
}
+
+ companion object {
+ fun ContentResolver.areAnimationsEnabled(): Boolean {
+ val animationScale =
+ Settings.Global.getStringForUser(
+ this,
+ Settings.Global.ANIMATOR_DURATION_SCALE,
+ this.userId
+ )
+ ?.toFloatOrNull()
+ ?: 1f
+ return animationScale != 0f
+ }
+ }
}
diff --git a/packages/VpnDialogs/res/values-en-rCA/strings.xml b/packages/VpnDialogs/res/values-en-rCA/strings.xml
index cb8b79d..f715c05 100644
--- a/packages/VpnDialogs/res/values-en-rCA/strings.xml
+++ b/packages/VpnDialogs/res/values-en-rCA/strings.xml
@@ -26,8 +26,8 @@
<string name="data_received" msgid="4062776929376067820">"Received:"</string>
<string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> bytes / <xliff:g id="NUMBER_1">%2$s</xliff:g> packets"</string>
<string name="always_on_disconnected_title" msgid="1906740176262776166">"Can\'t connect to always-on VPN"</string>
- <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect at the moment. Your phone will use a public network until it can reconnect to <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string>
- <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect at the moment. You won\'t have a connection until the VPN can reconnect."</string>
+ <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect right now. Your phone will use a public network until it can reconnect to <xliff:g id="VPN_APP_1">%1$s</xliff:g>."</string>
+ <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> is set up to stay connected all the time, but it can\'t connect right now. You won\'t have a connection until the VPN can reconnect."</string>
<string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
<string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Change VPN settings"</string>
<string name="configure" msgid="4905518375574791375">"Configure"</string>
diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
index 9db960f..e7ec332 100644
--- a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="7305489596221077240">"Punch hole cutout"</string>
+ <string name="display_cutout_emulation_overlay" msgid="7305489596221077240">"Punch Hole cutout"</string>
</resources>
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
index 92435d0..9ffb314 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
@@ -113,7 +113,7 @@
info.minWidth = parser.getAttributeInt(null, ATTR_MIN_WIDTH, 0);
info.minHeight = parser.getAttributeInt(null, ATTR_MIN_HEIGHT, 0);
info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_WIDTH, 0);
- info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_HEIGHT, 0);
+ info.minResizeHeight = parser.getAttributeInt(null, ATTR_MIN_RESIZE_HEIGHT, 0);
info.maxResizeWidth = parser.getAttributeInt(null, ATTR_MAX_RESIZE_WIDTH, 0);
info.maxResizeHeight = parser.getAttributeInt(null, ATTR_MAX_RESIZE_HEIGHT, 0);
info.targetCellWidth = parser.getAttributeInt(null, ATTR_TARGET_CELL_WIDTH, 0);
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 2f8dea7..e282679 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1287,6 +1287,13 @@
}
@Override
+ public int getBatteryHealth() {
+ synchronized (mLock) {
+ return mHealthInfo.batteryHealth;
+ }
+ }
+
+ @Override
public boolean getBatteryLevelLow() {
synchronized (mLock) {
return mBatteryLevelLow;
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 104d10d..3487613 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,6 +19,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
@@ -73,6 +74,7 @@
private final boolean mAllowTheaterModeWakeFromDock;
private final List<ExtconStateConfig> mExtconStateConfigs;
+ private DeviceProvisionedObserver mDeviceProvisionedObserver;
static final class ExtconStateProvider {
private final Map<String, String> mState;
@@ -110,7 +112,7 @@
Slog.w(TAG, "No state file found at: " + stateFilePath);
return new ExtconStateProvider(new HashMap<>());
} catch (Exception e) {
- Slog.e(TAG, "" , e);
+ Slog.e(TAG, "", e);
return new ExtconStateProvider(new HashMap<>());
}
}
@@ -136,7 +138,7 @@
private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) {
String[] rows = context.getResources().getStringArray(
- com.android.internal.R.array.config_dockExtconStateMapping);
+ com.android.internal.R.array.config_dockExtconStateMapping);
try {
ArrayList<ExtconStateConfig> configs = new ArrayList<>();
for (String row : rows) {
@@ -167,6 +169,7 @@
com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
mKeepDreamingWhenUndocking = context.getResources().getBoolean(
com.android.internal.R.bool.config_keepDreamingWhenUndocking);
+ mDeviceProvisionedObserver = new DeviceProvisionedObserver(mHandler);
mExtconStateConfigs = loadExtconStateConfigs(context);
@@ -199,15 +202,19 @@
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
synchronized (mLock) {
mSystemReady = true;
-
- // don't bother broadcasting undocked here
- if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- updateLocked();
- }
+ mDeviceProvisionedObserver.onSystemReady();
+ updateIfDockedLocked();
}
}
}
+ private void updateIfDockedLocked() {
+ // don't bother broadcasting undocked here
+ if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ updateLocked();
+ }
+ }
+
private void setActualDockStateLocked(int newState) {
mActualDockState = newState;
if (!mUpdatesStopped) {
@@ -252,8 +259,7 @@
// Skip the dock intent if not yet provisioned.
final ContentResolver cr = getContext().getContentResolver();
- if (Settings.Global.getInt(cr,
- Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
+ if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
return;
}
@@ -302,6 +308,7 @@
getContext(), soundUri);
if (sfx != null) {
sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+ sfx.preferBuiltinDevice(true);
sfx.play();
}
}
@@ -418,4 +425,48 @@
}
}
}
+
+ private final class DeviceProvisionedObserver extends ContentObserver {
+ private boolean mRegistered;
+
+ public DeviceProvisionedObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ synchronized (mLock) {
+ updateRegistration();
+ if (isDeviceProvisioned()) {
+ // Send the dock broadcast if device is docked after provisioning.
+ updateIfDockedLocked();
+ }
+ }
+ }
+
+ void onSystemReady() {
+ updateRegistration();
+ }
+
+ private void updateRegistration() {
+ boolean register = !isDeviceProvisioned();
+ if (register == mRegistered) {
+ return;
+ }
+ final ContentResolver resolver = getContext().getContentResolver();
+ if (register) {
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false, this);
+ } else {
+ resolver.unregisterContentObserver(this);
+ }
+ mRegistered = register;
+ }
+
+ boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(getContext().getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 78df983..166806b 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -357,13 +357,24 @@
* Starts the given user.
*/
public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
-
final TargetUser targetUser = newTargetUser(userId);
synchronized (mTargetUsers) {
+ // On Automotive / Headless System User Mode, the system user will be started twice:
+ // - Once by some external or local service that switches the system user to
+ // the background.
+ // - Once by the ActivityManagerService, when the system is marked ready.
+ // These two events are not synchronized and the order of execution is
+ // non-deterministic. To avoid starting the system user twice, verify whether
+ // the system user has already been started by checking the mTargetUsers.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move
+ // the headless-user start logic to UserManager-land.
+ if (userId == UserHandle.USER_SYSTEM && mTargetUsers.contains(userId)) {
+ Slog.e(TAG, "Skipping starting system user twice");
+ return;
+ }
mTargetUsers.put(userId, targetUser);
}
-
+ EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index c128b5e..4f1efd6 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -528,7 +528,7 @@
private Map<Account, Integer> getAccountsAndVisibilityForPackage(String packageName,
List<String> accountTypes, Integer callingUid, UserAccounts accounts) {
if (!packageExistsForUser(packageName, accounts.userId)) {
- Log.d(TAG, "Package not found " + packageName);
+ Log.w(TAG, "getAccountsAndVisibilityForPackage#Package not found " + packageName);
return new LinkedHashMap<>();
}
@@ -677,7 +677,7 @@
restoreCallingIdentity(identityToken);
}
} catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found " + e.getMessage());
+ Log.w(TAG, "resolveAccountVisibility#Package not found " + e.getMessage());
return AccountManager.VISIBILITY_NOT_VISIBLE;
}
@@ -756,7 +756,7 @@
}
return true;
} catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found " + e.getMessage());
+ Log.w(TAG, "isPreOApplication#Package not found " + e.getMessage());
return true;
}
}
@@ -4063,7 +4063,7 @@
int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
return hasAccountAccess(account, packageName, uid);
} catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found " + e.getMessage());
+ Log.w(TAG, "hasAccountAccess#Package not found " + e.getMessage());
return false;
}
}
@@ -4195,7 +4195,7 @@
}
final long token = Binder.clearCallingIdentity();
try {
- AccountAndUser[] allAccounts = getAllAccounts();
+ AccountAndUser[] allAccounts = getAllAccountsForSystemProcess();
for (int i = allAccounts.length - 1; i >= 0; i--) {
if (allAccounts[i].account.equals(account)) {
return true;
@@ -4345,10 +4345,11 @@
/**
* Returns accounts for all running users, ignores visibility values.
*
+ * Should only be called by System process.
* @hide
*/
@NonNull
- public AccountAndUser[] getRunningAccounts() {
+ public AccountAndUser[] getRunningAccountsForSystem() {
final int[] runningUserIds;
try {
runningUserIds = ActivityManager.getService().getRunningUserIds();
@@ -4356,26 +4357,34 @@
// Running in system_server; should never happen
throw new RuntimeException(e);
}
- return getAccounts(runningUserIds);
+ return getAccountsForSystem(runningUserIds);
}
/**
* Returns accounts for all users, ignores visibility values.
*
+ * Should only be called by system process
+ *
* @hide
*/
@NonNull
- public AccountAndUser[] getAllAccounts() {
+ public AccountAndUser[] getAllAccountsForSystemProcess() {
final List<UserInfo> users = getUserManager().getAliveUsers();
final int[] userIds = new int[users.size()];
for (int i = 0; i < userIds.length; i++) {
userIds[i] = users.get(i).id;
}
- return getAccounts(userIds);
+ return getAccountsForSystem(userIds);
}
+ /**
+ * Returns all accounts for the given user, ignores all visibility checks.
+ * This should only be called by system process.
+ *
+ * @hide
+ */
@NonNull
- private AccountAndUser[] getAccounts(int[] userIds) {
+ private AccountAndUser[] getAccountsForSystem(int[] userIds) {
final ArrayList<AccountAndUser> runningAccounts = Lists.newArrayList();
for (int userId : userIds) {
UserAccounts userAccounts = getUserAccounts(userId);
@@ -4384,7 +4393,7 @@
userAccounts,
null /* type */,
Binder.getCallingUid(),
- null /* packageName */,
+ "android"/* packageName */,
false /* include managed not visible*/);
for (Account account : accounts) {
runningAccounts.add(new AccountAndUser(account, userId));
@@ -5355,7 +5364,7 @@
}
} else {
Account[] accounts = getAccountsFromCache(userAccounts, null /* type */,
- Process.SYSTEM_UID, null /* packageName */, false);
+ Process.SYSTEM_UID, "android" /* packageName */, false);
fout.println("Accounts: " + accounts.length);
for (Account account : accounts) {
fout.println(" " + account.toString());
@@ -5550,7 +5559,7 @@
return true;
}
} catch (PackageManager.NameNotFoundException e) {
- Log.d(TAG, "Package not found " + e.getMessage());
+ Log.w(TAG, "isPrivileged#Package not found " + e.getMessage());
}
}
} finally {
@@ -6074,7 +6083,7 @@
}
}
} catch (NameNotFoundException e) {
- Log.d(TAG, "Package not found " + e.getMessage());
+ Log.w(TAG, "filterSharedAccounts#Package not found " + e.getMessage());
}
Map<Account, Integer> filtered = new LinkedHashMap<>();
for (Map.Entry<Account, Integer> entry : unfiltered.entrySet()) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index c372096..3016228 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -246,6 +246,18 @@
private static final String KEY_MAX_PHANTOM_PROCESSES = "max_phantom_processes";
/**
+ * Enables proactive killing of cached apps
+ */
+ private static final String KEY_PROACTIVE_KILLS_ENABLED = "proactive_kills_enabled";
+
+ /**
+ * Trim LRU cached app when swap falls below this minimum percentage.
+ *
+ * Depends on KEY_PROACTIVE_KILLS_ENABLED
+ */
+ private static final String KEY_LOW_SWAP_THRESHOLD_PERCENT = "low_swap_threshold_percent";
+
+ /**
* Default value for mFlagBackgroundActivityStartsEnabled if not explicitly set in
* Settings.Global. This allows it to be set experimentally unless it has been
* enabled/disabled in developer options. Defaults to false.
@@ -833,6 +845,10 @@
*/
private static final long DEFAULT_MIN_ASSOC_LOG_DURATION = 5 * 60 * 1000; // 5 mins
+ private static final boolean DEFAULT_PROACTIVE_KILLS_ENABLED = false;
+
+ private static final float DEFAULT_LOW_SWAP_THRESHOLD_PERCENT = 0.10f;
+
private static final String KEY_MIN_ASSOC_LOG_DURATION = "min_assoc_log_duration";
public static long MIN_ASSOC_LOG_DURATION = DEFAULT_MIN_ASSOC_LOG_DURATION;
@@ -863,6 +879,8 @@
public static boolean BINDER_HEAVY_HITTER_AUTO_SAMPLER_ENABLED;
public static int BINDER_HEAVY_HITTER_AUTO_SAMPLER_BATCHSIZE;
public static float BINDER_HEAVY_HITTER_AUTO_SAMPLER_THRESHOLD;
+ public static boolean PROACTIVE_KILLS_ENABLED = DEFAULT_PROACTIVE_KILLS_ENABLED;
+ public static float LOW_SWAP_THRESHOLD_PERCENT = DEFAULT_LOW_SWAP_THRESHOLD_PERCENT;
private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
new OnPropertiesChangedListener() {
@@ -990,6 +1008,12 @@
case KEY_NETWORK_ACCESS_TIMEOUT_MS:
updateNetworkAccessTimeoutMs();
break;
+ case KEY_PROACTIVE_KILLS_ENABLED:
+ updateProactiveKillsEnabled();
+ break;
+ case KEY_LOW_SWAP_THRESHOLD_PERCENT:
+ updateLowSwapThresholdPercent();
+ break;
default:
break;
}
@@ -1592,6 +1616,20 @@
CUR_TRIM_CACHED_PROCESSES = (MAX_CACHED_PROCESSES-rawMaxEmptyProcesses)/3;
}
+ private void updateProactiveKillsEnabled() {
+ PROACTIVE_KILLS_ENABLED = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_PROACTIVE_KILLS_ENABLED,
+ DEFAULT_PROACTIVE_KILLS_ENABLED);
+ }
+
+ private void updateLowSwapThresholdPercent() {
+ LOW_SWAP_THRESHOLD_PERCENT = DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_LOW_SWAP_THRESHOLD_PERCENT,
+ DEFAULT_LOW_SWAP_THRESHOLD_PERCENT);
+ }
+
private void updateMinAssocLogDuration() {
MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION,
@@ -1779,6 +1817,10 @@
pw.print("="); pw.println(mServiceBindAlmostPerceptibleTimeoutMs);
pw.print(" "); pw.print(KEY_NETWORK_ACCESS_TIMEOUT_MS);
pw.print("="); pw.println(mNetworkAccessTimeoutMs);
+ pw.print(" "); pw.print(KEY_PROACTIVE_KILLS_ENABLED);
+ pw.print("="); pw.println(PROACTIVE_KILLS_ENABLED);
+ pw.print(" "); pw.print(KEY_LOW_SWAP_THRESHOLD_PERCENT);
+ pw.print("="); pw.println(LOW_SWAP_THRESHOLD_PERCENT);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index fde96b9..bc083f1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8163,15 +8163,12 @@
mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
Integer.toString(currentUserId), currentUserId);
- // On Automotive, at this point the system user has already been started and unlocked,
- // and some of the tasks we do here have already been done. So skip those in that case.
- // TODO(b/132262830, b/203885241): this workdound shouldn't be necessary once we move the
- // headless-user start logic to UserManager-land
- final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
-
- if (bootingSystemUser) {
- mSystemServiceManager.onUserStarting(t, currentUserId);
- }
+ // On Automotive / Headless System User Mode, at this point the system user has already been
+ // started and unlocked, and some of the tasks we do here have already been done. So skip
+ // those in that case. The duplicate system user start is guarded in SystemServiceManager.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user
+ // start logic to UserManager-land.
+ mSystemServiceManager.onUserStarting(t, currentUserId);
synchronized (this) {
// Only start up encryption-aware persistent apps; once user is
@@ -8201,7 +8198,15 @@
t.traceEnd();
}
- if (bootingSystemUser) {
+ // Some systems - like automotive - will explicitly unlock system user then switch
+ // to a secondary user. Hence, we don't want to send duplicate broadcasts for
+ // the system user here.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move
+ // the headless-user start logic to UserManager-land.
+ final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM)
+ && !UserManager.isHeadlessSystemUserMode();
+
+ if (isBootingSystemUser) {
t.traceBegin("startHomeOnAllDisplays");
mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
t.traceEnd();
@@ -8212,7 +8217,7 @@
t.traceEnd();
- if (bootingSystemUser) {
+ if (isBootingSystemUser) {
t.traceBegin("sendUserStartBroadcast");
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
@@ -8253,7 +8258,7 @@
mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
t.traceEnd();
- if (bootingSystemUser) {
+ if (isBootingSystemUser) {
t.traceBegin("sendUserSwitchBroadcasts");
mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
t.traceEnd();
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 2ec744f..c868f53 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -248,6 +248,8 @@
return runForceStop(pw);
case "stop-app":
return runStopApp(pw);
+ case "clear-recent-apps":
+ return runClearRecentApps(pw);
case "fgs-notification-rate-limit":
return runFgsNotificationRateLimit(pw);
case "crash":
@@ -1192,6 +1194,11 @@
return 0;
}
+ int runClearRecentApps(PrintWriter pw) throws RemoteException {
+ mTaskInterface.removeAllVisibleRecentTasks();
+ return 0;
+ }
+
int runFgsNotificationRateLimit(PrintWriter pw) throws RemoteException {
final String toggleValue = getNextArgRequired();
final boolean enable;
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index c235e05..621e3db 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -45,10 +45,10 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.ApplicationExitInfo;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
import android.app.PendingIntent;
-import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
@@ -665,18 +665,14 @@
thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
data, extras, ordered, sticky, sendingUser,
app.mState.getReportedProcState());
- // TODO: Uncomment this when (b/28322359) is fixed and we aren't getting
- // DeadObjectException when the process isn't actually dead.
- //} catch (DeadObjectException ex) {
- // Failed to call into the process. It's dying so just let it die and move on.
- // throw ex;
} catch (RemoteException ex) {
// Failed to call into the process. It's either dying or wedged. Kill it gently.
synchronized (mService) {
- Slog.w(TAG, "Can't deliver broadcast to " + app.processName
- + " (pid " + app.getPid() + "). Crashing it.");
- app.scheduleCrashLocked("can't deliver broadcast",
- CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
+ final String msg = "Failed to schedule " + intent + " to " + receiver
+ + " via " + app + ": " + ex;
+ Slog.w(TAG, msg);
+ app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
}
throw ex;
}
@@ -1890,8 +1886,11 @@
processCurBroadcastLocked(r, app);
return;
} catch (RemoteException e) {
- Slog.w(TAG, "Exception when sending broadcast to "
- + r.curComponent, e);
+ final String msg = "Failed to schedule " + r.intent + " to " + info
+ + " via " + app + ": " + e;
+ Slog.w(TAG, msg);
+ app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
} catch (RuntimeException e) {
Slog.wtf(TAG, "Failed sending broadcast to "
+ r.curComponent + " with " + r.intent, e);
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 9921956..cee9f79 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -651,7 +651,7 @@
/**
* Retrieves the free swap percentage.
*/
- static private native double getFreeSwapPercent();
+ static native double getFreeSwapPercent();
/**
* Reads the flag value from DeviceConfig to determine whether app compaction
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index bc939d6..441506d 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1034,6 +1034,12 @@
private long mNextNoKillDebugMessageTime;
+ private double mLastFreeSwapPercent = 1.00;
+
+ private static double getFreeSwapPercent() {
+ return CachedAppOptimizer.getFreeSwapPercent();
+ }
+
@GuardedBy({"mService", "mProcLock"})
private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
final long oldTime, final ActiveUids activeUids, String oomAdjReason) {
@@ -1058,6 +1064,11 @@
int numEmpty = 0;
int numTrimming = 0;
+ boolean proactiveKillsEnabled = mConstants.PROACTIVE_KILLS_ENABLED;
+ double lowSwapThresholdPercent = mConstants.LOW_SWAP_THRESHOLD_PERCENT;
+ double freeSwapPercent = proactiveKillsEnabled ? getFreeSwapPercent() : 1.00;
+ ProcessRecord lruCachedApp = null;
+
for (int i = numLru - 1; i >= 0; i--) {
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
@@ -1095,6 +1106,8 @@
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
true);
+ } else if (proactiveKillsEnabled) {
+ lruCachedApp = app;
}
break;
case PROCESS_STATE_CACHED_EMPTY:
@@ -1114,6 +1127,8 @@
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY,
true);
+ } else if (proactiveKillsEnabled) {
+ lruCachedApp = app;
}
}
break;
@@ -1145,6 +1160,20 @@
}
}
+ if (proactiveKillsEnabled // Proactive kills enabled?
+ && doKillExcessiveProcesses // Should kill excessive processes?
+ && freeSwapPercent < lowSwapThresholdPercent // Swap below threshold?
+ && lruCachedApp != null // If no cached app, let LMKD decide
+ // If swap is non-decreasing, give reclaim a chance to catch up
+ && freeSwapPercent < mLastFreeSwapPercent) {
+ lruCachedApp.killLocked("swap low and too many cached",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
+ true);
+ }
+
+ mLastFreeSwapPercent = freeSwapPercent;
+
return mService.mAppProfiler.updateLowMemStateLSP(numCached, numEmpty, numTrimming);
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 3eac406..9700e56 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2207,16 +2207,25 @@
Map<String, Pair<String, Long>> allowlistedAppDataInfoMap;
boolean bindMountAppStorageDirs = false;
boolean bindMountAppsData = mAppDataIsolationEnabled
- && (UserHandle.isApp(app.uid) || UserHandle.isIsolated(app.uid))
+ && (UserHandle.isApp(app.uid) || UserHandle.isIsolated(app.uid)
+ || app.isSdkSandbox)
&& mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info);
// Get all packages belongs to the same shared uid. sharedPackages is empty array
// if it doesn't have shared uid.
final PackageManagerInternal pmInt = mService.getPackageManagerInternal();
- final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage(
- app.info.packageName, app.userId);
- final String[] targetPackagesList = sharedPackages.length == 0
- ? new String[]{app.info.packageName} : sharedPackages;
+
+ // In the case of sdk sandbox, the pkgDataInfoMap of only the client app associated with
+ // the sandbox is required to handle app visibility restrictions for the sandbox.
+ final String[] targetPackagesList;
+ if (app.isSdkSandbox) {
+ targetPackagesList = new String[]{app.sdkSandboxClientAppPackage};
+ } else {
+ final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage(
+ app.info.packageName, app.userId);
+ targetPackagesList = sharedPackages.length == 0
+ ? new String[]{app.info.packageName} : sharedPackages;
+ }
final boolean hasAppStorage = hasAppStorage(pmInt, app.info.packageName);
@@ -2242,7 +2251,7 @@
bindMountAppsData = false;
}
- if (!hasAppStorage) {
+ if (!hasAppStorage && !app.isSdkSandbox) {
bindMountAppsData = false;
pkgDataInfoMap = null;
allowlistedAppDataInfoMap = null;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 4a9ac77..17cd5d1 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -86,6 +86,7 @@
DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS,
DeviceConfig.NAMESPACE_LMKD_NATIVE,
DeviceConfig.NAMESPACE_MEDIA_NATIVE,
+ DeviceConfig.NAMESPACE_MGLRU_NATIVE,
DeviceConfig.NAMESPACE_NETD_NATIVE,
DeviceConfig.NAMESPACE_NNAPI_NATIVE,
DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 4013ace..3584f16 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -1276,20 +1276,12 @@
void onUserSwitching(TargetUser from, TargetUser to) {
final int toUserId = to.getUserIdentifier();
- if (from != null) {
- synchronized (mLock) {
- final int fromUserId = from.getUserIdentifier();
- if (mSettings.containsKey(fromUserId)) {
- final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
- msg.obj = fromUserId;
- mHandler.sendMessage(msg);
- }
- }
- }
+ // we want to re-populate the setting when switching user as the device config may have
+ // changed, which will only update for the previous user, see
+ // DeviceConfigListener#onPropertiesChanged.
final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
msg.obj = toUserId;
mHandler.sendMessage(msg);
-
if (mGameServiceController != null) {
mGameServiceController.notifyNewForegroundUser(to);
}
@@ -1429,9 +1421,10 @@
Slog.v(TAG, "Package configuration not found for " + packageName);
return;
}
+ } else {
+ updateFps(packageConfig, packageName, gameMode, userId);
+ updateCompatModeDownscale(packageConfig, packageName, gameMode);
}
- updateCompatModeDownscale(packageConfig, packageName, gameMode);
- updateFps(packageConfig, packageName, gameMode, userId);
updateUseAngle(packageName, gameMode);
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 53fcf32..e8f2549 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3599,6 +3599,18 @@
}
}
+ // TODO enforce MODIFY_AUDIO_SYSTEM_SETTINGS when defined
+ private void enforceModifyAudioRoutingOrSystemSettingsPermission() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ != PackageManager.PERMISSION_GRANTED
+ /*&& mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+ != PackageManager.PERMISSION_DENIED*/) {
+ throw new SecurityException(
+ "Missing MODIFY_AUDIO_ROUTING or MODIFY_AUDIO_SYSTEM_SETTINGS permission");
+ }
+ }
+
private void enforceAccessUltrasoundPermission() {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND)
!= PackageManager.PERMISSION_GRANTED) {
@@ -3703,20 +3715,38 @@
}
/** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes)
- * Part of service interface, check permissions and parameters here */
+ * Part of service interface, check permissions and parameters here
+ * Note calling package is for logging purposes only, not to be trusted
+ */
public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada,
- @NonNull String callingPackage, @Nullable String attributionTag) {
- enforceModifyAudioRoutingPermission();
+ @NonNull String callingPackage) {
+ enforceModifyAudioRoutingOrSystemSettingsPermission();
Objects.requireNonNull(vi);
Objects.requireNonNull(ada);
Objects.requireNonNull(callingPackage);
+
+ AudioService.sVolumeLogger.loglogi("setDeviceVolume" + " from:" + callingPackage + " "
+ + vi + " " + ada, TAG);
+
if (!vi.hasStreamType()) {
Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
return;
}
int index = vi.getVolumeIndex();
- if (index == VolumeInfo.INDEX_NOT_SET) {
- throw new IllegalArgumentException("changing device volume requires a volume index");
+ if (index == VolumeInfo.INDEX_NOT_SET && !vi.hasMuteCommand()) {
+ throw new IllegalArgumentException(
+ "changing device volume requires a volume index or mute command");
+ }
+
+ // TODO handle unmuting of current audio device
+ // if a stream is not muted but the VolumeInfo is for muting, set the volume index
+ // for the device to min volume
+ if (vi.hasMuteCommand() && vi.isMuted() && !isStreamMute(vi.getStreamType())) {
+ setStreamVolumeWithAttributionInt(vi.getStreamType(),
+ mStreamStates[vi.getStreamType()].getMinIndex(),
+ /*flags*/ 0,
+ ada, callingPackage, null);
+ return;
}
if (vi.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET
@@ -3738,7 +3768,7 @@
}
}
setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0,
- ada, callingPackage, attributionTag);
+ ada, callingPackage, null);
}
/** Retain API for unsupported app usage */
@@ -4644,6 +4674,40 @@
}
}
+ /**
+ * @see AudioDeviceVolumeManager#getDeviceVolume(VolumeInfo, AudioDeviceAttributes)
+ */
+ public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
+ @NonNull AudioDeviceAttributes ada, @NonNull String callingPackage) {
+ enforceModifyAudioRoutingOrSystemSettingsPermission();
+ Objects.requireNonNull(vi);
+ Objects.requireNonNull(ada);
+ Objects.requireNonNull(callingPackage);
+ if (!vi.hasStreamType()) {
+ Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
+ return getDefaultVolumeInfo();
+ }
+
+ int streamType = vi.getStreamType();
+ final VolumeInfo.Builder vib = new VolumeInfo.Builder(vi);
+ vib.setMinVolumeIndex((mStreamStates[streamType].mIndexMin + 5) / 10);
+ vib.setMaxVolumeIndex((mStreamStates[streamType].mIndexMax + 5) / 10);
+ synchronized (VolumeStreamState.class) {
+ final int index;
+ if (isFixedVolumeDevice(ada.getInternalType())) {
+ index = (mStreamStates[streamType].mIndexMax + 5) / 10;
+ } else {
+ index = (mStreamStates[streamType].getIndex(ada.getInternalType()) + 5) / 10;
+ }
+ vib.setVolumeIndex(index);
+ // only set as a mute command if stream muted
+ if (mStreamStates[streamType].mIsMuted) {
+ vib.setMuted(true);
+ }
+ return vib.build();
+ }
+ }
+
/** @see AudioManager#getStreamMaxVolume(int) */
public int getStreamMaxVolume(int streamType) {
ensureValidStreamType(streamType);
@@ -4682,7 +4746,6 @@
sDefaultVolumeInfo = new VolumeInfo.Builder(AudioSystem.STREAM_MUSIC)
.setMinVolumeIndex(getStreamMinVolume(AudioSystem.STREAM_MUSIC))
.setMaxVolumeIndex(getStreamMaxVolume(AudioSystem.STREAM_MUSIC))
- .setMuted(false)
.build();
}
return sDefaultVolumeInfo;
@@ -6992,9 +7055,10 @@
private @AudioManager.DeviceVolumeBehavior
int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) {
- // translate Java device type to native device type (for the devices masks for full / fixed)
- final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
- device.getType());
+ // Get the internal type set by the AudioDeviceAttributes constructor which is always more
+ // exact (avoids double conversions) than a conversion from SDK type via
+ // AudioDeviceInfo.convertDeviceTypeToInternalDevice()
+ final int audioSystemDeviceOut = device.getInternalType();
int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut);
if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) {
@@ -7839,6 +7903,7 @@
boolean hasModifyAudioSettings) {
boolean changed;
int oldIndex;
+ final boolean isCurrentDevice;
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
oldIndex = getIndex(device);
@@ -7854,7 +7919,7 @@
// - there is no volume index stored for this device on alias stream.
// If changing volume of current device, also change volume of current
// device on aliased stream
- final boolean isCurrentDevice = (device == getDeviceForStream(mStreamType));
+ isCurrentDevice = (device == getDeviceForStream(mStreamType));
final int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
final VolumeStreamState aliasStreamState = mStreamStates[streamType];
@@ -7894,8 +7959,9 @@
EventLogTags.writeVolumeChanged(mStreamType, oldIndex, index, mIndexMax / 10,
caller);
}
- // fire changed intents for all streams
- if (index != oldIndex) {
+ // fire changed intents for all streams, but only when the device it changed on
+ // is the current device
+ if ((index != oldIndex) && isCurrentDevice) {
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index da43618..d584c99 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -120,7 +120,7 @@
// if a final consumer is set it will call destroy/disable on the next value if requested
if (!mDestroyed && mNextConsumer == null) {
- disableLightSensorLoggingLocked();
+ disableLightSensorLoggingLocked(false /* destroying */);
}
}
@@ -130,7 +130,7 @@
// if a final consumer is set it will call destroy/disable on the next value if requested
if (!mDestroyed && mNextConsumer == null) {
- disableLightSensorLoggingLocked();
+ disableLightSensorLoggingLocked(true /* destroying */);
mDestroyed = true;
}
}
@@ -177,11 +177,10 @@
final float current = mLastAmbientLux;
if (current > -1f) {
nextConsumer.consume(current);
- } else if (mDestroyed) {
- nextConsumer.consume(-1f);
} else if (mNextConsumer != null) {
mNextConsumer.add(nextConsumer);
} else {
+ mDestroyed = false;
mNextConsumer = nextConsumer;
enableLightSensorLoggingLocked();
}
@@ -199,12 +198,14 @@
resetTimerLocked(true /* start */);
}
- private void disableLightSensorLoggingLocked() {
+ private void disableLightSensorLoggingLocked(boolean destroying) {
resetTimerLocked(false /* start */);
if (mEnabled) {
mEnabled = false;
- mLastAmbientLux = -1;
+ if (!destroying) {
+ mLastAmbientLux = -1;
+ }
mSensorManager.unregisterListener(mLightSensorListener);
Slog.v(TAG, "Disable ALS: " + mLightSensorListener.hashCode());
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 94b67ce..598e2b9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -452,13 +452,6 @@
return -1;
}
- if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
- // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
- // ever be invoked when the user is encrypted or lockdown.
- Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
- return -1;
- }
-
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 73afa60..eb81e70 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -2215,7 +2215,8 @@
pw.print("Storage low: "); pw.println(storageLowIntent != null);
pw.print("Clock valid: "); pw.println(mSyncStorageEngine.isClockValid());
- final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
+ final AccountAndUser[] accounts =
+ AccountManagerService.getSingleton().getAllAccountsForSystemProcess();
pw.print("Accounts: ");
if (accounts != INITIAL_ACCOUNTS_ARRAY) {
@@ -3274,7 +3275,8 @@
private void updateRunningAccountsH(EndPoint syncTargets) {
synchronized (mAccountsLock) {
AccountAndUser[] oldAccounts = mRunningAccounts;
- mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
+ mRunningAccounts =
+ AccountManagerService.getSingleton().getRunningAccountsForSystem();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Slog.v(TAG, "Accounts list: ");
for (AccountAndUser acc : mRunningAccounts) {
@@ -3316,7 +3318,8 @@
}
// Cancel all jobs from non-existent accounts.
- AccountAndUser[] allAccounts = AccountManagerService.getSingleton().getAllAccounts();
+ AccountAndUser[] allAccounts =
+ AccountManagerService.getSingleton().getAllAccountsForSystemProcess();
List<SyncOperation> ops = getAllPendingSyncs();
for (int i = 0, opsSize = ops.size(); i < opsSize; i++) {
SyncOperation op = ops.get(i);
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index f8d4b8f..42fe9d8 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -18,11 +18,11 @@
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
-import static android.view.Display.DEFAULT_DISPLAY;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceStateManager;
import com.android.internal.util.Preconditions;
@@ -55,6 +55,15 @@
*/
public static final int FLAG_APP_INACCESSIBLE = 1 << 1;
+ /**
+ * Some device states can be both entered through a physical configuration as well as emulation
+ * through {@link DeviceStateManager#requestState}, while some states can only be entered
+ * through emulation and have no physical configuration to match.
+ *
+ * This flag indicates that the corresponding state can only be entered through emulation.
+ */
+ public static final int FLAG_EMULATED_ONLY = 1 << 2;
+
/** @hide */
@IntDef(prefix = {"FLAG_"}, flag = true, value = {
FLAG_CANCEL_OVERRIDE_REQUESTS,
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 44c8e18..c856cab 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,6 +17,8 @@
package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
@@ -56,6 +58,7 @@
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowProcessController;
@@ -157,6 +160,15 @@
private Set<Integer> mDeviceStatesAvailableForAppRequests;
+ private Set<Integer> mFoldedDeviceStates;
+
+ @Nullable
+ private DeviceState mRearDisplayState;
+
+ // TODO(259328837) Generalize for all pending feature requests in the future
+ @Nullable
+ private OverrideRequest mRearDisplayPendingOverrideRequest;
+
@VisibleForTesting
interface SystemPropertySetter {
void setDebugTracingDeviceStateProperty(String value);
@@ -201,6 +213,7 @@
synchronized (mLock) {
readStatesAvailableForRequestFromApps();
+ mFoldedDeviceStates = readFoldedStates();
}
}
@@ -350,6 +363,8 @@
mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
updatePendingStateLocked();
+ setRearDisplayStateLocked();
+
if (!mPendingState.isPresent()) {
// If the change in the supported states didn't result in a change of the pending
// state commitPendingState() will never be called and the callbacks will never be
@@ -361,6 +376,15 @@
}
}
+ @GuardedBy("mLock")
+ private void setRearDisplayStateLocked() {
+ int rearDisplayIdentifier = getContext().getResources().getInteger(
+ R.integer.config_deviceStateRearDisplay);
+ if (rearDisplayIdentifier != INVALID_DEVICE_STATE) {
+ mRearDisplayState = mDeviceStates.get(rearDisplayIdentifier);
+ }
+ }
+
/**
* Returns {@code true} if the provided state is supported. Requires that
* {@link #mDeviceStates} is sorted prior to calling.
@@ -398,6 +422,10 @@
// Base state hasn't changed. Nothing to do.
return;
}
+ // There is a pending rear display request, so we check if the overlay should be closed
+ if (mRearDisplayPendingOverrideRequest != null) {
+ handleRearDisplayBaseStateChangedLocked(identifier);
+ }
mBaseState = Optional.of(baseState);
if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
@@ -663,7 +691,7 @@
}
private void requestStateInternal(int state, int flags, int callingPid,
- @NonNull IBinder token) {
+ @NonNull IBinder token, boolean hasControlDeviceStatePermission) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
if (processRecord == null) {
@@ -685,7 +713,30 @@
OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
- mOverrideRequestController.addRequest(request);
+
+ // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
+ if (!hasControlDeviceStatePermission && mRearDisplayState != null
+ && state == mRearDisplayState.getIdentifier()) {
+ showRearDisplayEducationalOverlayLocked(request);
+ } else {
+ mOverrideRequestController.addRequest(request);
+ }
+ }
+ }
+
+ /**
+ * If we get a request to enter rear display mode, we need to display an educational
+ * overlay to let the user know what will happen. This calls into the
+ * {@link StatusBarManagerInternal} to notify SystemUI to display the educational dialog.
+ */
+ @GuardedBy("mLock")
+ private void showRearDisplayEducationalOverlayLocked(OverrideRequest request) {
+ mRearDisplayPendingOverrideRequest = request;
+
+ StatusBarManagerInternal statusBar =
+ LocalServices.getService(StatusBarManagerInternal.class);
+ if (statusBar != null) {
+ statusBar.showRearDisplayDialog(mBaseState.get().getIdentifier());
}
}
@@ -738,6 +789,27 @@
}
}
+ /**
+ * Adds the rear display state request to the {@link OverrideRequestController} if the
+ * educational overlay was closed in a way that should enable the feature, and cancels the
+ * request if it was dismissed in a way that should cancel the feature.
+ */
+ private void onStateRequestOverlayDismissedInternal(boolean shouldCancelRequest) {
+ if (mRearDisplayPendingOverrideRequest != null) {
+ synchronized (mLock) {
+ if (shouldCancelRequest) {
+ ProcessRecord processRecord = mProcessRecords.get(
+ mRearDisplayPendingOverrideRequest.getPid());
+ processRecord.notifyRequestCanceledAsync(
+ mRearDisplayPendingOverrideRequest.getToken());
+ } else {
+ mOverrideRequestController.addRequest(mRearDisplayPendingOverrideRequest);
+ }
+ mRearDisplayPendingOverrideRequest = null;
+ }
+ }
+ }
+
private void dumpInternal(PrintWriter pw) {
pw.println("DEVICE STATE MANAGER (dumpsys device_state)");
@@ -823,6 +895,16 @@
}
}
+ private Set<Integer> readFoldedStates() {
+ Set<Integer> foldedStates = new HashSet();
+ int[] mFoldedStatesArray = getContext().getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates);
+ for (int i = 0; i < mFoldedStatesArray.length; i++) {
+ foldedStates.add(mFoldedStatesArray[i]);
+ }
+ return foldedStates;
+ }
+
@GuardedBy("mLock")
private boolean isValidState(int state) {
for (int i = 0; i < mDeviceStates.size(); i++) {
@@ -833,6 +915,28 @@
return false;
}
+ /**
+ * If the device is being opened, in response to the rear display educational overlay, we should
+ * dismiss the overlay and enter the mode.
+ */
+ @GuardedBy("mLock")
+ private void handleRearDisplayBaseStateChangedLocked(int newBaseState) {
+ if (isDeviceOpeningLocked(newBaseState)) {
+ onStateRequestOverlayDismissedInternal(false);
+ }
+ }
+
+ /**
+ * Determines if the device is being opened and if we are going from a folded state to a
+ * non-folded state.
+ */
+ @GuardedBy("mLock")
+ private boolean isDeviceOpeningLocked(int newBaseState) {
+ return mBaseState.filter(
+ deviceState -> mFoldedDeviceStates.contains(deviceState.getIdentifier())
+ && !mFoldedDeviceStates.contains(newBaseState)).isPresent();
+ }
+
private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
@@ -850,6 +954,7 @@
if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
throw new IllegalArgumentException("Invalid identifier: " + identifier);
}
+
mCurrentBaseState = identifier;
setBaseState(identifier);
}
@@ -977,9 +1082,12 @@
throw new IllegalArgumentException("Request token must not be null.");
}
+ boolean hasControlStatePermission = getContext().checkCallingOrSelfPermission(
+ CONTROL_DEVICE_STATE) == PERMISSION_GRANTED;
+
final long callingIdentity = Binder.clearCallingIdentity();
try {
- requestStateInternal(state, flags, callingPid, token);
+ requestStateInternal(state, flags, callingPid, token, hasControlStatePermission);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -1034,6 +1142,21 @@
}
@Override // Binder call
+ public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) {
+
+ getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+ "CONTROL_DEVICE_STATE permission required to control the state request "
+ + "overlay");
+
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ onStateRequestOverlayDismissedInternal(shouldCancelRequest);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override // Binder call
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver result) {
new DeviceStateManagerShellCommand(DeviceStateManagerService.this)
diff --git a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
index d74c702..7c9a484 100644
--- a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
+++ b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
@@ -27,6 +27,7 @@
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -287,6 +288,14 @@
localDate)) {
return lastBrightnessStats;
} else {
+ // It is a new day, and we have available data, so log data. The daily boundary
+ // might not be right if the user changes timezones but that is fine, since it
+ // won't be that frequent.
+ if (lastBrightnessStats != null) {
+ FrameworkStatsLog.write(FrameworkStatsLog.AMBIENT_BRIGHTNESS_STATS_REPORTED,
+ lastBrightnessStats.getStats(),
+ lastBrightnessStats.getBucketBoundaries());
+ }
AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(localDate,
BUCKET_BOUNDARIES_FOR_NEW_STATS);
if (userStats.size() == MAX_DAYS_TO_TRACK) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e51976b..1b20e6a 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -123,6 +123,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
@@ -146,6 +147,7 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
+
/**
* Manages attached displays.
* <p>
@@ -1880,6 +1882,15 @@
if (displayDevice == null) {
return;
}
+ if (mLogicalDisplayMapper.getDisplayLocked(displayDevice) != null
+ && mLogicalDisplayMapper.getDisplayLocked(displayDevice)
+ .getDisplayInfoLocked().type == Display.TYPE_INTERNAL && c != null) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BRIGHTNESS_CONFIGURATION_UPDATED,
+ c.getCurve().first,
+ c.getCurve().second,
+ // should not be logged for virtual displays
+ uniqueId);
+ }
mPersistentDataStore.setBrightnessConfigurationForDisplayLocked(c, displayDevice,
userSerial, packageName);
} finally {
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 912b1b2..c131ed6 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -523,8 +523,10 @@
* changed
*/
public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
- mSettingsObserver.setRefreshRates(displayDeviceConfig);
- mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig);
+ mSettingsObserver.setRefreshRates(displayDeviceConfig,
+ /* attemptLoadingFromDeviceConfig= */ true);
+ mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
+ /* attemptLoadingFromDeviceConfig= */ true);
}
/**
@@ -1142,19 +1144,25 @@
SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
super(handler);
mContext = context;
- setRefreshRates(/* displayDeviceConfig= */ null);
+ // We don't want to load from the DeviceConfig while constructing since this leads to
+ // a spike in the latency of DisplayManagerService startup. This happens because
+ // reading from the DeviceConfig is an intensive IO operation and having it in the
+ // startup phase where we thrive to keep the latency very low has significant impact.
+ setRefreshRates(/* displayDeviceConfig= */ null,
+ /* attemptLoadingFromDeviceConfig= */ false);
}
/**
* This is used to update the refresh rate configs from the DeviceConfig, which
* if missing from DisplayDeviceConfig, and finally fallback to config.xml.
*/
- public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig) {
- setDefaultPeakRefreshRate(displayDeviceConfig);
+ public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
+ setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig);
mDefaultRefreshRate =
(displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
- R.integer.config_defaultRefreshRate)
- : (float) displayDeviceConfig.getDefaultRefreshRate();
+ R.integer.config_defaultRefreshRate)
+ : (float) displayDeviceConfig.getDefaultRefreshRate();
}
public void observe() {
@@ -1215,13 +1223,27 @@
}
}
- private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig) {
+ @VisibleForTesting
+ float getDefaultRefreshRate() {
+ return mDefaultRefreshRate;
+ }
+
+ @VisibleForTesting
+ float getDefaultPeakRefreshRate() {
+ return mDefaultPeakRefreshRate;
+ }
+
+ private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
Float defaultPeakRefreshRate = null;
- try {
- defaultPeakRefreshRate =
+
+ if (attemptLoadingFromDeviceConfig) {
+ try {
+ defaultPeakRefreshRate =
mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
- } catch (Exception exception) {
- // Do nothing
+ } catch (Exception exception) {
+ // Do nothing
+ }
}
if (defaultPeakRefreshRate == null) {
defaultPeakRefreshRate =
@@ -1544,7 +1566,8 @@
mContext = context;
mHandler = handler;
mInjector = injector;
- updateBlockingZoneThresholds(/* displayDeviceConfig= */ null);
+ updateBlockingZoneThresholds(/* displayDeviceConfig= */ null,
+ /* attemptLoadingFromDeviceConfig= */ false);
mRefreshRateInHighZone = context.getResources().getInteger(
R.integer.config_fixedRefreshRateInHighZone);
}
@@ -1553,22 +1576,44 @@
* This is used to update the blocking zone thresholds from the DeviceConfig, which
* if missing from DisplayDeviceConfig, and finally fallback to config.xml.
*/
- public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig) {
- loadLowBrightnessThresholds(displayDeviceConfig);
- loadHighBrightnessThresholds(displayDeviceConfig);
+ public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
+ loadLowBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
+ loadHighBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig);
}
- private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+ @VisibleForTesting
+ int[] getLowDisplayBrightnessThreshold() {
+ return mLowDisplayBrightnessThresholds;
+ }
+
+ @VisibleForTesting
+ int[] getLowAmbientBrightnessThreshold() {
+ return mLowAmbientBrightnessThresholds;
+ }
+
+ @VisibleForTesting
+ int[] getHighDisplayBrightnessThreshold() {
+ return mHighDisplayBrightnessThresholds;
+ }
+
+ @VisibleForTesting
+ int[] getHighAmbientBrightnessThreshold() {
+ return mHighAmbientBrightnessThresholds;
+ }
+
+ private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
mLowDisplayBrightnessThresholds = loadBrightnessThresholds(
() -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(),
() -> displayDeviceConfig.getLowDisplayBrightnessThresholds(),
R.array.config_brightnessThresholdsOfPeakRefreshRate,
- displayDeviceConfig);
+ displayDeviceConfig, attemptLoadingFromDeviceConfig);
mLowAmbientBrightnessThresholds = loadBrightnessThresholds(
() -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(),
() -> displayDeviceConfig.getLowAmbientBrightnessThresholds(),
R.array.config_ambientThresholdsOfPeakRefreshRate,
- displayDeviceConfig);
+ displayDeviceConfig, attemptLoadingFromDeviceConfig);
if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) {
throw new RuntimeException("display low brightness threshold array and ambient "
+ "brightness threshold array have different length: "
@@ -1579,17 +1624,18 @@
}
}
- private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig) {
+ private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig,
+ boolean attemptLoadingFromDeviceConfig) {
mHighDisplayBrightnessThresholds = loadBrightnessThresholds(
() -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(),
() -> displayDeviceConfig.getHighDisplayBrightnessThresholds(),
R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate,
- displayDeviceConfig);
+ displayDeviceConfig, attemptLoadingFromDeviceConfig);
mHighAmbientBrightnessThresholds = loadBrightnessThresholds(
() -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(),
() -> displayDeviceConfig.getHighAmbientBrightnessThresholds(),
R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate,
- displayDeviceConfig);
+ displayDeviceConfig, attemptLoadingFromDeviceConfig);
if (mHighDisplayBrightnessThresholds.length
!= mHighAmbientBrightnessThresholds.length) {
throw new RuntimeException("display high brightness threshold array and ambient "
@@ -1605,13 +1651,16 @@
Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable,
Callable<int[]> loadFromDisplayDeviceConfigCallable,
int brightnessThresholdOfFixedRefreshRateKey,
- DisplayDeviceConfig displayDeviceConfig) {
+ DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) {
int[] brightnessThresholds = null;
- try {
- brightnessThresholds =
+
+ if (attemptLoadingFromDeviceConfig) {
+ try {
+ brightnessThresholds =
loadFromDeviceConfigDisplaySettingsCallable.call();
- } catch (Exception exception) {
- // Do nothing
+ } catch (Exception exception) {
+ // Do nothing
+ }
}
if (brightnessThresholds == null) {
try {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 69c890d..c426e69 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1677,7 +1677,7 @@
mTempBrightnessEvent.rbcStrength = mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1;
mTempBrightnessEvent.powerFactor = mPowerRequest.screenLowPowerBrightnessFactor;
-
+ mTempBrightnessEvent.wasShortTermModelActive = hadUserBrightnessPoint;
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1687,12 +1687,6 @@
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.brightness;
mTempBrightnessEvent.initialBrightness = lastBrightness;
- mTempBrightnessEvent.fastAmbientLux =
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getFastAmbientLux();
- mTempBrightnessEvent.slowAmbientLux =
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getSlowAmbientLux();
mTempBrightnessEvent.automaticBrightnessEnabled = mPowerRequest.useAutoBrightness;
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -2579,7 +2573,7 @@
pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" +
mCachedBrightnessInfo.hbmTransitionPoint.value);
pw.println(" mCachedBrightnessInfo.brightnessMaxReason =" +
- mCachedBrightnessInfo.brightnessMaxReason .value);
+ mCachedBrightnessInfo.brightnessMaxReason.value);
}
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2810,18 +2804,22 @@
float appliedPowerFactor = event.isLowPowerModeSet() ? event.powerFactor : -1f;
- FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
- convertToNits(event.initialBrightness),
- convertToNits(event.brightness),
- event.slowAmbientLux,
- event.physicalDisplayId,
- event.isShortTermModelActive(),
- appliedPowerFactor,
- appliedRbcStrength,
- appliedHbmMaxNits,
- appliedThermalCapNits,
- event.automaticBrightnessEnabled,
- FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+ if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
+ && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
+ .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
+ FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+ convertToNits(event.initialBrightness),
+ convertToNits(event.brightness),
+ event.lux,
+ event.physicalDisplayId,
+ event.wasShortTermModelActive,
+ appliedPowerFactor,
+ appliedRbcStrength,
+ appliedHbmMaxNits,
+ appliedThermalCapNits,
+ event.automaticBrightnessEnabled,
+ FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+ }
}
class BrightnessEvent {
@@ -2837,8 +2835,6 @@
public int displayId;
public String physicalDisplayId;
public float lux;
- public float fastAmbientLux;
- public float slowAmbientLux;
public float preThresholdLux;
public long time;
public float brightness;
@@ -2850,6 +2846,7 @@
public float thermalMax;
public float powerFactor;
public int hbmMode;
+ public boolean wasShortTermModelActive;
public int flags;
public int adjustmentFlags;
public boolean automaticBrightnessEnabled;
@@ -2868,8 +2865,6 @@
physicalDisplayId = that.physicalDisplayId;
time = that.time;
lux = that.lux;
- fastAmbientLux = that.fastAmbientLux;
- slowAmbientLux = that.slowAmbientLux;
preThresholdLux = that.preThresholdLux;
brightness = that.brightness;
initialBrightness = that.initialBrightness;
@@ -2881,6 +2876,7 @@
powerFactor = that.powerFactor;
flags = that.flags;
hbmMode = that.hbmMode;
+ wasShortTermModelActive = that.wasShortTermModelActive;
reason.set(that.reason);
adjustmentFlags = that.adjustmentFlags;
automaticBrightnessEnabled = that.automaticBrightnessEnabled;
@@ -2893,13 +2889,12 @@
initialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
recommendedBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
lux = 0f;
- fastAmbientLux = 0f;
- slowAmbientLux = 0f;
preThresholdLux = 0f;
preThresholdBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
hbmMax = PowerManager.BRIGHTNESS_MAX;
rbcStrength = 0;
powerFactor = 1f;
+ wasShortTermModelActive = false;
thermalMax = PowerManager.BRIGHTNESS_MAX;
flags = 0;
hbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
@@ -2934,10 +2929,6 @@
&& Float.floatToRawIntBits(preThresholdBrightness)
== Float.floatToRawIntBits(that.preThresholdBrightness)
&& Float.floatToRawIntBits(lux) == Float.floatToRawIntBits(that.lux)
- && Float.floatToRawIntBits(fastAmbientLux)
- == Float.floatToRawIntBits(that.fastAmbientLux)
- && Float.floatToRawIntBits(slowAmbientLux)
- == Float.floatToRawIntBits(that.slowAmbientLux)
&& Float.floatToRawIntBits(preThresholdLux)
== Float.floatToRawIntBits(that.preThresholdLux)
&& rbcStrength == that.rbcStrength
@@ -2947,6 +2938,7 @@
== Float.floatToRawIntBits(that.thermalMax)
&& Float.floatToRawIntBits(powerFactor)
== Float.floatToRawIntBits(that.powerFactor)
+ && wasShortTermModelActive == that.wasShortTermModelActive
&& flags == that.flags
&& adjustmentFlags == that.adjustmentFlags
&& reason.equals(that.reason)
@@ -2963,14 +2955,13 @@
+ ", rcmdBrt=" + recommendedBrightness
+ ", preBrt=" + preThresholdBrightness
+ ", lux=" + lux
- + ", fastAmbientLux=" + fastAmbientLux
- + ", slowAmbientLux=" + slowAmbientLux
+ ", preLux=" + preThresholdLux
+ ", hbmMax=" + hbmMax
+ ", hbmMode=" + BrightnessInfo.hbmToString(hbmMode)
+ ", rbcStrength=" + rbcStrength
+ ", powerFactor=" + powerFactor
+ ", thrmMax=" + thermalMax
+ + ", wasShortTermModelActive=" + wasShortTermModelActive
+ ", flags=" + flagsToString()
+ ", reason=" + reason.toString(adjustmentFlags)
+ ", autoBrightness=" + automaticBrightnessEnabled;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index f64006c..70c9e23 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -167,12 +167,6 @@
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
@NonNull Handler handler) {
- this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap());
- }
-
- LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
- @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
- @NonNull Handler handler, DeviceStateToLayoutMap deviceStateToLayoutMap) {
mSyncRoot = syncRoot;
mPowerManager = context.getSystemService(PowerManager.class);
mInteractive = mPowerManager.isInteractive();
@@ -187,7 +181,7 @@
mDeviceStatesOnWhichToSleep = toSparseBooleanArray(context.getResources().getIntArray(
com.android.internal.R.array.config_deviceStatesOnWhichToSleep));
mDisplayDeviceRepo.addListener(this);
- mDeviceStateToLayoutMap = deviceStateToLayoutMap;
+ mDeviceStateToLayoutMap = new DeviceStateToLayoutMap();
}
@Override
@@ -375,7 +369,9 @@
// the transition is smooth. Plus, on some devices, only one internal displays can be
// on at a time. We use DISPLAY_PHASE_LAYOUT_TRANSITION to mark a display that needs to be
// temporarily turned off.
- resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION);
+ if (mDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) {
+ resetLayoutLocked(mDeviceState, state, LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION);
+ }
mPendingDeviceState = state;
final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
mInteractive, mBootCompleted);
@@ -895,8 +891,8 @@
newDisplay.swapDisplaysLocked(oldDisplay);
}
- if (displayLayout.isEnabled()) {
- setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_ENABLED);
+ if (!displayLayout.isEnabled()) {
+ setDisplayPhase(newDisplay, LogicalDisplay.DISPLAY_PHASE_DISABLED);
}
}
@@ -916,7 +912,7 @@
final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
display.updateLocked(mDisplayDeviceRepo);
mLogicalDisplays.put(displayId, display);
- setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_DISABLED);
+ setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED);
return display;
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index b9511c4..3a49d86 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -81,6 +81,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* Service api for managing dreams.
@@ -116,10 +117,14 @@
private final DreamUiEventLogger mDreamUiEventLogger;
private final ComponentName mAmbientDisplayComponent;
private final boolean mDismissDreamOnActivityStart;
- private final boolean mDreamsOnlyEnabledForSystemUser;
+ private final boolean mDreamsOnlyEnabledForDockUser;
private final boolean mDreamsEnabledByDefaultConfig;
private final boolean mDreamsActivatedOnChargeByDefault;
private final boolean mDreamsActivatedOnDockByDefault;
+ private final boolean mKeepDreamingWhenUndockedDefault;
+
+ private final CopyOnWriteArrayList<DreamManagerInternal.DreamManagerStateListener>
+ mDreamManagerStateListeners = new CopyOnWriteArrayList<>();
@GuardedBy("mLock")
private DreamRecord mCurrentDream;
@@ -214,8 +219,8 @@
mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
- mDreamsOnlyEnabledForSystemUser =
- mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForSystemUser);
+ mDreamsOnlyEnabledForDockUser =
+ mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForDockUser);
mDismissDreamOnActivityStart = mContext.getResources().getBoolean(
R.bool.config_dismissDreamOnActivityStart);
@@ -226,6 +231,8 @@
mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
mSettingsObserver = new SettingsObserver(mHandler);
+ mKeepDreamingWhenUndockedDefault = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_keepDreamingWhenUndocking);
}
@Override
@@ -292,15 +299,15 @@
pw.println();
pw.println("mCurrentDream=" + mCurrentDream);
pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
- pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+ pw.println("mDreamsOnlyEnabledForDockUser=" + mDreamsOnlyEnabledForDockUser);
pw.println("mDreamsEnabledSetting=" + mDreamsEnabledSetting);
pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
- pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
pw.println("mDreamsActivatedOnDockByDefault=" + mDreamsActivatedOnDockByDefault);
pw.println("mDreamsActivatedOnChargeByDefault=" + mDreamsActivatedOnChargeByDefault);
pw.println("mIsDocked=" + mIsDocked);
pw.println("mIsCharging=" + mIsCharging);
pw.println("mWhenToDream=" + mWhenToDream);
+ pw.println("mKeepDreamingWhenUndockedDefault=" + mKeepDreamingWhenUndockedDefault);
pw.println("getDozeComponent()=" + getDozeComponent());
pw.println();
@@ -329,7 +336,16 @@
}
}
- /** Whether a real dream is occurring. */
+ private void reportKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+ mHandler.post(() -> {
+ for (DreamManagerInternal.DreamManagerStateListener listener
+ : mDreamManagerStateListeners) {
+ listener.onKeepDreamingWhenUndockedChanged(keepDreaming);
+ }
+ });
+ }
+
+ /** Whether a real dream is occurring. */
private boolean isDreamingInternal() {
synchronized (mLock) {
return mCurrentDream != null && !mCurrentDream.isPreview
@@ -572,6 +588,8 @@
}
mSystemDreamComponent = componentName;
+ reportKeepDreamingWhenUndockedChanged(
+ mKeepDreamingWhenUndockedDefault && mSystemDreamComponent == null);
// Switch dream if currently dreaming and not dozing.
if (isDreamingInternal() && !isDozingInternal()) {
@@ -602,7 +620,8 @@
}
private boolean dreamsEnabledForUser(int userId) {
- return !mDreamsOnlyEnabledForSystemUser || (userId == UserHandle.USER_SYSTEM);
+ // TODO(b/257333623): Support non-system Dock Users in HSUM.
+ return !mDreamsOnlyEnabledForDockUser || (userId == UserHandle.USER_SYSTEM);
}
private ServiceInfo getServiceInfo(ComponentName name) {
@@ -1012,6 +1031,22 @@
public void requestDream() {
requestDreamInternal();
}
+
+ @Override
+ public boolean keepDreamingWhenUndockedDefault() {
+ // This value does not change, so a lock should not be needed.
+ return mKeepDreamingWhenUndockedDefault;
+ }
+
+ @Override
+ public void registerDreamManagerStateListener(DreamManagerStateListener listener) {
+ mDreamManagerStateListeners.add(listener);
+ }
+
+ @Override
+ public void unregisterDreamManagerStateListener(DreamManagerStateListener listener) {
+ mDreamManagerStateListeners.remove(listener);
+ }
}
private static final class DreamRecord {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 91d5698..89e0d05 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -18,6 +18,8 @@
import static android.view.KeyEvent.KEYCODE_UNKNOWN;
+import android.Manifest;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
@@ -2678,6 +2680,13 @@
mNative.cancelCurrentTouch();
}
+ @EnforcePermission(Manifest.permission.MONITOR_INPUT)
+ @Override
+ public void pilferPointers(IBinder inputChannelToken) {
+ Objects.requireNonNull(inputChannelToken);
+ mNative.pilferPointers(inputChannelToken);
+ }
+
@Override
public void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener) {
Objects.requireNonNull(listener);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index e27cbea..350aa6b 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -58,6 +58,8 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -87,6 +89,7 @@
private static final int PACKAGE_IMPORTANCE_FOR_DISCOVERY = IMPORTANCE_FOREGROUND_SERVICE;
private final Context mContext;
+ private final UserManagerInternal mUserManagerInternal;
private final Object mLock = new Object();
final AtomicInteger mNextRouterOrManagerId = new AtomicInteger(1);
final ActivityManager mActivityManager;
@@ -99,7 +102,7 @@
@GuardedBy("mLock")
private final ArrayMap<IBinder, ManagerRecord> mAllManagerRecords = new ArrayMap<>();
@GuardedBy("mLock")
- private int mCurrentUserId = -1;
+ private int mCurrentActiveUserId = -1;
private final ActivityManager.OnUidImportanceListener mOnUidImportanceListener =
(uid, importance) -> {
@@ -125,12 +128,13 @@
}
};
- MediaRouter2ServiceImpl(Context context) {
+ /* package */ MediaRouter2ServiceImpl(Context context) {
mContext = context;
mActivityManager = mContext.getSystemService(ActivityManager.class);
mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener,
PACKAGE_IMPORTANCE_FOR_DISCOVERY);
mPowerManager = mContext.getSystemService(PowerManager.class);
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
IntentFilter screenOnOffIntentFilter = new IntentFilter();
screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
@@ -601,25 +605,23 @@
}
}
- //TODO(b/136703681): Review this is handling multi-user properly.
- void switchUser() {
+ /* package */ void updateRunningUserAndProfiles(int newActiveUserId) {
synchronized (mLock) {
- int userId = ActivityManager.getCurrentUser();
- if (mCurrentUserId != userId) {
- final int oldUserId = mCurrentUserId;
- mCurrentUserId = userId; // do this first
-
- UserRecord oldUser = mUserRecords.get(oldUserId);
- if (oldUser != null) {
- oldUser.mHandler.sendMessage(
- obtainMessage(UserHandler::stop, oldUser.mHandler));
- disposeUserIfNeededLocked(oldUser); // since no longer current user
- }
-
- UserRecord newUser = mUserRecords.get(userId);
- if (newUser != null) {
- newUser.mHandler.sendMessage(
- obtainMessage(UserHandler::start, newUser.mHandler));
+ if (mCurrentActiveUserId != newActiveUserId) {
+ mCurrentActiveUserId = newActiveUserId;
+ for (int i = 0; i < mUserRecords.size(); i++) {
+ int userId = mUserRecords.keyAt(i);
+ UserRecord userRecord = mUserRecords.valueAt(i);
+ if (isUserActiveLocked(userId)) {
+ // userId corresponds to the active user, or one of its profiles. We
+ // ensure the associated structures are initialized.
+ userRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::start, userRecord.mHandler));
+ } else {
+ userRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::stop, userRecord.mHandler));
+ disposeUserIfNeededLocked(userRecord);
+ }
}
}
}
@@ -637,11 +639,21 @@
}
}
+ /**
+ * Returns {@code true} if the given {@code userId} corresponds to the active user or a profile
+ * of the active user, returns {@code false} otherwise.
+ */
+ @GuardedBy("mLock")
+ private boolean isUserActiveLocked(int userId) {
+ return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId;
+ }
+
////////////////////////////////////////////////////////////////
//// ***Locked methods related to MediaRouter2
//// - Should have @NonNull/@Nullable on all arguments
////////////////////////////////////////////////////////////////
+ @GuardedBy("mLock")
private void registerRouter2Locked(@NonNull IMediaRouter2 router, int uid, int pid,
@NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission,
boolean hasModifyAudioRoutingPermission) {
@@ -669,6 +681,7 @@
userRecord.mHandler, routerRecord));
}
+ @GuardedBy("mLock")
private void unregisterRouter2Locked(@NonNull IMediaRouter2 router, boolean died) {
RouterRecord routerRecord = mAllRouterRecords.remove(router.asBinder());
if (routerRecord == null) {
@@ -891,6 +904,7 @@
return sessionInfos;
}
+ @GuardedBy("mLock")
private void registerManagerLocked(@NonNull IMediaRouter2Manager manager,
int uid, int pid, @NonNull String packageName, int userId) {
final IBinder binder = manager.asBinder();
@@ -1125,13 +1139,14 @@
//// - Should have @NonNull/@Nullable on all arguments
////////////////////////////////////////////////////////////
+ @GuardedBy("mLock")
private UserRecord getOrCreateUserRecordLocked(int userId) {
UserRecord userRecord = mUserRecords.get(userId);
if (userRecord == null) {
userRecord = new UserRecord(userId);
mUserRecords.put(userId, userRecord);
userRecord.init();
- if (userId == mCurrentUserId) {
+ if (isUserActiveLocked(userId)) {
userRecord.mHandler.sendMessage(
obtainMessage(UserHandler::start, userRecord.mHandler));
}
@@ -1139,12 +1154,13 @@
return userRecord;
}
+ @GuardedBy("mLock")
private void disposeUserIfNeededLocked(@NonNull UserRecord userRecord) {
// If there are no records left and the user is no longer current then go ahead
// and purge the user record and all of its associated state. If the user is current
// then leave it alone since we might be connected to a route or want to query
// the same route information again soon.
- if (userRecord.mUserId != mCurrentUserId
+ if (!isUserActiveLocked(userRecord.mUserId)
&& userRecord.mRouterRecords.isEmpty()
&& userRecord.mManagerRecords.isEmpty()) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index e61f553..440178a 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -17,7 +17,9 @@
package com.android.server.media;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager;
+import android.app.UserSwitchObserver;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
@@ -61,7 +63,9 @@
import android.util.TimeUtils;
import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
import com.android.server.Watchdog;
+import com.android.server.pm.UserManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -102,9 +106,10 @@
// State guarded by mLock.
private final Object mLock = new Object();
+ private final UserManagerInternal mUserManagerInternal;
private final SparseArray<UserRecord> mUserRecords = new SparseArray<>();
private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<>();
- private int mCurrentUserId = -1;
+ private int mCurrentActiveUserId = -1;
private final IAudioService mAudioService;
private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
private final Handler mHandler = new Handler();
@@ -130,6 +135,7 @@
mBluetoothA2dpRouteId =
res.getString(com.android.internal.R.string.bluetooth_a2dp_audio_route_id);
+ mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(context);
@@ -217,18 +223,27 @@
context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
}
- public void systemRunning() {
- IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
- switchUser();
- }
- }
- }, filter);
-
- switchUser();
+ /**
+ * Initializes the MediaRouter service.
+ *
+ * @throws RemoteException If an error occurs while registering the {@link UserSwitchObserver}.
+ */
+ @RequiresPermission(
+ anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.INTERACT_ACROSS_USERS_FULL"
+ })
+ public void systemRunning() throws RemoteException {
+ ActivityManager.getService()
+ .registerUserSwitchObserver(
+ new UserSwitchObserver() {
+ @Override
+ public void onUserSwitchComplete(int newUserId) {
+ updateRunningUserAndProfiles(newUserId);
+ }
+ },
+ TAG);
+ updateRunningUserAndProfiles(ActivityManager.getCurrentUser());
}
@Override
@@ -448,7 +463,7 @@
pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
pw.println();
pw.println("Global state");
- pw.println(" mCurrentUserId=" + mCurrentUserId);
+ pw.println(" mCurrentUserId=" + mCurrentActiveUserId);
synchronized (mLock) {
final int count = mUserRecords.size();
@@ -702,26 +717,31 @@
}
}
- void switchUser() {
+ /**
+ * Starts all {@link UserRecord user records} associated with the active user (whose ID is
+ * {@code newActiveUserId}) or the active user's profiles.
+ *
+ * <p>All other records are stopped, and those without associated client records are removed.
+ */
+ private void updateRunningUserAndProfiles(int newActiveUserId) {
synchronized (mLock) {
- int userId = ActivityManager.getCurrentUser();
- if (mCurrentUserId != userId) {
- final int oldUserId = mCurrentUserId;
- mCurrentUserId = userId; // do this first
-
- UserRecord oldUser = mUserRecords.get(oldUserId);
- if (oldUser != null) {
- oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
- disposeUserIfNeededLocked(oldUser); // since no longer current user
- }
-
- UserRecord newUser = mUserRecords.get(userId);
- if (newUser != null) {
- newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+ if (mCurrentActiveUserId != newActiveUserId) {
+ mCurrentActiveUserId = newActiveUserId;
+ for (int i = 0; i < mUserRecords.size(); i++) {
+ int userId = mUserRecords.keyAt(i);
+ UserRecord userRecord = mUserRecords.valueAt(i);
+ if (isUserActiveLocked(userId)) {
+ // userId corresponds to the active user, or one of its profiles. We
+ // ensure the associated structures are initialized.
+ userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+ } else {
+ userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
+ disposeUserIfNeededLocked(userRecord);
+ }
}
}
}
- mService2.switchUser();
+ mService2.updateRunningUserAndProfiles(newActiveUserId);
}
void clientDied(ClientRecord clientRecord) {
@@ -776,8 +796,10 @@
clientRecord.mGroupId = groupId;
if (groupId != null) {
userRecord.addToGroup(groupId, clientRecord);
- userRecord.mHandler.obtainMessage(UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED, groupId)
- .sendToTarget();
+ userRecord
+ .mHandler
+ .obtainMessage(UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED, groupId)
+ .sendToTarget();
}
}
@@ -863,9 +885,13 @@
clientRecord.mUserRecord.mClientGroupMap.get(clientRecord.mGroupId);
if (group != null) {
group.mSelectedRouteId = routeId;
- clientRecord.mUserRecord.mHandler.obtainMessage(
- UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED, clientRecord.mGroupId)
- .sendToTarget();
+ clientRecord
+ .mUserRecord
+ .mHandler
+ .obtainMessage(
+ UserHandler.MSG_NOTIFY_GROUP_ROUTE_SELECTED,
+ clientRecord.mGroupId)
+ .sendToTarget();
}
}
}
@@ -897,7 +923,7 @@
if (DEBUG) {
Slog.d(TAG, userRecord + ": Initialized");
}
- if (userRecord.mUserId == mCurrentUserId) {
+ if (isUserActiveLocked(userRecord.mUserId)) {
userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
}
}
@@ -907,8 +933,7 @@
// and purge the user record and all of its associated state. If the user is current
// then leave it alone since we might be connected to a route or want to query
// the same route information again soon.
- if (userRecord.mUserId != mCurrentUserId
- && userRecord.mClientRecords.isEmpty()) {
+ if (!isUserActiveLocked(userRecord.mUserId) && userRecord.mClientRecords.isEmpty()) {
if (DEBUG) {
Slog.d(TAG, userRecord + ": Disposed");
}
@@ -917,6 +942,14 @@
}
}
+ /**
+ * Returns {@code true} if the given {@code userId} corresponds to the active user or a profile
+ * of the active user, returns {@code false} otherwise.
+ */
+ private boolean isUserActiveLocked(int userId) {
+ return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId;
+ }
+
private void initializeClientLocked(ClientRecord clientRecord) {
if (DEBUG) {
Slog.d(TAG, clientRecord + ": Registered");
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c2df904d..4d44c886 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4966,7 +4966,16 @@
}
enforcePolicyAccess(Binder.getCallingUid(), "addAutomaticZenRule");
- return mZenModeHelper.addAutomaticZenRule(pkg, automaticZenRule,
+ // If the calling app is the system (from any user), take the package name from the
+ // rule's owner rather than from the caller's package.
+ String rulePkg = pkg;
+ if (isCallingAppIdSystem()) {
+ if (automaticZenRule.getOwner() != null) {
+ rulePkg = automaticZenRule.getOwner().getPackageName();
+ }
+ }
+
+ return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule,
"addAutomaticZenRule");
}
@@ -9721,6 +9730,12 @@
return uid == Process.SYSTEM_UID;
}
+ protected boolean isCallingAppIdSystem() {
+ final int uid = Binder.getCallingUid();
+ final int appid = UserHandle.getAppId(uid);
+ return appid == Process.SYSTEM_UID;
+ }
+
protected boolean isUidSystemOrPhone(int uid) {
final int appid = UserHandle.getAppId(uid);
return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index d426679..4c23ab8 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -326,7 +326,7 @@
public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
String reason) {
- if (!isSystemRule(automaticZenRule)) {
+ if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
if (component == null) {
component = getActivityInfo(automaticZenRule.getConfigurationActivity());
@@ -582,11 +582,6 @@
}
}
- private boolean isSystemRule(AutomaticZenRule rule) {
- return rule.getOwner() != null
- && ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
- }
-
private ServiceInfo getServiceInfo(ComponentName owner) {
Intent queryIntent = new Intent();
queryIntent.setComponent(owner);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 06458d1..7c2e3ea 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -367,12 +367,12 @@
private boolean mStageDirInUse = false;
/**
- * True if the installation is already in progress. This is used to prevent the caller
- * from {@link #commit(IntentSender, boolean) committing} the session again while the
- * installation is still in progress.
+ * True if the verification is already in progress. This is used to prevent running
+ * verification again while one is already in progress which will break internal states.
+ *
+ * Worker thread only.
*/
- @GuardedBy("mLock")
- private boolean mInstallationInProgress = false;
+ private boolean mVerificationInProgress = false;
/** Permissions have been accepted by the user (see {@link #setPermissionsResult}) */
@GuardedBy("mLock")
@@ -1669,14 +1669,6 @@
}
}
- synchronized (mLock) {
- if (mInstallationInProgress) {
- throw new IllegalStateException("Installation is already in progress. Don't "
- + "commit session=" + sessionId + " again.");
- }
- mInstallationInProgress = true;
- }
-
dispatchSessionSealed();
}
@@ -2152,6 +2144,12 @@
return;
}
+ if (mVerificationInProgress) {
+ Slog.w(TAG, "Verification is already in progress for session " + sessionId);
+ return;
+ }
+ mVerificationInProgress = true;
+
if (params.isStaged) {
mStagedSession.verifySession();
} else {
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 0ae92b4..32ee21c 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -616,6 +616,10 @@
grantPermissionsToSystemPackage(pm, getDefaultCaptivePortalLoginPackage(), userId,
NOTIFICATION_PERMISSIONS);
+ // Dock Manager
+ grantPermissionsToSystemPackage(pm, getDefaultDockManagerPackage(), userId,
+ NOTIFICATION_PERMISSIONS);
+
// Camera
grantPermissionsToSystemPackage(pm,
getDefaultSystemHandlerActivityPackage(pm, MediaStore.ACTION_IMAGE_CAPTURE, userId),
@@ -932,6 +936,10 @@
return mContext.getString(R.string.config_defaultCaptivePortalLoginPackageName);
}
+ private String getDefaultDockManagerPackage() {
+ return mContext.getString(R.string.config_defaultDockManagerPackageName);
+ }
+
@SafeVarargs
private final void grantPermissionToEachSystemPackage(PackageManagerWrapper pm,
ArrayList<String> packages, int userId, Set<String>... permissions) {
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 9b7d19a..2a65ea2 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -96,6 +96,7 @@
private static final String CONFIG_FILE_NAME = "device_state_configuration.xml";
private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE";
+ private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
/** Interface that allows reading the device state configuration. */
interface ReadableConfig {
@@ -149,6 +150,8 @@
case FLAG_APP_INACCESSIBLE:
flags |= DeviceState.FLAG_APP_INACCESSIBLE;
break;
+ case FLAG_EMULATED_ONLY:
+ flags |= DeviceState.FLAG_EMULATED_ONLY;
default:
Slog.w(TAG, "Parsed unknown flag with name: "
+ configFlagString);
@@ -225,7 +228,13 @@
}
final Conditions conditions = stateConditions.get(i);
if (conditions == null) {
- mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
+ // If this state has the FLAG_EMULATED_ONLY flag on it, it should never be triggered
+ // by a physical hardware change, and should always return false for it's conditions
+ if (deviceStates.get(i).hasFlag(DeviceState.FLAG_EMULATED_ONLY)) {
+ mStateConditions.put(state, FALSE_BOOLEAN_SUPPLIER);
+ } else {
+ mStateConditions.put(state, TRUE_BOOLEAN_SUPPLIER);
+ }
continue;
}
@@ -358,7 +367,7 @@
return;
}
- int newState = mOrderedStates[0].getIdentifier();
+ int newState = INVALID_DEVICE_STATE;
for (int i = 0; i < mOrderedStates.length; i++) {
int state = mOrderedStates[i].getIdentifier();
if (DEBUG) {
@@ -386,8 +395,12 @@
break;
}
}
+ if (newState == INVALID_DEVICE_STATE) {
+ Slog.e(TAG, "No declared device states match any of the required conditions.");
+ dumpSensorValues();
+ }
- if (newState != mLastReportedState) {
+ if (newState != INVALID_DEVICE_STATE && newState != mLastReportedState) {
mLastReportedState = newState;
stateToReport = newState;
}
@@ -583,6 +596,19 @@
return null;
}
+ @GuardedBy("mLock")
+ private void dumpSensorValues() {
+ Slog.i(TAG, "Sensor values:");
+ for (Sensor sensor : mLatestSensorEvent.keySet()) {
+ SensorEvent sensorEvent = mLatestSensorEvent.get(sensor);
+ if (sensorEvent != null) {
+ Slog.i(TAG, sensor.getName() + ": " + Arrays.toString(sensorEvent.values));
+ } else {
+ Slog.i(TAG, sensor.getName() + ": null");
+ }
+ }
+ }
+
/**
* Tries to parse the provided file into a {@link DeviceStateConfig} object. Returns
* {@code null} if the file could not be successfully parsed.
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 725fb3f..b1c986e 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -419,6 +419,9 @@
// The current battery level percentage.
private int mBatteryLevel;
+ // The amount of battery drained while the device has been in a dream state.
+ private int mDreamsBatteryLevelDrain;
+
// True if updatePowerStateLocked() is already in progress.
// TODO(b/215518989): Remove this once transactions are in place
private boolean mUpdatePowerStateInProgress;
@@ -451,11 +454,6 @@
@GuardedBy("mEnhancedDischargeTimeLock")
private boolean mEnhancedDischargePredictionIsPersonalized;
- // The battery level percentage at the time the dream started.
- // This is used to terminate a dream and go to sleep if the battery is
- // draining faster than it is charging and the user activity timeout has expired.
- private int mBatteryLevelWhenDreamStarted;
-
// The current dock state.
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
@@ -468,9 +466,6 @@
// True if the device should wake up when plugged or unplugged.
private boolean mWakeUpWhenPluggedOrUnpluggedConfig;
- // True if the device should keep dreaming when undocked.
- private boolean mKeepDreamingWhenUndockingConfig;
-
// True if the device should wake up when plugged or unplugged in theater mode.
private boolean mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig;
@@ -677,6 +672,19 @@
// but the DreamService has not yet been told to start (it's an async process).
private boolean mDozeStartInProgress;
+ // Whether to keep dreaming when the device is undocked.
+ private boolean mKeepDreamingWhenUndocked;
+
+ private final class DreamManagerStateListener implements
+ DreamManagerInternal.DreamManagerStateListener {
+ @Override
+ public void onKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+ synchronized (mLock) {
+ mKeepDreamingWhenUndocked = keepDreaming;
+ }
+ }
+ }
+
private final class PowerGroupWakefulnessChangeListener implements
PowerGroup.PowerGroupListener {
@GuardedBy("mLock")
@@ -1033,7 +1041,7 @@
super(context);
mContext = context;
- mBinderService = new BinderService();
+ mBinderService = new BinderService(mContext);
mLocalService = new LocalService();
mNativeWrapper = injector.createNativeWrapper();
mSystemProperties = injector.createSystemPropertiesWrapper();
@@ -1265,6 +1273,12 @@
new DisplayGroupPowerChangeListener();
mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
+ // These DreamManager methods do not acquire locks, so they should be safe to call.
+ mKeepDreamingWhenUndocked = mDreamManager.keepDreamingWhenUndockedDefault();
+ if (mKeepDreamingWhenUndocked) {
+ mDreamManager.registerDreamManagerStateListener(new DreamManagerStateListener());
+ }
+
mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
mInjector.createSuspendBlocker(
this, "PowerManagerService.WirelessChargerDetector"),
@@ -1382,8 +1396,6 @@
com.android.internal.R.bool.config_powerDecoupleInteractiveModeFromDisplay);
mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean(
com.android.internal.R.bool.config_unplugTurnsOnScreen);
- mKeepDreamingWhenUndockingConfig = resources.getBoolean(
- com.android.internal.R.bool.config_keepDreamingWhenUndocking);
mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig = resources.getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromUnplug);
mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean(
@@ -2185,6 +2197,15 @@
if (sQuiescent) {
mDirty |= DIRTY_QUIESCENT;
}
+ PowerGroup defaultGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP);
+ if (defaultGroup.getWakefulnessLocked() == WAKEFULNESS_DOZING) {
+ // Workaround for b/187231320 where the AOD can get stuck in a "half on /
+ // half off" state when a non-default-group VirtualDisplay causes the global
+ // wakefulness to change to awake, even though the default display is
+ // dozing. We set sandman summoned to restart dreaming to get it unstuck.
+ // TODO(b/255688811) - fix this so that AOD never gets interrupted at all.
+ defaultGroup.setSandmanSummonedLocked(true);
+ }
break;
case WAKEFULNESS_ASLEEP:
@@ -2448,15 +2469,25 @@
final int oldPlugType = mPlugType;
mIsPowered = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
mPlugType = mBatteryManagerInternal.getPlugType();
+ final int oldBatteryLevel = mBatteryLevel;
mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
mBatteryLevelLow = mBatteryManagerInternal.getBatteryLevelLow();
+ final boolean isOverheat = mBatteryManagerInternal.getBatteryHealth()
+ == BatteryManager.BATTERY_HEALTH_OVERHEAT;
if (DEBUG_SPEW) {
Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered
+ ", mIsPowered=" + mIsPowered
+ ", oldPlugType=" + oldPlugType
+ ", mPlugType=" + mPlugType
- + ", mBatteryLevel=" + mBatteryLevel);
+ + ", oldBatteryLevel=" + oldBatteryLevel
+ + ", mBatteryLevel=" + mBatteryLevel
+ + ", isOverheat=" + isOverheat);
+ }
+
+ if (!isOverheat && oldBatteryLevel > 0
+ && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING) {
+ mDreamsBatteryLevelDrain += (oldBatteryLevel - mBatteryLevel);
}
if (wasPowered != mIsPowered || oldPlugType != mPlugType) {
@@ -2508,7 +2539,7 @@
}
// Don't wake when undocking while dreaming if configured not to.
- if (mKeepDreamingWhenUndockingConfig
+ if (mKeepDreamingWhenUndocked
&& getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING
&& wasPowered && !mIsPowered
&& oldPlugType == BatteryManager.BATTERY_PLUGGED_DOCK) {
@@ -3278,7 +3309,7 @@
// Remember the initial battery level when the dream started.
if (startDreaming && isDreaming) {
- mBatteryLevelWhenDreamStarted = mBatteryLevel;
+ mDreamsBatteryLevelDrain = 0;
if (wakefulness == WAKEFULNESS_DOZING) {
Slog.i(TAG, "Dozing...");
} else {
@@ -3299,16 +3330,15 @@
if (wakefulness == WAKEFULNESS_DREAMING) {
if (isDreaming && canDreamLocked(powerGroup)) {
if (mDreamsBatteryLevelDrainCutoffConfig >= 0
- && mBatteryLevel < mBatteryLevelWhenDreamStarted
- - mDreamsBatteryLevelDrainCutoffConfig
+ && mDreamsBatteryLevelDrain > mDreamsBatteryLevelDrainCutoffConfig
&& !isBeingKeptAwakeLocked(powerGroup)) {
// If the user activity timeout expired and the battery appears
// to be draining faster than it is charging then stop dreaming
// and go to sleep.
Slog.i(TAG, "Stopping dream because the battery appears to "
+ "be draining faster than it is charging. "
- + "Battery level when dream started: "
- + mBatteryLevelWhenDreamStarted + "%. "
+ + "Battery level drained while dreaming: "
+ + mDreamsBatteryLevelDrain + "%. "
+ "Battery level now: " + mBatteryLevel + "%.");
} else {
return; // continue dreaming
@@ -3539,6 +3569,11 @@
mScreenBrightnessBoostInProgress);
}
+ @VisibleForTesting
+ int getDreamsBatteryLevelDrain() {
+ return mDreamsBatteryLevelDrain;
+ }
+
private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks =
new DisplayManagerInternal.DisplayPowerCallbacks() {
@@ -4383,7 +4418,7 @@
pw.println(" mIsPowered=" + mIsPowered);
pw.println(" mPlugType=" + mPlugType);
pw.println(" mBatteryLevel=" + mBatteryLevel);
- pw.println(" mBatteryLevelWhenDreamStarted=" + mBatteryLevelWhenDreamStarted);
+ pw.println(" mDreamsBatteryLevelDrain=" + mDreamsBatteryLevelDrain);
pw.println(" mDockState=" + mDockState);
pw.println(" mStayOn=" + mStayOn);
pw.println(" mProximityPositive=" + mProximityPositive);
@@ -4457,8 +4492,7 @@
+ mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig);
pw.println(" mTheaterModeEnabled="
+ mTheaterModeEnabled);
- pw.println(" mKeepDreamingWhenUndockingConfig="
- + mKeepDreamingWhenUndockingConfig);
+ pw.println(" mKeepDreamingWhenUndocked=" + mKeepDreamingWhenUndocked);
pw.println(" mSuspendWhenScreenOffDueToProximityConfig="
+ mSuspendWhenScreenOffDueToProximityConfig);
pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig);
@@ -4625,8 +4659,8 @@
proto.write(PowerManagerServiceDumpProto.PLUG_TYPE, mPlugType);
proto.write(PowerManagerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel);
proto.write(
- PowerManagerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED,
- mBatteryLevelWhenDreamStarted);
+ PowerManagerServiceDumpProto.BATTERY_LEVEL_DRAINED_WHILE_DREAMING,
+ mDreamsBatteryLevelDrain);
proto.write(PowerManagerServiceDumpProto.DOCK_STATE, mDockState);
proto.write(PowerManagerServiceDumpProto.IS_STAY_ON, mStayOn);
proto.write(PowerManagerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
@@ -5465,12 +5499,17 @@
@VisibleForTesting
final class BinderService extends IPowerManager.Stub {
+ private final PowerManagerShellCommand mShellCommand;
+
+ BinderService(Context context) {
+ mShellCommand = new PowerManagerShellCommand(context, this);
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- (new PowerManagerShellCommand(this)).exec(
- this, in, out, err, args, callback, resultReceiver);
+ mShellCommand.exec(this, in, out, err, args, callback, resultReceiver);
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index a9b33ed..9439b76 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -16,10 +16,15 @@
package com.android.server.power;
+import android.content.Context;
import android.content.Intent;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ShellCommand;
+import android.util.SparseArray;
+import android.view.Display;
import java.io.PrintWriter;
import java.util.List;
@@ -27,9 +32,13 @@
class PowerManagerShellCommand extends ShellCommand {
private static final int LOW_POWER_MODE_ON = 1;
- final PowerManagerService.BinderService mService;
+ private final Context mContext;
+ private final PowerManagerService.BinderService mService;
- PowerManagerShellCommand(PowerManagerService.BinderService service) {
+ private SparseArray<WakeLock> mProxWakelocks = new SparseArray<>();
+
+ PowerManagerShellCommand(Context context, PowerManagerService.BinderService service) {
+ mContext = context;
mService = service;
}
@@ -52,6 +61,8 @@
return runSuppressAmbientDisplay();
case "list-ambient-display-suppression-tokens":
return runListAmbientDisplaySuppressionTokens();
+ case "set-prox":
+ return runSetProx();
default:
return handleDefaultCommands(cmd);
}
@@ -117,6 +128,56 @@
return 0;
}
+
+ /** TODO: Consider updating this code to support all wakelock types. */
+ private int runSetProx() throws RemoteException {
+ PrintWriter pw = getOutPrintWriter();
+ final boolean acquire;
+ switch (getNextArgRequired().toLowerCase()) {
+ case "list":
+ pw.println("Wakelocks:");
+ pw.println(mProxWakelocks);
+ return 0;
+ case "acquire":
+ acquire = true;
+ break;
+ case "release":
+ acquire = false;
+ break;
+ default:
+ pw.println("Error: Allowed options are 'list' 'enable' and 'disable'.");
+ return -1;
+ }
+
+ int displayId = Display.INVALID_DISPLAY;
+ String displayOption = getNextArg();
+ if ("-d".equals(displayOption)) {
+ String idStr = getNextArg();
+ displayId = Integer.parseInt(idStr);
+ if (displayId < 0) {
+ pw.println("Error: Specified displayId (" + idStr + ") must a non-negative int.");
+ return -1;
+ }
+ }
+
+ int wakelockIndex = displayId + 1; // SparseArray doesn't support negative indexes
+ WakeLock wakelock = mProxWakelocks.get(wakelockIndex);
+ if (wakelock == null) {
+ PowerManager pm = mContext.getSystemService(PowerManager.class);
+ wakelock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
+ "PowerManagerShellCommand[" + displayId + "]", displayId);
+ mProxWakelocks.put(wakelockIndex, wakelock);
+ }
+
+ if (acquire) {
+ wakelock.acquire();
+ } else {
+ wakelock.release();
+ }
+ pw.println(wakelock);
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -138,6 +199,11 @@
pw.println(" ambient display");
pw.println(" list-ambient-display-suppression-tokens");
pw.println(" prints the tokens used to suppress ambient display");
+ pw.println(" set-prox [list|acquire|release] (-d <display_id>)");
+ pw.println(" Acquires the proximity sensor wakelock. Wakelock is associated with");
+ pw.println(" a specific display if specified. 'list' lists wakelocks previously");
+ pw.println(" created by set-prox including their held status.");
+
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 690dd10..c758f48 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -172,4 +172,11 @@
* @see com.android.internal.statusbar.IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
*/
void setUdfpsHbmListener(IUdfpsHbmListener listener);
+
+ /**
+ * Shows the rear display educational dialog
+ *
+ * @see com.android.internal.statusbar.IStatusBar#showRearDisplayDialog
+ */
+ void showRearDisplayDialog(int currentBaseState);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 0367392..194dfb2 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.statusbar;
+import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
@@ -31,6 +32,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityThread;
@@ -685,6 +687,15 @@
} catch (RemoteException ex) { }
}
}
+
+ @Override
+ public void showRearDisplayDialog(int currentBaseState) {
+ if (mBar != null) {
+ try {
+ mBar.showRearDisplayDialog(currentBaseState);
+ } catch (RemoteException ex) { }
+ }
+ }
};
private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
@@ -1288,6 +1299,11 @@
"StatusBarManagerService");
}
+ @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+ private void enforceControlDeviceStatePermission() {
+ mContext.enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE, "StatusBarManagerService");
+ }
+
private boolean doesCallerHoldInteractAcrossUserPermission() {
return mContext.checkCallingPermission(INTERACT_ACROSS_USERS_FULL) == PERMISSION_GRANTED
|| mContext.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
@@ -2171,6 +2187,19 @@
}
}
+ @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+ @Override
+ public void showRearDisplayDialog(int currentState) {
+ enforceControlDeviceStatePermission();
+ if (mBar != null) {
+ try {
+ mBar.showRearDisplayDialog(currentState);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "showRearDisplayDialog", e);
+ }
+ }
+ }
+
/** @hide */
public void passThroughShellCommand(String[] args, FileDescriptor fd) {
enforceStatusBarOrShell();
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 4b8c7c1..36293d5 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -107,6 +107,7 @@
// Trust state
private boolean mTrusted;
private boolean mWaitingForTrustableDowngrade = false;
+ private boolean mWithinSecurityLockdownWindow = false;
private boolean mTrustable;
private CharSequence mMessage;
private boolean mDisplayTrustGrantedMessage;
@@ -160,6 +161,7 @@
mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
mWaitingForTrustableDowngrade = true;
+ setSecurityWindowTimer();
} else {
mWaitingForTrustableDowngrade = false;
}
@@ -452,6 +454,9 @@
if (mBound) {
scheduleRestart();
}
+ if (mWithinSecurityLockdownWindow) {
+ mTrustManagerService.lockUser(mUserId);
+ }
// mTrustDisabledByDpm maintains state
}
};
@@ -673,6 +678,22 @@
}
}
+ private void setSecurityWindowTimer() {
+ mWithinSecurityLockdownWindow = true;
+ long expiration = SystemClock.elapsedRealtime() + (15 * 1000); // timer for 15 seconds
+ mAlarmManager.setExact(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ expiration,
+ TAG,
+ new AlarmManager.OnAlarmListener() {
+ @Override
+ public void onAlarm() {
+ mWithinSecurityLockdownWindow = false;
+ }
+ },
+ Handler.getMain());
+ }
+
public boolean isManagingTrust() {
return mManagingTrust && !mTrustDisabledByDpm;
}
@@ -691,7 +712,6 @@
public void destroy() {
mHandler.removeMessages(MSG_RESTART_TIMEOUT);
-
if (!mBound) {
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index eca2e74..da6e7e8 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -580,17 +580,18 @@
}
/**
- * Returns the windowing mode of the task that hosts the activity, or {@code -1} if task is not
- * found.
+ * Returns the {@link Configuration} of the task which hosts the Activity, or {@code null} if
+ * the task {@link Configuration} cannot be obtained.
*/
@Override
- public int getTaskWindowingMode(IBinder activityToken) {
+ @Nullable
+ public Configuration getTaskConfiguration(IBinder activityToken) {
synchronized (mGlobalLock) {
final ActivityRecord ar = ActivityRecord.isInAnyTask(activityToken);
if (ar == null) {
- return -1;
+ return null;
}
- return ar.getTask().getWindowingMode();
+ return ar.getTask().getConfiguration();
}
}
@@ -1347,8 +1348,38 @@
}
}
+ /**
+ * Return {@code true} when the given Activity is a relative Task root. That is, the rest of
+ * the Activities in the Task should be finished when it finishes. Otherwise, return {@code
+ * false}.
+ */
+ private boolean isRelativeTaskRootActivity(ActivityRecord r, ActivityRecord taskRoot) {
+ // Not a relative root if the given Activity is not the root Activity of its TaskFragment.
+ final TaskFragment taskFragment = r.getTaskFragment();
+ if (r != taskFragment.getActivity(ar -> !ar.finishing || ar == r,
+ false /* traverseTopToBottom */)) {
+ return false;
+ }
+
+ // The given Activity is the relative Task root if its TaskFragment is a companion
+ // TaskFragment to the taskRoot (i.e. the taskRoot TF will be finished together).
+ return taskRoot.getTaskFragment().getCompanionTaskFragment() == taskFragment;
+ }
+
+ private boolean isTopActivityInTaskFragment(ActivityRecord activity) {
+ return activity.getTaskFragment().topRunningActivity() == activity;
+ }
+
+ private void requestCallbackFinish(IRequestFinishCallback callback) {
+ try {
+ callback.requestFinish();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to invoke request finish callback", e);
+ }
+ }
+
@Override
- public void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) {
+ public void onBackPressed(IBinder token, IRequestFinishCallback callback) {
final long origId = Binder.clearCallingIdentity();
try {
final Intent baseActivityIntent;
@@ -1358,20 +1389,29 @@
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) return;
- if (mService.mWindowOrganizerController.mTaskOrganizerController
+ final Task task = r.getTask();
+ final ActivityRecord root = task.getRootActivity(false /*ignoreRelinquishIdentity*/,
+ true /*setToBottomIfNone*/);
+ final boolean isTaskRoot = r == root;
+ if (isTaskRoot) {
+ if (mService.mWindowOrganizerController.mTaskOrganizerController
.handleInterceptBackPressedOnTaskRoot(r.getRootTask())) {
- // This task is handled by a task organizer that has requested the back pressed
- // callback.
+ // This task is handled by a task organizer that has requested the back
+ // pressed callback.
+ return;
+ }
+ } else if (!isRelativeTaskRootActivity(r, root)) {
+ // Finish the Activity if the activity is not the task root or relative root.
+ requestCallbackFinish(callback);
return;
}
- final Task task = r.getTask();
- isLastRunningActivity = task.topRunningActivity() == r;
+ isLastRunningActivity = isTopActivityInTaskFragment(isTaskRoot ? root : r);
- final boolean isBaseActivity = r.mActivityComponent.equals(task.realActivity);
- baseActivityIntent = isBaseActivity ? r.intent : null;
+ final boolean isBaseActivity = root.mActivityComponent.equals(task.realActivity);
+ baseActivityIntent = isBaseActivity ? root.intent : null;
- launchedFromHome = r.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
+ launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
}
// If the activity is one of the main entry points for the application, then we should
@@ -1386,16 +1426,12 @@
if (baseActivityIntent != null && isLastRunningActivity
&& ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent))
|| isLauncherActivity(baseActivityIntent.getComponent()))) {
- moveActivityTaskToBack(token, false /* nonRoot */);
+ moveActivityTaskToBack(token, true /* nonRoot */);
return;
}
// The default option for handling the back button is to finish the Activity.
- try {
- callback.requestFinish();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to invoke request finish callback", e);
- }
+ requestCallbackFinish(callback);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ccab968..cf72833 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1750,6 +1750,7 @@
}
prevDc.mClosingApps.remove(this);
+ prevDc.getDisplayPolicy().removeRelaunchingApp(this);
if (prevDc.mFocusedApp == this) {
prevDc.setFocusedApp(null);
@@ -3969,6 +3970,9 @@
void startRelaunching() {
if (mPendingRelaunchCount == 0) {
mRelaunchStartTime = SystemClock.elapsedRealtime();
+ if (mVisibleRequested) {
+ mDisplayContent.getDisplayPolicy().addRelaunchingApp(this);
+ }
}
clearAllDrawn();
@@ -3982,7 +3986,7 @@
mPendingRelaunchCount--;
if (mPendingRelaunchCount == 0 && !isClientVisible()) {
// Don't count if the client won't report drawn.
- mRelaunchStartTime = 0;
+ finishOrAbortReplacingWindow();
}
} else {
// Update keyguard flags upon finishing relaunch.
@@ -4003,7 +4007,12 @@
return;
}
mPendingRelaunchCount = 0;
+ finishOrAbortReplacingWindow();
+ }
+
+ void finishOrAbortReplacingWindow() {
mRelaunchStartTime = 0;
+ mDisplayContent.getDisplayPolicy().removeRelaunchingApp(this);
}
/**
@@ -5112,6 +5121,9 @@
mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
}
logAppCompatState();
+ if (!visible) {
+ finishOrAbortReplacingWindow();
+ }
}
/**
@@ -5249,42 +5261,7 @@
}
// If we are preparing an app transition, then delay changing
// the visibility of this token until we execute that transition.
- // Note that we ignore display frozen since we want the opening / closing transition type
- // can be updated correctly even display frozen, and it's safe since in applyAnimation will
- // still check DC#okToAnimate again if the transition animation is fine to apply.
- // TODO(new-app-transition): Rewrite this logic using WM Shell.
- final boolean recentsAnimating = isAnimating(PARENTS, ANIMATION_TYPE_RECENTS);
- final boolean isEnteringPipWithoutVisibleChange = mWaitForEnteringPinnedMode
- && mVisible == visible;
- if (okToAnimate(true /* ignoreFrozen */, canTurnScreenOn())
- && (appTransition.isTransitionSet()
- || (recentsAnimating && !isActivityTypeHome()))
- // If the visibility is not changed during enter PIP, we don't want to include it in
- // app transition to affect the animation theme, because the Pip organizer will
- // animate the entering PIP instead.
- && !isEnteringPipWithoutVisibleChange) {
- if (visible) {
- displayContent.mOpeningApps.add(this);
- mEnteringAnimation = true;
- } else if (mVisible) {
- displayContent.mClosingApps.add(this);
- mEnteringAnimation = false;
- }
- if ((appTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
- // We're launchingBehind, add the launching activity to mOpeningApps.
- final WindowState win = getDisplayContent().findFocusedWindow();
- if (win != null) {
- final ActivityRecord focusedActivity = win.mActivityRecord;
- if (focusedActivity != null) {
- ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
- "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps",
- focusedActivity);
-
- // Force animation to be loaded.
- displayContent.mOpeningApps.add(focusedActivity);
- }
- }
- }
+ if (deferCommitVisibilityChange(visible)) {
return;
}
@@ -5292,6 +5269,61 @@
updateReportedVisibilityLocked();
}
+ /**
+ * Returns {@code true} if this activity is either added to opening-apps or closing-apps.
+ * Then its visibility will be committed until the transition is ready.
+ */
+ private boolean deferCommitVisibilityChange(boolean visible) {
+ if (!mDisplayContent.mAppTransition.isTransitionSet()) {
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ // Shell transition doesn't use opening/closing sets.
+ return false;
+ }
+ // Defer committing visibility for non-home app which is animating by recents.
+ if (isActivityTypeHome() || !isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+ return false;
+ }
+ }
+ if (mWaitForEnteringPinnedMode && mVisible == visible) {
+ // If the visibility is not changed during enter PIP, we don't want to include it in
+ // app transition to affect the animation theme, because the Pip organizer will
+ // animate the entering PIP instead.
+ return false;
+ }
+
+ // The animation will be visible soon so do not skip by screen off.
+ final boolean ignoreScreenOn = canTurnScreenOn() || mTaskSupervisor.getKeyguardController()
+ .isKeyguardGoingAway(mDisplayContent.mDisplayId);
+ // Ignore display frozen so the opening / closing transition type can be updated correctly
+ // even if the display is frozen. And it's safe since in applyAnimation will still check
+ // DC#okToAnimate again if the transition animation is fine to apply.
+ if (!okToAnimate(true /* ignoreFrozen */, ignoreScreenOn)) {
+ return false;
+ }
+ if (visible) {
+ mDisplayContent.mOpeningApps.add(this);
+ mEnteringAnimation = true;
+ } else if (mVisible) {
+ mDisplayContent.mClosingApps.add(this);
+ mEnteringAnimation = false;
+ }
+ if ((mDisplayContent.mAppTransition.getTransitFlags() & TRANSIT_FLAG_OPEN_BEHIND) != 0) {
+ // Add the launching-behind activity to mOpeningApps.
+ final WindowState win = mDisplayContent.findFocusedWindow();
+ if (win != null) {
+ final ActivityRecord focusedActivity = win.mActivityRecord;
+ if (focusedActivity != null) {
+ ProtoLog.d(WM_DEBUG_APP_TRANSITIONS,
+ "TRANSIT_FLAG_OPEN_BEHIND, adding %s to mOpeningApps",
+ focusedActivity);
+ // Force animation to be loaded.
+ mDisplayContent.mOpeningApps.add(focusedActivity);
+ }
+ }
+ }
+ return true;
+ }
+
@Override
boolean applyAnimation(LayoutParams lp, @TransitionOldType int transit, boolean enter,
boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {
@@ -6862,6 +6894,10 @@
if (r == null || r.getParent() == null) {
return INVALID_TASK_ID;
}
+ return getTaskForActivityLocked(r, onlyRoot);
+ }
+
+ static int getTaskForActivityLocked(ActivityRecord r, boolean onlyRoot) {
final Task task = r.task;
if (onlyRoot && r.compareTo(task.getRootActivity(
false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)) > 0) {
@@ -8884,9 +8920,7 @@
}
if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN)
- && getParent().getConfiguration().orientation == ORIENTATION_PORTRAIT
- && getParent().getWindowConfiguration().getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN) {
+ && isParentFullscreenPortrait()) {
// We are using the parent configuration here as this is the most recent one that gets
// passed to onConfigurationChanged when a relevant change takes place
return info.getMinAspectRatio();
@@ -8909,6 +8943,13 @@
return info.getMinAspectRatio();
}
+ private boolean isParentFullscreenPortrait() {
+ final WindowContainer parent = getParent();
+ return parent != null
+ && parent.getConfiguration().orientation == ORIENTATION_PORTRAIT
+ && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+ }
+
/**
* Returns true if the activity has maximum or minimum aspect ratio.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 9f502ab..e1c3cbf 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -556,47 +556,52 @@
final Task rootTask = mService.mRootWindowContainer.getDefaultTaskDisplayArea()
.getRootTask(WINDOWING_MODE_UNDEFINED, activityType);
if (rootTask == null) return false;
+ final RemoteTransition remote = options.getRemoteTransition();
final ActivityRecord r = rootTask.topRunningActivity();
- if (r == null || r.mVisibleRequested || !r.attachedToProcess()
+ if (r == null || r.mVisibleRequested || !r.attachedToProcess() || remote == null
|| !r.mActivityComponent.equals(intent.getComponent())
// Recents keeps invisible while device is locked.
|| r.mDisplayContent.isKeyguardLocked()) {
return false;
}
mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r);
- final RemoteTransition remote = options.getRemoteTransition();
- if (remote != null && rootTask.mTransitionController.isCollecting()) {
- final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
- 0 /* flags */, rootTask.mTransitionController,
- mService.mWindowManager.mSyncEngine);
+ final ActivityMetricsLogger.LaunchingState launchingState =
+ mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
+ final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT,
+ 0 /* flags */, r.mTransitionController, mService.mWindowManager.mSyncEngine);
+ if (r.mTransitionController.isCollecting()) {
// Special case: we are entering recents while an existing transition is running. In
// this case, we know it's safe to "defer" the activity launch, so lets do so now so
// that it can get its own transition and thus update launcher correctly.
mService.mWindowManager.mSyncEngine.queueSyncSet(
- () -> rootTask.mTransitionController.moveToCollecting(transition),
() -> {
- final Task task = r.getTask();
- task.mTransitionController.requestStartTransition(transition,
- task, remote, null /* displayChange */);
- task.mTransitionController.collect(task);
- startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+ if (r.isAttached()) {
+ r.mTransitionController.moveToCollecting(transition);
+ }
+ },
+ () -> {
+ if (r.isAttached() && transition.isCollecting()) {
+ startExistingRecentsIfPossibleInner(options, r, rootTask,
+ launchingState, remote, transition);
+ }
});
} else {
- final Task task = r.getTask();
- task.mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_TO_FRONT,
- 0 /* flags */, task, task /* readyGroupRef */,
- options.getRemoteTransition(), null /* displayChange */);
- startExistingRecentsIfPossibleInner(intent, options, r, task, rootTask);
+ r.mTransitionController.moveToCollecting(transition);
+ startExistingRecentsIfPossibleInner(options, r, rootTask, launchingState, remote,
+ transition);
}
return true;
}
- void startExistingRecentsIfPossibleInner(Intent intent, ActivityOptions options,
- ActivityRecord r, Task task, Task rootTask) {
- final ActivityMetricsLogger.LaunchingState launchingState =
- mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
+ private void startExistingRecentsIfPossibleInner(ActivityOptions options, ActivityRecord r,
+ Task rootTask, ActivityMetricsLogger.LaunchingState launchingState,
+ RemoteTransition remoteTransition, Transition transition) {
+ final Task task = r.getTask();
mService.deferWindowLayout();
try {
+ r.mTransitionController.requestStartTransition(transition,
+ task, remoteTransition, null /* displayChange */);
+ r.mTransitionController.collect(task);
r.mTransitionController.setTransientLaunch(r,
TaskDisplayArea.getRootTaskAbove(rootTask));
task.moveToFront("startExistingRecents");
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index dc69ca6..435caa7 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -55,7 +55,9 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.INVALID_UID;
import static android.view.Display.DEFAULT_DISPLAY;
+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_START_ACTIVITY_IN_TASK_FRAGMENT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
@@ -1666,9 +1668,9 @@
&& transitionController.getTransitionPlayer() != null)
? transitionController.createTransition(TRANSIT_OPEN) : null;
RemoteTransition remoteTransition = r.takeRemoteTransition();
- transitionController.collect(r);
try {
mService.deferWindowLayout();
+ transitionController.collect(r);
try {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
@@ -2247,12 +2249,6 @@
? targetTask.getTopNonFinishingActivity()
: targetTaskTop;
- // At this point we are certain we want the task moved to the front. If we need to dismiss
- // any other always-on-top root tasks, now is the time to do it.
- if (targetTaskTop.canTurnScreenOn() && mService.isDreaming()) {
- targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
- }
-
if (mMovedToFront) {
// We moved the task to front, use starting window to hide initial drawn delay.
targetTaskTop.showStartingWindow(true /* taskSwitch */);
@@ -2264,6 +2260,12 @@
// And for paranoia, make sure we have correctly resumed the top activity.
resumeTargetRootTaskIfNeeded();
+ // This is moving an existing task to front. But since dream activity has a higher z-order
+ // to cover normal activities, it needs the awakening event to be dismissed.
+ if (mService.isDreaming() && targetTaskTop.canTurnScreenOn()) {
+ targetTaskTop.mTaskSupervisor.wakeUp("recycleTask#turnScreenOnFlag");
+ }
+
mLastStartActivityRecord = targetTaskTop;
return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
}
@@ -2394,10 +2396,15 @@
mStartActivity.mUserId);
if (act != null) {
final Task task = act.getTask();
- boolean actuallyMoved = task.moveActivityToFrontLocked(act);
+ boolean actuallyMoved = task.moveActivityToFront(act);
if (actuallyMoved) {
// Only record if the activity actually moved.
mMovedToTopActivity = act;
+ if (mNoAnimation) {
+ act.mDisplayContent.prepareAppTransition(TRANSIT_NONE);
+ } else {
+ act.mDisplayContent.prepareAppTransition(TRANSIT_TO_FRONT);
+ }
}
act.updateOptionsLocked(mOptions);
deliverNewIntent(act, intentGrants);
@@ -3037,12 +3044,6 @@
newParent = candidateTf;
}
}
- if (newParent.asTask() == null) {
- // only collect task-fragments.
- // TODO(b/258095975): we probably shouldn't ever collect the parent here since it isn't
- // changing. The logic that changes it should collect it.
- newParent.mTransitionController.collect(newParent);
- }
if (mStartActivity.getTaskFragment() == null
|| mStartActivity.getTaskFragment() == newParent) {
newParent.addChild(mStartActivity, POSITION_TOP);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index fe691c6..6eaeb15e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -27,6 +27,7 @@
import static android.app.ActivityManager.START_FLAG_NATIVE_DEBUGGING;
import static android.app.ActivityManager.START_FLAG_TRACK_ALLOCATION;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.app.ActivityOptions.ANIM_REMOTE_ANIMATION;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
import static android.app.WaitResult.INVALID_DELAY;
@@ -2592,6 +2593,11 @@
// Apply options to prevent pendingOptions be taken when scheduling
// activity lifecycle transaction to make sure the override pending app
// transition will be applied immediately.
+ if (activityOptions != null
+ && activityOptions.getAnimationType() == ANIM_REMOTE_ANIMATION) {
+ targetActivity.mPendingRemoteAnimation =
+ activityOptions.getRemoteAnimationAdapter();
+ }
targetActivity.applyOptionsAnimation();
if (activityOptions != null && activityOptions.getLaunchCookie() != null) {
targetActivity.mLaunchCookie = activityOptions.getLaunchCookie();
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 27370bf..adf862b 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -169,7 +169,8 @@
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
- private final TransitionAnimation mTransitionAnimation;
+ @VisibleForTesting
+ final TransitionAnimation mTransitionAnimation;
private @TransitionFlags int mNextAppTransitionFlags = 0;
private final ArrayList<Integer> mNextAppTransitionRequests = new ArrayList<>();
@@ -315,10 +316,33 @@
setAppTransitionState(APP_STATE_TIMEOUT);
}
+ /**
+ * Gets the animation overridden by app via {@link #overridePendingAppTransition}.
+ */
+ @Nullable
+ Animation getNextAppRequestedAnimation(boolean enter) {
+ final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
+ mNextAppTransitionPackage,
+ enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
+ if (mNextAppTransitionBackgroundColor != 0 && a != null) {
+ a.setBackdropColor(mNextAppTransitionBackgroundColor);
+ }
+ return a;
+ }
+
+ /**
+ * Gets the animation background color overridden by app via
+ * {@link #overridePendingAppTransition}.
+ */
@ColorInt int getNextAppTransitionBackgroundColor() {
return mNextAppTransitionBackgroundColor;
}
+ @VisibleForTesting
+ boolean isNextAppTransitionOverrideRequested() {
+ return mNextAppTransitionOverrideRequested;
+ }
+
HardwareBuffer getAppTransitionThumbnailHeader(WindowContainer container) {
AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
container.hashCode());
@@ -411,9 +435,12 @@
}
void clear() {
+ clear(true /* clearAppOverride */);
+ }
+
+ private void clear(boolean clearAppOverride) {
mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
mNextAppTransitionOverrideRequested = false;
- mNextAppTransitionPackage = null;
mNextAppTransitionAnimationsSpecs.clear();
mRemoteAnimationController = null;
mNextAppTransitionAnimationsSpecsFuture = null;
@@ -421,6 +448,12 @@
mAnimationFinishedCallback = null;
mOverrideTaskTransition = false;
mNextAppTransitionIsSync = false;
+ if (clearAppOverride) {
+ mNextAppTransitionPackage = null;
+ mNextAppTransitionEnter = 0;
+ mNextAppTransitionExit = 0;
+ mNextAppTransitionBackgroundColor = 0;
+ }
}
void freeze() {
@@ -528,7 +561,7 @@
return TransitionAnimation.loadAnimationSafely(context, resId, TAG);
}
- static int mapOpenCloseTransitTypes(int transit, boolean enter) {
+ private static int mapOpenCloseTransitTypes(int transit, boolean enter) {
int animAttr = 0;
switch (transit) {
case TRANSIT_OLD_ACTIVITY_OPEN:
@@ -788,11 +821,7 @@
"applyAnimation: anim=%s transit=%s Callers=%s", a,
appTransitionOldToString(transit), Debug.getCallers(3));
} else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
- a = mTransitionAnimation.loadAppTransitionAnimation(mNextAppTransitionPackage,
- enter ? mNextAppTransitionEnter : mNextAppTransitionExit);
- if (mNextAppTransitionBackgroundColor != 0) {
- a.setBackdropColor(mNextAppTransitionBackgroundColor);
- }
+ a = getNextAppRequestedAnimation(enter);
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: anim=%s nextAppTransition=ANIM_CUSTOM transit=%s "
+ "isEntrance=%b Callers=%s",
@@ -1032,7 +1061,9 @@
ProtoLog.i(WM_DEBUG_APP_TRANSITIONS, "Override pending remote transitionSet=%b adapter=%s",
isTransitionSet(), remoteAnimationAdapter);
if (isTransitionSet() && !mNextAppTransitionIsSync) {
- clear();
+ // ActivityEmbedding animation will run by the app process for which we want to respect
+ // the app override for whether or not to show background color.
+ clear(!isActivityEmbedding /* clearAppOverride */);
mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
remoteAnimationAdapter, mHandler, isActivityEmbedding);
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 12133bc..7e93109 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -562,6 +562,34 @@
}
/**
+ * Whether the transition contains any embedded {@link TaskFragment} that does not fill the
+ * parent {@link Task} before or after the transition.
+ */
+ private boolean transitionContainsTaskFragmentWithBoundsOverride() {
+ for (int i = mDisplayContent.mChangingContainers.size() - 1; i >= 0; i--) {
+ final WindowContainer wc = mDisplayContent.mChangingContainers.valueAt(i);
+ if (wc.isEmbedded()) {
+ // Contains embedded TaskFragment with bounds changed.
+ return true;
+ }
+ }
+ mTempTransitionWindows.clear();
+ mTempTransitionWindows.addAll(mDisplayContent.mClosingApps);
+ mTempTransitionWindows.addAll(mDisplayContent.mOpeningApps);
+ boolean containsTaskFragmentWithBoundsOverride = false;
+ for (int i = mTempTransitionWindows.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = mTempTransitionWindows.get(i).asActivityRecord();
+ final TaskFragment tf = r.getTaskFragment();
+ if (tf != null && tf.isEmbeddedWithBoundsOverride()) {
+ containsTaskFragmentWithBoundsOverride = true;
+ break;
+ }
+ }
+ mTempTransitionWindows.clear();
+ return containsTaskFragmentWithBoundsOverride;
+ }
+
+ /**
* Finds the common parent {@link Task} that is parent of all embedded app windows in the
* current transition.
* @return {@code null} if app windows in the transition are not children of the same Task, or
@@ -664,12 +692,17 @@
if (transitionMayContainNonAppWindows(transit)) {
return false;
}
+ if (!transitionContainsTaskFragmentWithBoundsOverride()) {
+ // No need to play TaskFragment remote animation if all embedded TaskFragment in the
+ // transition fill the Task.
+ return false;
+ }
final Task task = findParentTaskForAllEmbeddedWindows();
final ITaskFragmentOrganizer organizer = findTaskFragmentOrganizer(task);
final RemoteAnimationDefinition definition = organizer != null
? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
- .getRemoteAnimationDefinition(organizer, task.mTaskId)
+ .getRemoteAnimationDefinition(organizer)
: null;
final RemoteAnimationAdapter adapter = definition != null
? definition.getAdapter(transit, activityTypes)
@@ -1196,13 +1229,23 @@
"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;
}
- if (activity.isAnimating(PARENTS, ANIMATION_TYPE_RECENTS)) {
+ // 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;
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index d345227..cd26e2e 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -226,6 +226,9 @@
}
private void setReady(boolean ready) {
+ if (mReady == ready) {
+ return;
+ }
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId);
mReady = ready;
if (!ready) return;
@@ -239,7 +242,9 @@
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc);
wc.setSyncGroup(this);
wc.prepareSync();
- mWm.mWindowPlacerLocked.requestTraversal();
+ if (mReady) {
+ mWm.mWindowPlacerLocked.requestTraversal();
+ }
}
void onCancelSync(WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index b033dca..a32e460 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -341,7 +341,11 @@
if (childArea == null) {
continue;
}
- pw.println(prefix + "* " + childArea.getName());
+ pw.print(prefix + "* " + childArea.getName());
+ if (childArea.isOrganized()) {
+ pw.print(" (organized)");
+ }
+ pw.println();
if (childArea.isTaskDisplayArea()) {
// TaskDisplayArea can only contain task. And it is already printed by display.
continue;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 38f6a53..b9aeec6 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3353,7 +3353,7 @@
}
}
mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
- controller.mTransitionMetricsReporter.associate(t,
+ controller.mTransitionMetricsReporter.associate(t.getToken(),
startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
startAsyncRotation(false /* shouldDebounce */);
}
@@ -3458,9 +3458,8 @@
@Override
public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
- super.dump(pw, prefix, dumpAll);
pw.print(prefix);
- pw.println("Display: mDisplayId=" + mDisplayId + " rootTasks=" + getRootTaskCount());
+ pw.println("Display: mDisplayId=" + mDisplayId + (isOrganized() ? " (organized)" : ""));
final String subPrefix = " " + prefix;
pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity);
@@ -3491,6 +3490,7 @@
pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion);
pw.println();
+ super.dump(pw, prefix, dumpAll);
pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq);
pw.print(" mCurrentFocus="); pw.println(mCurrentFocus);
@@ -3582,6 +3582,7 @@
pw.println();
mInsetsStateController.dump(prefix, pw);
mDwpcHelper.dump(prefix, pw);
+ pw.println();
}
@Override
@@ -4935,9 +4936,8 @@
@Override
boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) {
return okToDisplay(ignoreFrozen, ignoreScreenOn)
- && (mDisplayId != DEFAULT_DISPLAY
- || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
- && getDisplayPolicy().isScreenOnFully();
+ && (mDisplayId != DEFAULT_DISPLAY || mWmService.mPolicy.okToAnimate(ignoreScreenOn))
+ && (ignoreFrozen || mDisplayPolicy.isScreenOnFully());
}
static final class TaskForResizePointSearchResult implements Predicate<Task> {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 2688ff7..2c289c9 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -282,6 +282,12 @@
private final ArraySet<WindowState> mInsetsSourceWindowsExceptIme = new ArraySet<>();
+ /** Apps which are controlling the appearance of system bars */
+ private final ArraySet<ActivityRecord> mSystemBarColorApps = new ArraySet<>();
+
+ /** Apps which are relaunching and were controlling the appearance of system bars */
+ private final ArraySet<ActivityRecord> mRelaunchingSystemBarColorApps = new ArraySet<>();
+
private boolean mIsFreeformWindowOverlappingWithNavBar;
private boolean mLastImmersiveMode;
@@ -1550,6 +1556,7 @@
mStatusBarBackgroundWindows.clear();
mStatusBarColorCheckedBounds.setEmpty();
mStatusBarBackgroundCheckedBounds.setEmpty();
+ mSystemBarColorApps.clear();
mAllowLockscreenWhenOn = false;
mShowingDream = false;
@@ -1626,6 +1633,7 @@
win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
new Rect(win.getFrame())));
mStatusBarColorCheckedBounds.union(sTmpRect);
+ addSystemBarColorApp(win);
}
}
@@ -1638,6 +1646,7 @@
if (isOverlappingWithNavBar) {
if (mNavBarColorWindowCandidate == null) {
mNavBarColorWindowCandidate = win;
+ addSystemBarColorApp(win);
}
if (mNavBarBackgroundWindow == null) {
mNavBarBackgroundWindow = win;
@@ -1656,9 +1665,11 @@
}
} else if (win.isDimming()) {
if (mStatusBar != null) {
- addStatusBarAppearanceRegionsForDimmingWindow(
+ if (addStatusBarAppearanceRegionsForDimmingWindow(
win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,
- mStatusBar.getFrame(), win.getBounds(), win.getFrame());
+ mStatusBar.getFrame(), win.getBounds(), win.getFrame())) {
+ addSystemBarColorApp(win);
+ }
}
if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) {
mNavBarColorWindowCandidate = win;
@@ -1666,18 +1677,21 @@
}
}
- private void addStatusBarAppearanceRegionsForDimmingWindow(int appearance, Rect statusBarFrame,
- Rect winBounds, Rect winFrame) {
+ /**
+ * Returns true if mStatusBarAppearanceRegionList is changed.
+ */
+ private boolean addStatusBarAppearanceRegionsForDimmingWindow(
+ int appearance, Rect statusBarFrame, Rect winBounds, Rect winFrame) {
if (!sTmpRect.setIntersect(winBounds, statusBarFrame)) {
- return;
+ return false;
}
if (mStatusBarColorCheckedBounds.contains(sTmpRect)) {
- return;
+ return false;
}
if (appearance == 0 || !sTmpRect2.setIntersect(winFrame, statusBarFrame)) {
mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(winBounds)));
mStatusBarColorCheckedBounds.union(sTmpRect);
- return;
+ return true;
}
// A dimming window can divide status bar into different appearance regions (up to 3).
// +---------+-------------+---------+
@@ -1706,6 +1720,14 @@
// We don't have vertical status bar yet, so we don't handle the other orientation.
}
mStatusBarColorCheckedBounds.union(sTmpRect);
+ return true;
+ }
+
+ private void addSystemBarColorApp(WindowState win) {
+ final ActivityRecord app = win.mActivityRecord;
+ if (app != null) {
+ mSystemBarColorApps.add(app);
+ }
}
/**
@@ -2202,6 +2224,25 @@
return mDisplayContent.getInsetsPolicy();
}
+ /**
+ * Called when an app has started replacing its main window.
+ */
+ void addRelaunchingApp(ActivityRecord app) {
+ if (mSystemBarColorApps.contains(app)) {
+ mRelaunchingSystemBarColorApps.add(app);
+ }
+ }
+
+ /**
+ * Called when an app has finished replacing its main window or aborted.
+ */
+ void removeRelaunchingApp(ActivityRecord app) {
+ final boolean removed = mRelaunchingSystemBarColorApps.remove(app);
+ if (removed & mRelaunchingSystemBarColorApps.isEmpty()) {
+ updateSystemBarAttributes();
+ }
+ }
+
void resetSystemBarAttributes() {
mLastDisableFlags = 0;
updateSystemBarAttributes();
@@ -2244,6 +2285,11 @@
final int displayId = getDisplayId();
final int disableFlags = win.getDisableFlags();
final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
+ if (!mRelaunchingSystemBarColorApps.isEmpty()) {
+ // The appearance of system bars might change while relaunching apps. We don't report
+ // the intermediate state to system UI. Otherwise, it might trigger redundant effects.
+ return;
+ }
final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate,
mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
final boolean isNavbarColorManagedByIme =
@@ -2707,6 +2753,14 @@
pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
pw.println(mTopFullscreenOpaqueWindowState);
}
+ if (!mSystemBarColorApps.isEmpty()) {
+ pw.print(prefix); pw.print("mSystemBarColorApps=");
+ pw.println(mSystemBarColorApps);
+ }
+ if (!mRelaunchingSystemBarColorApps.isEmpty()) {
+ pw.print(prefix); pw.print("mRelaunchingSystemBarColorApps=");
+ pw.println(mRelaunchingSystemBarColorApps);
+ }
if (mNavBarColorWindowCandidate != null) {
pw.print(prefix); pw.print("mNavBarColorWindowCandidate=");
pw.println(mNavBarColorWindowCandidate);
diff --git a/services/core/java/com/android/server/wm/LaunchParamsUtil.java b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
new file mode 100644
index 0000000..09a17e1
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LaunchParamsUtil.java
@@ -0,0 +1,198 @@
+/*
+ * 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.server.wm;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.NonNull;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.util.Size;
+import android.view.View;
+
+/**
+ * The static class that defines some utility constants and functions that are shared among launch
+ * params modifiers.
+ */
+class LaunchParamsUtil {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "LaunchParamsUtil" : TAG_ATM;
+ private static final boolean DEBUG = false;
+
+ // Screen size of Nexus 5x
+ static final int DEFAULT_PORTRAIT_FREEFORM_WIDTH_DP = 412;
+ static final int DEFAULT_PORTRAIT_FREEFORM_HEIGHT_DP = 732;
+
+ // One of the most common tablet sizes that are small enough to fit in most large screens.
+ private static final int DEFAULT_LANDSCAPE_FREEFORM_WIDTH_DP = 1064;
+ private static final int DEFAULT_LANDSCAPE_FREEFORM_HEIGHT_DP = 600;
+
+ private static final int DISPLAY_EDGE_OFFSET_DP = 27;
+
+ private static final Rect TMP_STABLE_BOUNDS = new Rect();
+
+ private LaunchParamsUtil() {}
+
+ /**
+ * Gets centered bounds of width x height. If inOutBounds is not empty, the result bounds
+ * centers at its center or displayArea's app bounds center if inOutBounds is empty.
+ */
+ static void centerBounds(@NonNull TaskDisplayArea displayArea, int width, int height,
+ @NonNull Rect inOutBounds) {
+ if (inOutBounds.isEmpty()) {
+ displayArea.getStableRect(inOutBounds);
+ }
+ final int left = inOutBounds.centerX() - width / 2;
+ final int top = inOutBounds.centerY() - height / 2;
+ inOutBounds.set(left, top, left + width, top + height);
+ }
+
+ /**
+ * Calculate the default size for a freeform environment. |defaultSize| is used as the default
+ * DP size, but if this is null, the portrait phone size is used.
+ */
+ static Size getDefaultFreeformSize(@NonNull ActivityRecord activityRecord,
+ @NonNull TaskDisplayArea displayArea,
+ @NonNull ActivityInfo.WindowLayout layout, int orientation,
+ @NonNull Rect stableBounds) {
+ // Get window size based on Nexus 5x screen, we assume that this is enough to show content
+ // of activities.
+ final float density = (float) displayArea.getConfiguration().densityDpi / DENSITY_DEFAULT;
+ final int freeformWidthInDp = (orientation == SCREEN_ORIENTATION_LANDSCAPE)
+ ? DEFAULT_LANDSCAPE_FREEFORM_WIDTH_DP : DEFAULT_PORTRAIT_FREEFORM_WIDTH_DP;
+ final int freeformHeightInDp = (orientation == SCREEN_ORIENTATION_LANDSCAPE)
+ ? DEFAULT_LANDSCAPE_FREEFORM_HEIGHT_DP : DEFAULT_PORTRAIT_FREEFORM_HEIGHT_DP;
+ final int freeformWidth = (int) (freeformWidthInDp * density + 0.5f);
+ final int freeformHeight = (int) (freeformHeightInDp * density + 0.5f);
+
+ // Minimum layout requirements.
+ final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth;
+ final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight;
+
+ // Max size, which is letterboxing/pillarboxing in displayArea. That's to say the large
+ // dimension of default size is the small dimension of displayArea size, and the small
+ // dimension of default size is calculated to keep the same aspect ratio as the
+ // displayArea's. Here we use stable bounds of displayArea because that indicates the area
+ // that isn't occupied by system widgets (e.g. sysbar and navbar).
+ final int portraitHeight = Math.min(stableBounds.width(), stableBounds.height());
+ final int otherDimension = Math.max(stableBounds.width(), stableBounds.height());
+ final int portraitWidth = (portraitHeight * portraitHeight) / otherDimension;
+ final int maxWidth = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? portraitHeight
+ : portraitWidth;
+ final int maxHeight = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? portraitWidth
+ : portraitHeight;
+ final int width = Math.min(maxWidth, Math.max(freeformWidth, layoutMinWidth));
+ final int height = Math.min(maxHeight, Math.max(freeformHeight, layoutMinHeight));
+ final float aspectRatio = (float) Math.max(width, height) / (float) Math.min(width, height);
+
+ // Aspect ratio requirements.
+ final float minAspectRatio = activityRecord.getMinAspectRatio();
+ final float maxAspectRatio = activityRecord.info.getMaxAspectRatio();
+
+ // Adjust the width and height to the aspect ratio requirements.
+ int adjWidth = width;
+ int adjHeight = height;
+ if (minAspectRatio >= 1 && aspectRatio < minAspectRatio) {
+ // The aspect ratio is below the minimum, adjust it to the minimum.
+ if (orientation == SCREEN_ORIENTATION_LANDSCAPE) {
+ // Fix the width, scale the height.
+ adjHeight = (int) (adjWidth / minAspectRatio + 0.5f);
+ } else {
+ // Fix the height, scale the width.
+ adjWidth = (int) (adjHeight / minAspectRatio + 0.5f);
+ }
+ } else if (maxAspectRatio >= 1 && aspectRatio > maxAspectRatio) {
+ // The aspect ratio exceeds the maximum, adjust it to the maximum.
+ if (orientation == SCREEN_ORIENTATION_LANDSCAPE) {
+ // Fix the width, scale the height.
+ adjHeight = (int) (adjWidth / maxAspectRatio + 0.5f);
+ } else {
+ // Fix the height, scale the width.
+ adjWidth = (int) (adjHeight / maxAspectRatio + 0.5f);
+ }
+ }
+
+ return new Size(adjWidth, adjHeight);
+ }
+
+ static void adjustBoundsToFitInDisplayArea(@NonNull TaskDisplayArea displayArea,
+ int layoutDirection,
+ @NonNull ActivityInfo.WindowLayout layout,
+ @NonNull Rect inOutBounds) {
+ // Give a small margin between the window bounds and the display bounds.
+ final Rect stableBounds = TMP_STABLE_BOUNDS;
+ displayArea.getStableRect(stableBounds);
+ final float density = (float) displayArea.getConfiguration().densityDpi / DENSITY_DEFAULT;
+ final int displayEdgeOffset = (int) (DISPLAY_EDGE_OFFSET_DP * density + 0.5f);
+ stableBounds.inset(displayEdgeOffset, displayEdgeOffset);
+
+ if (stableBounds.width() < inOutBounds.width()
+ || stableBounds.height() < inOutBounds.height()) {
+ final float heightShrinkRatio = stableBounds.width() / (float) inOutBounds.width();
+ final float widthShrinkRatio =
+ stableBounds.height() / (float) inOutBounds.height();
+ final float shrinkRatio = Math.min(heightShrinkRatio, widthShrinkRatio);
+ // Minimum layout requirements.
+ final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth;
+ final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight;
+ int adjustedWidth = Math.max(layoutMinWidth, (int) (inOutBounds.width() * shrinkRatio));
+ int adjustedHeight = Math.max(layoutMinHeight,
+ (int) (inOutBounds.height() * shrinkRatio));
+ if (stableBounds.width() < adjustedWidth
+ || stableBounds.height() < adjustedHeight) {
+ // There is no way for us to fit the bounds in the displayArea without breaking min
+ // size constraints. Set the min size to make visible as much content as possible.
+ final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL
+ ? stableBounds.right - adjustedWidth
+ : stableBounds.left;
+ inOutBounds.set(left, stableBounds.top, left + adjustedWidth,
+ stableBounds.top + adjustedHeight);
+ return;
+ }
+ inOutBounds.set(inOutBounds.left, inOutBounds.top,
+ inOutBounds.left + adjustedWidth, inOutBounds.top + adjustedHeight);
+ }
+
+ final int dx;
+ if (inOutBounds.right > stableBounds.right) {
+ // Right edge is out of displayArea.
+ dx = stableBounds.right - inOutBounds.right;
+ } else if (inOutBounds.left < stableBounds.left) {
+ // Left edge is out of displayArea.
+ dx = stableBounds.left - inOutBounds.left;
+ } else {
+ // Vertical edges are all in displayArea.
+ dx = 0;
+ }
+
+ final int dy;
+ if (inOutBounds.top < stableBounds.top) {
+ // Top edge is out of displayArea.
+ dy = stableBounds.top - inOutBounds.top;
+ } else if (inOutBounds.bottom > stableBounds.bottom) {
+ // Bottom edge is out of displayArea.
+ dy = stableBounds.bottom - inOutBounds.bottom;
+ } else {
+ // Horizontal edges are all in displayArea.
+ dy = 0;
+ }
+ inOutBounds.offset(dx, dy);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index ea82417..6edb63c 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -87,10 +87,6 @@
private final LetterboxConfiguration mLetterboxConfiguration;
private final ActivityRecord mActivityRecord;
- // Taskbar expanded height. Used to determine whether to crop an app window to display rounded
- // corners above the taskbar.
- private final float mExpandedTaskBarHeight;
-
private boolean mShowWallpaperForLetterboxBackground;
@Nullable
@@ -102,8 +98,6 @@
// is created in its constructor. It shouldn't be used in this constructor but it's safe
// to use it after since controller is only used in ActivityRecord.
mActivityRecord = activityRecord;
- mExpandedTaskBarHeight =
- getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
}
/** Cleans up {@link Letterbox} if it exists.*/
@@ -285,14 +279,17 @@
}
float getSplitScreenAspectRatio() {
+ // Getting the same aspect ratio that apps get in split screen.
+ final DisplayContent displayContent = mActivityRecord.getDisplayContent();
+ if (displayContent == null) {
+ return getDefaultMinAspectRatioForUnresizableApps();
+ }
int dividerWindowWidth =
getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
int dividerInsets =
getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
int dividerSize = dividerWindowWidth - dividerInsets * 2;
-
- // Getting the same aspect ratio that apps get in split screen.
- Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds());
+ final Rect bounds = new Rect(displayContent.getBounds());
if (bounds.width() >= bounds.height()) {
bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
bounds.right = bounds.centerX();
@@ -501,12 +498,16 @@
if (hasVisibleTaskbar(mainWindow)) {
cropBounds = new Rect(mActivityRecord.getBounds());
+
+ // Rounded corners should be displayed above the taskbar.
+ // It is important to call adjustBoundsForTaskbarUnchecked before offsetTo
+ // because taskbar bounds are in screen coordinates
+ adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
+
// Activity bounds are in screen coordinates while (0,0) for activity's surface
// control is at the top left corner of an app window so offsetting bounds
// accordingly.
cropBounds.offsetTo(0, 0);
- // Rounded corners should be displayed above the taskbar.
- adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
}
transaction
@@ -551,7 +552,6 @@
final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
return taskbarInsetsSource != null
- && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
&& taskbarInsetsSource.isVisible();
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2866f42..89cad9c 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3457,7 +3457,6 @@
final DisplayContent display = getChildAt(i);
display.dump(pw, prefix, dumpAll);
}
- pw.println();
}
/**
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index f3670e4..1ae7816 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -440,6 +440,7 @@
.setPixelFormat(PixelFormat.RGBA_8888)
.setChildrenOnly(true)
.setAllowProtected(true)
+ .setCaptureSecureLayers(true)
.build();
final SurfaceControl.ScreenshotHardwareBuffer edgeBuffer =
SurfaceControl.captureLayers(captureArgs);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 66d7af9..84e3014 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -20,9 +20,6 @@
import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED;
import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -1408,13 +1405,26 @@
* Reorder the history task so that the passed activity is brought to the front.
* @return whether it was actually moved (vs already being top).
*/
- final boolean moveActivityToFrontLocked(ActivityRecord newTop) {
+ final boolean moveActivityToFront(ActivityRecord newTop) {
ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing and adding activity %s to root task at top "
+ "callers=%s", newTop, Debug.getCallers(4));
- int origDist = getDistanceFromTop(newTop);
- positionChildAtTop(newTop);
+ final TaskFragment taskFragment = newTop.getTaskFragment();
+ boolean moved;
+ if (taskFragment != this) {
+ if (taskFragment.isEmbedded() && taskFragment.getNonFinishingActivityCount() == 1) {
+ taskFragment.mClearedForReorderActivityToFront = true;
+ }
+ newTop.reparent(this, POSITION_TOP);
+ moved = true;
+ if (taskFragment.isEmbedded()) {
+ mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
+ .onActivityReparentedToTask(newTop);
+ }
+ } else {
+ moved = moveChildToFront(newTop);
+ }
updateEffectiveIntent();
- return getDistanceFromTop(newTop) != origDist;
+ return moved;
}
@Override
@@ -1591,6 +1601,11 @@
removeChild(r, reason);
});
} else {
+ // Finish or destroy apps from the bottom to ensure that all the other activity have
+ // been finished and the top task in another task gets resumed when a top activity is
+ // removed. Otherwise, shell transitions wouldn't run because there would be no event
+ // that sets the transition ready.
+ final boolean traverseTopToBottom = !mTransitionController.isShellTransitionsEnabled();
forAllActivities((r) -> {
if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
return;
@@ -1604,7 +1619,7 @@
} else {
r.destroyIfPossible(reason);
}
- });
+ }, traverseTopToBottom);
}
}
@@ -3096,20 +3111,6 @@
});
}
- void positionChildAtTop(ActivityRecord child) {
- positionChildAt(child, POSITION_TOP);
- }
-
- void positionChildAt(ActivityRecord child, int position) {
- if (child == null) {
- Slog.w(TAG_WM,
- "Attempted to position of non-existing app");
- return;
- }
-
- positionChildAt(position, child, false /* includeParents */);
- }
-
void setTaskDescription(TaskDescription taskDescription) {
mTaskDescription = taskDescription;
}
@@ -3125,20 +3126,6 @@
}
@Override
- int getOrientation(int candidate) {
- return canSpecifyOrientation() ? super.getOrientation(candidate) : SCREEN_ORIENTATION_UNSET;
- }
-
- private boolean canSpecifyOrientation() {
- final int windowingMode = getWindowingMode();
- final int activityType = getActivityType();
- return windowingMode == WINDOWING_MODE_FULLSCREEN
- || activityType == ACTIVITY_TYPE_HOME
- || activityType == ACTIVITY_TYPE_RECENTS
- || activityType == ACTIVITY_TYPE_ASSISTANT;
- }
-
- @Override
void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
final int count = mChildren.size();
boolean isLeafTask = true;
@@ -5196,7 +5183,16 @@
final Task task = taskTop.getTask();
// If ActivityOptions are moved out and need to be aborted or moved to taskTop.
- final ActivityOptions topOptions = sResetTargetTaskHelper.process(task, forceReset);
+ final ActivityOptions topOptions;
+
+ // Set the task to be reused, so the TaskFragment#mClearedTaskForReuse can be set if the
+ // embedded activities are finished while reset task.
+ mReuseTask = true;
+ try {
+ topOptions = sResetTargetTaskHelper.process(task, forceReset);
+ } finally {
+ mReuseTask = false;
+ }
if (mChildren.contains(task)) {
final ActivityRecord newTop = task.getTopNonFinishingActivity();
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index ff09163..1088a2e 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -27,7 +27,6 @@
import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
@@ -649,17 +648,6 @@
}, SCREEN_ORIENTATION_UNSET);
}
- // Apps and their containers are not allowed to specify an orientation of non floating
- // visible tasks created by organizer and that has an adjacent task.
- final Task nonFloatingTopTask =
- getTask(t -> !t.getWindowConfiguration().tasksAreFloating());
- if (nonFloatingTopTask != null) {
- final Task task = nonFloatingTopTask.getCreatedByOrganizerTask();
- if (task != null && task.getAdjacentTaskFragment() != null && task.isVisible()) {
- return SCREEN_ORIENTATION_UNSPECIFIED;
- }
- }
-
final int orientation = super.getOrientation(candidate);
if (orientation != SCREEN_ORIENTATION_UNSET
&& orientation != SCREEN_ORIENTATION_BEHIND) {
@@ -929,6 +917,7 @@
// Update windowing mode if necessary, e.g. launch into a different windowing mode.
if (windowingMode != WINDOWING_MODE_UNDEFINED && candidateTask.isRootTask()
&& candidateTask.getWindowingMode() != windowingMode) {
+ candidateTask.mTransitionController.collect(candidateTask);
candidateTask.setWindowingMode(windowingMode);
}
return candidateTask.getRootTask();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 377c5b4..cb25498 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -17,7 +17,9 @@
package com.android.server.wm;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -27,6 +29,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -76,6 +80,7 @@
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.ResumeActivityItem;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
@@ -222,6 +227,14 @@
private TaskFragment mAdjacentTaskFragment;
/**
+ * Unlike the {@link mAdjacentTaskFragment}, the companion TaskFragment is not always visually
+ * adjacent to this one, but this TaskFragment will be removed by the organizer if the
+ * companion TaskFragment is removed.
+ */
+ @Nullable
+ private TaskFragment mCompanionTaskFragment;
+
+ /**
* Prevents duplicate calls to onTaskAppeared.
*/
boolean mTaskFragmentAppearedSent;
@@ -239,6 +252,12 @@
boolean mClearedTaskFragmentForPip;
/**
+ * The last running activity of the TaskFragment was removed and added to the top-most of the
+ * Task because it was launched with FLAG_ACTIVITY_REORDER_TO_FRONT.
+ */
+ boolean mClearedForReorderActivityToFront;
+
+ /**
* When we are in the process of pausing an activity, before starting the
* next one, this variable holds the activity that is currently being paused.
*
@@ -289,6 +308,12 @@
private final IBinder mFragmentToken;
/**
+ * Whether to delay the call to {@link #updateOrganizedTaskFragmentSurface()} when there is a
+ * configuration change.
+ */
+ private boolean mDelayOrganizedTaskFragmentSurfaceUpdate;
+
+ /**
* Whether to delay the last activity of TaskFragment being immediately removed while finishing.
* This should only be set on a embedded TaskFragment, where the organizer can have the
* opportunity to perform animations and finishing the adjacent TaskFragment.
@@ -388,6 +413,14 @@
}
}
+ void setCompanionTaskFragment(@Nullable TaskFragment companionTaskFragment) {
+ mCompanionTaskFragment = companionTaskFragment;
+ }
+
+ TaskFragment getCompanionTaskFragment() {
+ return mCompanionTaskFragment;
+ }
+
void resetAdjacentTaskFragment() {
// Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
@@ -553,15 +586,7 @@
@Override
boolean isEmbedded() {
- if (mIsEmbedded) {
- return true;
- }
- final WindowContainer<?> parent = getParent();
- if (parent != null) {
- final TaskFragment taskFragment = parent.asTaskFragment();
- return taskFragment != null && taskFragment.isEmbedded();
- }
- return false;
+ return mIsEmbedded;
}
@EmbeddingCheckResult
@@ -1140,8 +1165,16 @@
}
next.delayedResume = false;
- final TaskDisplayArea taskDisplayArea = getDisplayArea();
+ // If we are currently pausing an activity, then don't do anything until that is done.
+ final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
+ if (!allPausedComplete) {
+ ProtoLog.v(WM_DEBUG_STATES,
+ "resumeTopActivity: Skip resume: some activity pausing.");
+ return false;
+ }
+
+ final TaskDisplayArea taskDisplayArea = getDisplayArea();
// If the top activity is the resumed one, nothing to do.
if (mResumedActivity == next && next.isState(RESUMED)
&& taskDisplayArea.allResumedActivitiesComplete()) {
@@ -1164,14 +1197,6 @@
return false;
}
- // If we are currently pausing an activity, then don't do anything until that is done.
- final boolean allPausedComplete = mRootWindowContainer.allPausedActivitiesComplete();
- if (!allPausedComplete) {
- ProtoLog.v(WM_DEBUG_STATES,
- "resumeTopActivity: Skip resume: some activity pausing.");
- return false;
- }
-
// If we are sleeping, and there is no resumed activity, and the top activity is paused,
// well that is the state we want.
if (mLastPausedActivity == next && shouldSleepOrShutDownActivities()) {
@@ -1787,6 +1812,51 @@
}
}
+ @ActivityInfo.ScreenOrientation
+ @Override
+ int getOrientation(@ActivityInfo.ScreenOrientation int candidate) {
+ if (shouldReportOrientationUnspecified()) {
+ return SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+ if (canSpecifyOrientation()) {
+ return super.getOrientation(candidate);
+ }
+ return SCREEN_ORIENTATION_UNSET;
+ }
+
+ /**
+ * Whether or not to allow this container to specify an app requested orientation.
+ *
+ * This is different from {@link #providesOrientation()} that
+ * 1. The container may still provide an orientation even if it can't specify the app requested
+ * one, such as {@link #shouldReportOrientationUnspecified()}
+ * 2. Even if the container can specify an app requested orientation, it may not be used by the
+ * parent container if it is {@link ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
+ */
+ boolean canSpecifyOrientation() {
+ final int windowingMode = getWindowingMode();
+ final int activityType = getActivityType();
+ return windowingMode == WINDOWING_MODE_FULLSCREEN
+ || activityType == ACTIVITY_TYPE_HOME
+ || activityType == ACTIVITY_TYPE_RECENTS
+ || activityType == ACTIVITY_TYPE_ASSISTANT;
+ }
+
+ /**
+ * Whether or not the parent container should use the orientation provided by this container
+ * even if it is {@link ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}.
+ */
+ @Override
+ boolean providesOrientation() {
+ return super.providesOrientation() || shouldReportOrientationUnspecified();
+ }
+
+ private boolean shouldReportOrientationUnspecified() {
+ // Apps and their containers are not allowed to specify orientation from adjacent
+ // TaskFragment.
+ return getAdjacentTaskFragment() != null && isVisibleRequested();
+ }
+
@Override
void forAllTaskFragments(Consumer<TaskFragment> callback, boolean traverseTopToBottom) {
super.forAllTaskFragments(callback, traverseTopToBottom);
@@ -1844,6 +1914,7 @@
ActivityRecord r = topRunningActivity();
mClearedTaskForReuse = false;
mClearedTaskFragmentForPip = false;
+ mClearedForReorderActivityToFront = false;
final ActivityRecord addingActivity = child.asActivityRecord();
final boolean isAddingActivity = addingActivity != null;
@@ -2273,35 +2344,41 @@
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
- // Task will animate differently.
- if (mTaskFragmentOrganizer != null) {
- mTmpPrevBounds.set(getBounds());
- }
-
super.onConfigurationChanged(newParentConfig);
- final boolean shouldStartChangeTransition = shouldStartChangeTransition(mTmpPrevBounds);
- if (shouldStartChangeTransition) {
- initializeChangeTransition(mTmpPrevBounds);
- }
if (mTaskFragmentOrganizer != null) {
- if (mTransitionController.isShellTransitionsEnabled()
- && !mTransitionController.isCollecting(this)) {
- // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
- // update the surface here if it is not collected by Shell transition.
- updateOrganizedTaskFragmentSurface();
- } else if (!mTransitionController.isShellTransitionsEnabled()
- && !shouldStartChangeTransition) {
- // Update the surface here instead of in the organizer so that we can make sure
- // it can be synced with the surface freezer for legacy app transition.
- updateOrganizedTaskFragmentSurface();
- }
+ updateOrganizedTaskFragmentSurface();
}
sendTaskFragmentInfoChanged();
}
+ void deferOrganizedTaskFragmentSurfaceUpdate() {
+ mDelayOrganizedTaskFragmentSurfaceUpdate = true;
+ }
+
+ void continueOrganizedTaskFragmentSurfaceUpdate() {
+ mDelayOrganizedTaskFragmentSurfaceUpdate = false;
+ updateOrganizedTaskFragmentSurface();
+ }
+
private void updateOrganizedTaskFragmentSurface() {
+ if (mDelayOrganizedTaskFragmentSurfaceUpdate) {
+ return;
+ }
+ if (mTransitionController.isShellTransitionsEnabled()
+ && !mTransitionController.isCollecting(this)) {
+ // TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
+ // update the surface here if it is not collected by Shell transition.
+ updateOrganizedTaskFragmentSurfaceUnchecked();
+ } else if (!mTransitionController.isShellTransitionsEnabled() && !isAnimating()) {
+ // Update the surface here instead of in the organizer so that we can make sure
+ // it can be synced with the surface freezer for legacy app transition.
+ updateOrganizedTaskFragmentSurfaceUnchecked();
+ }
+ }
+
+ private void updateOrganizedTaskFragmentSurfaceUnchecked() {
final SurfaceControl.Transaction t = getSyncTransaction();
updateSurfacePosition(t);
updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
@@ -2355,7 +2432,7 @@
}
/** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
- private boolean shouldStartChangeTransition(Rect startBounds) {
+ boolean shouldStartChangeTransition(Rect startBounds) {
if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
return false;
}
@@ -2375,7 +2452,7 @@
void setSurfaceControl(SurfaceControl sc) {
super.setSurfaceControl(sc);
if (mTaskFragmentOrganizer != null) {
- updateOrganizedTaskFragmentSurface();
+ updateOrganizedTaskFragmentSurfaceUnchecked();
// If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
// emit the callbacks now.
sendTaskFragmentAppeared();
@@ -2437,6 +2514,7 @@
positionInParent,
mClearedTaskForReuse,
mClearedTaskFragmentForPip,
+ mClearedForReorderActivityToFront,
calculateMinDimension());
}
@@ -2483,6 +2561,22 @@
return mTaskFragmentOrganizer != null;
}
+ /**
+ * Whether this is an embedded {@link TaskFragment} that does not fill the parent {@link Task}.
+ */
+ boolean isEmbeddedWithBoundsOverride() {
+ if (!mIsEmbedded) {
+ return false;
+ }
+ final Task task = getTask();
+ if (task == null) {
+ return false;
+ }
+ final Rect taskBounds = task.getBounds();
+ final Rect taskFragBounds = getBounds();
+ return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
+ }
+
/** Whether the Task should be visible. */
boolean isTaskVisibleRequested() {
final Task task = getTask();
@@ -2511,6 +2605,14 @@
return false;
}
+ @Override
+ boolean canCustomizeAppTransition() {
+ // This is only called when the app transition is going to be played by system server. In
+ // this case, we should allow custom app transition for fullscreen embedded TaskFragment
+ // just like Activity.
+ return isEmbedded() && matchParentBounds();
+ }
+
/** Clear {@link #mLastPausedActivity} for all {@link TaskFragment} children */
void clearLastPausedActivity() {
forAllTaskFragments(taskFragment -> taskFragment.mLastPausedActivity = null);
@@ -2715,6 +2817,16 @@
return callback.test(this) ? this : null;
}
+ /**
+ * Moves the passed child to front
+ * @return whether it was actually moved (vs already being top).
+ */
+ boolean moveChildToFront(WindowContainer newTop) {
+ int origDist = getDistanceFromTop(newTop);
+ positionChildAt(POSITION_TOP, newTop, false /* includeParents */);
+ return getDistanceFromTop(newTop) != origDist;
+ }
+
String toFullString() {
final StringBuilder sb = new StringBuilder(128);
sb.append(this);
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 509b1e6..6e4df79 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -34,8 +34,8 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Intent;
import android.content.res.Configuration;
-import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -132,12 +132,11 @@
new WeakHashMap<>();
/**
- * Map from Task Id to {@link RemoteAnimationDefinition}.
- * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations(int,
- * RemoteAnimationDefinition) )
+ * {@link RemoteAnimationDefinition} for embedded activities transition animation that is
+ * organized by this organizer.
*/
- private final SparseArray<RemoteAnimationDefinition> mRemoteAnimationDefinitions =
- new SparseArray<>();
+ @Nullable
+ private RemoteAnimationDefinition mRemoteAnimationDefinition;
/**
* Map from {@link TaskFragmentTransaction#getTransactionToken()} to the
@@ -322,9 +321,10 @@
+ " is not in a task belong to the organizer app.");
return null;
}
- if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
+ if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED
+ || !task.isAllowedToEmbedActivityInTrustedMode(activity, mOrganizerUid)) {
Slog.d(TAG, "Reparent activity=" + activity.token
- + " is not allowed to be embedded.");
+ + " is not allowed to be embedded in trusted mode.");
return null;
}
@@ -350,7 +350,7 @@
activity.token, task.mTaskId);
return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK)
.setTaskId(task.mTaskId)
- .setActivityIntent(activity.intent)
+ .setActivityIntent(trimIntent(activity.intent))
.setActivityToken(activityToken);
}
@@ -425,7 +425,7 @@
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Register task fragment organizer=%s uid=%d pid=%d",
organizer.asBinder(), uid, pid);
- if (mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
+ if (isOrganizerRegistered(organizer)) {
throw new IllegalStateException(
"Replacing existing organizer currently unsupported");
}
@@ -453,7 +453,7 @@
}
@Override
- public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId,
+ public void registerRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer,
@NonNull RemoteAnimationDefinition definition) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -466,20 +466,19 @@
if (organizerState == null) {
throw new IllegalStateException("The organizer hasn't been registered.");
}
- if (organizerState.mRemoteAnimationDefinitions.contains(taskId)) {
+ if (organizerState.mRemoteAnimationDefinition != null) {
throw new IllegalStateException(
"The organizer has already registered remote animations="
- + organizerState.mRemoteAnimationDefinitions.get(taskId)
- + " for TaskId=" + taskId);
+ + organizerState.mRemoteAnimationDefinition);
}
definition.setCallingPidUid(pid, uid);
- organizerState.mRemoteAnimationDefinitions.put(taskId, definition);
+ organizerState.mRemoteAnimationDefinition = definition;
}
}
@Override
- public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer, int taskId) {
+ public void unregisterRemoteAnimations(@NonNull ITaskFragmentOrganizer organizer) {
final int pid = Binder.getCallingPid();
final long uid = Binder.getCallingUid();
synchronized (mGlobalLock) {
@@ -493,7 +492,7 @@
return;
}
- organizerState.mRemoteAnimationDefinitions.remove(taskId);
+ organizerState.mRemoteAnimationDefinition = null;
}
}
@@ -503,10 +502,18 @@
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
- applyTransaction(wct, transitionType, shouldApplyIndependently);
- final TaskFragmentOrganizerState state = validateAndGetState(
- wct.getTaskFragmentOrganizer());
- state.onTransactionFinished(transactionToken);
+ if (isValidTransaction(wct)) {
+ applyTransaction(wct, transitionType, shouldApplyIndependently);
+ }
+ // Even if the transaction is empty, we still need to invoke #onTransactionFinished
+ // unless the organizer has been unregistered.
+ final ITaskFragmentOrganizer organizer = wct.getTaskFragmentOrganizer();
+ final TaskFragmentOrganizerState state = organizer != null
+ ? mTaskFragmentOrganizerState.get(organizer.asBinder())
+ : null;
+ if (state != null) {
+ state.onTransactionFinished(transactionToken);
+ }
}
}
@@ -515,7 +522,7 @@
@WindowManager.TransitionType int transitionType, boolean shouldApplyIndependently) {
// Keep the calling identity to avoid unsecure change.
synchronized (mGlobalLock) {
- if (wct.isEmpty()) {
+ if (!isValidTransaction(wct)) {
return;
}
mWindowOrganizerController.applyTaskFragmentTransactionLocked(wct, transitionType,
@@ -525,16 +532,16 @@
/**
* Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. Returns
- * {@code null} if it doesn't, or if the organizer has activity(ies) embedded in untrusted mode.
+ * {@code null} if it doesn't.
*/
@Nullable
public RemoteAnimationDefinition getRemoteAnimationDefinition(
- @NonNull ITaskFragmentOrganizer organizer, int taskId) {
+ @NonNull ITaskFragmentOrganizer organizer) {
synchronized (mGlobalLock) {
final TaskFragmentOrganizerState organizerState =
mTaskFragmentOrganizerState.get(organizer.asBinder());
return organizerState != null
- ? organizerState.mRemoteAnimationDefinitions.get(taskId)
+ ? organizerState.mRemoteAnimationDefinition
: null;
}
}
@@ -656,7 +663,7 @@
}
organizer = organizedTf[0].getTaskFragmentOrganizer();
}
- if (!mTaskFragmentOrganizerState.containsKey(organizer.asBinder())) {
+ if (!isOrganizerRegistered(organizer)) {
Slog.w(TAG, "The last TaskFragmentOrganizer no longer exists");
return;
}
@@ -702,7 +709,7 @@
mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).remove(event);
}
- boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
+ private boolean isOrganizerRegistered(@NonNull ITaskFragmentOrganizer organizer) {
return mTaskFragmentOrganizerState.containsKey(organizer.asBinder());
}
@@ -739,6 +746,20 @@
return state;
}
+ boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
+ if (t.isEmpty()) {
+ return false;
+ }
+ final ITaskFragmentOrganizer organizer = t.getTaskFragmentOrganizer();
+ if (t.getTaskFragmentOrganizer() == null || !isOrganizerRegistered(organizer)) {
+ // Transaction from an unregistered organizer should not be applied. This can happen
+ // when the organizer process died before the transaction is applied.
+ Slog.e(TAG, "Caller organizer=" + organizer + " is no longer registered");
+ return false;
+ }
+ return true;
+ }
+
/**
* A class to store {@link ITaskFragmentOrganizer} and its organized
* {@link TaskFragment TaskFragments} with different pending event request.
@@ -1083,16 +1104,18 @@
return false;
}
final TaskFragment taskFragment = activity.getOrganizedTaskFragment();
- if (taskFragment == null) {
- return false;
- }
- final Task parentTask = taskFragment.getTask();
- if (parentTask != null) {
- final Rect taskBounds = parentTask.getBounds();
- final Rect taskFragBounds = taskFragment.getBounds();
- return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
- }
- return false;
+ return taskFragment != null && taskFragment.isEmbeddedWithBoundsOverride();
}
}
+
+ /**
+ * Trims the given Intent to only those that are needed to for embedding rules. This helps to
+ * make it safer for cross-uid embedding even if we only send the Intent for trusted embedding.
+ */
+ private static Intent trimIntent(@NonNull Intent intent) {
+ return new Intent()
+ .setComponent(intent.getComponent())
+ .setPackage(intent.getPackage())
+ .setAction(intent.getAction());
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 8e98cb7..14a2d03 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -47,9 +47,9 @@
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.util.Size;
import android.util.Slog;
import android.view.Gravity;
-import android.view.View;
import android.window.WindowContainerToken;
import com.android.internal.annotations.VisibleForTesting;
@@ -66,10 +66,6 @@
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskLaunchParamsModifier" : TAG_ATM;
private static final boolean DEBUG = false;
- // Screen size of Nexus 5x
- private static final int DEFAULT_PORTRAIT_PHONE_WIDTH_DP = 412;
- private static final int DEFAULT_PORTRAIT_PHONE_HEIGHT_DP = 732;
-
// Allowance of size matching.
private static final int EPSILON = 2;
@@ -184,26 +180,34 @@
// is set with the suggestedDisplayArea. If it is set, but the eventual TaskDisplayArea is
// different, we should recalculating the bounds.
boolean hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = false;
- final boolean canApplyFreeformPolicy =
+ // Note that initial bounds needs to be set to fullscreen tasks too as it's used as restore
+ // bounds.
+ final boolean canCalculateBoundsForFullscreenTask =
+ canCalculateBoundsForFullscreenTask(suggestedDisplayArea, launchMode);
+ final boolean canApplyFreeformWindowPolicy =
canApplyFreeformWindowPolicy(suggestedDisplayArea, launchMode);
- if (mSupervisor.canUseActivityOptionsLaunchBounds(options)
- && (canApplyFreeformPolicy || canApplyPipWindowPolicy(launchMode))) {
+ final boolean canApplyWindowLayout = layout != null
+ && (canApplyFreeformWindowPolicy || canCalculateBoundsForFullscreenTask);
+ final boolean canApplyBoundsFromActivityOptions =
+ mSupervisor.canUseActivityOptionsLaunchBounds(options)
+ && (canApplyFreeformWindowPolicy
+ || canApplyPipWindowPolicy(launchMode)
+ || canCalculateBoundsForFullscreenTask);
+
+ if (canApplyBoundsFromActivityOptions) {
hasInitialBounds = true;
- launchMode = launchMode == WINDOWING_MODE_UNDEFINED
+ // |launchMode| at this point can be fullscreen, PIP, MultiWindow, etc. Only set
+ // freeform windowing mode if appropriate by checking |canApplyFreeformWindowPolicy|.
+ launchMode = launchMode == WINDOWING_MODE_UNDEFINED && canApplyFreeformWindowPolicy
? WINDOWING_MODE_FREEFORM
: launchMode;
outParams.mBounds.set(options.getLaunchBounds());
if (DEBUG) appendLog("activity-options-bounds=" + outParams.mBounds);
- } else if (launchMode == WINDOWING_MODE_PINNED) {
- // System controls PIP window's bounds, so don't apply launch bounds.
- if (DEBUG) appendLog("empty-window-layout-for-pip");
- } else if (launchMode == WINDOWING_MODE_FULLSCREEN) {
- if (DEBUG) appendLog("activity-options-fullscreen=" + outParams.mBounds);
- } else if (layout != null && canApplyFreeformPolicy) {
+ } else if (canApplyWindowLayout) {
mTmpBounds.set(currentParams.mBounds);
getLayoutBounds(suggestedDisplayArea, root, layout, mTmpBounds);
if (!mTmpBounds.isEmpty()) {
- launchMode = WINDOWING_MODE_FREEFORM;
+ launchMode = canApplyFreeformWindowPolicy ? WINDOWING_MODE_FREEFORM : launchMode;
outParams.mBounds.set(mTmpBounds);
hasInitialBounds = true;
hasInitialBoundsForSuggestedDisplayAreaInFreeformWindow = true;
@@ -213,6 +217,8 @@
}
} else if (launchMode == WINDOWING_MODE_MULTI_WINDOW
&& options != null && options.getLaunchBounds() != null) {
+ // TODO: Investigate whether we can migrate this clause to the
+ // |canApplyBoundsFromActivityOptions| case above.
outParams.mBounds.set(options.getLaunchBounds());
hasInitialBounds = true;
if (DEBUG) appendLog("multiwindow-activity-options-bounds=" + outParams.mBounds);
@@ -252,11 +258,9 @@
if (!currentParams.mBounds.isEmpty()) {
// Carry over bounds from callers regardless of launch mode because bounds is still
// used to restore last non-fullscreen bounds when launch mode is not freeform.
- // Therefore it's not a resolution step for non-freeform launch mode and only
- // consider it fully resolved only when launch mode is freeform.
outParams.mBounds.set(currentParams.mBounds);
+ fullyResolvedCurrentParam = true;
if (launchMode == WINDOWING_MODE_FREEFORM) {
- fullyResolvedCurrentParam = true;
if (DEBUG) appendLog("inherit-bounds=" + outParams.mBounds);
}
}
@@ -365,13 +369,13 @@
if (resolvedMode == WINDOWING_MODE_FREEFORM) {
// Make sure bounds are in the displayArea.
if (currentParams.mPreferredTaskDisplayArea != taskDisplayArea) {
- adjustBoundsToFitInDisplayArea(taskDisplayArea, outParams.mBounds);
+ adjustBoundsToFitInDisplayArea(taskDisplayArea, layout, outParams.mBounds);
}
// Even though we want to keep original bounds, we still don't want it to stomp on
// an existing task.
adjustBoundsToAvoidConflictInDisplayArea(taskDisplayArea, outParams.mBounds);
}
- } else if (taskDisplayArea.inFreeformWindowingMode()) {
+ } else {
if (source != null && source.inFreeformWindowingMode()
&& resolvedMode == WINDOWING_MODE_FREEFORM
&& outParams.mBounds.isEmpty()
@@ -548,10 +552,19 @@
return display.getDisplayId() == source.getDisplayId();
}
+ private boolean canCalculateBoundsForFullscreenTask(@NonNull TaskDisplayArea displayArea,
+ int launchMode) {
+ return mSupervisor.mService.mSupportsFreeformWindowManagement
+ && ((displayArea.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && launchMode == WINDOWING_MODE_UNDEFINED)
+ || launchMode == WINDOWING_MODE_FULLSCREEN);
+ }
+
private boolean canApplyFreeformWindowPolicy(@NonNull TaskDisplayArea suggestedDisplayArea,
int launchMode) {
return mSupervisor.mService.mSupportsFreeformWindowManagement
- && (suggestedDisplayArea.inFreeformWindowingMode()
+ && ((suggestedDisplayArea.inFreeformWindowingMode()
+ && launchMode == WINDOWING_MODE_UNDEFINED)
|| launchMode == WINDOWING_MODE_FREEFORM);
}
@@ -713,16 +726,10 @@
private void getTaskBounds(@NonNull ActivityRecord root, @NonNull TaskDisplayArea displayArea,
@NonNull ActivityInfo.WindowLayout layout, int resolvedMode, boolean hasInitialBounds,
@NonNull Rect inOutBounds) {
- if (resolvedMode == WINDOWING_MODE_FULLSCREEN) {
- // We don't handle letterboxing here. Letterboxing will be handled by valid checks
- // later.
- inOutBounds.setEmpty();
- if (DEBUG) appendLog("maximized-bounds");
- return;
- }
-
- if (resolvedMode != WINDOWING_MODE_FREEFORM) {
- // We don't apply freeform bounds adjustment to other windowing modes.
+ if (resolvedMode != WINDOWING_MODE_FREEFORM
+ && resolvedMode != WINDOWING_MODE_FULLSCREEN) {
+ // This function should be used only for freeform bounds adjustment. Freeform bounds
+ // needs to be set to fullscreen tasks too as restore bounds.
if (DEBUG) {
appendLog("skip-bounds-" + WindowConfiguration.windowingModeToString(resolvedMode));
}
@@ -738,7 +745,10 @@
}
// First we get the default size we want.
- getDefaultFreeformSize(root, displayArea, layout, orientation, mTmpBounds);
+ displayArea.getStableRect(mTmpStableBounds);
+ final Size defaultSize = LaunchParamsUtil.getDefaultFreeformSize(root, displayArea,
+ layout, orientation, mTmpStableBounds);
+ mTmpBounds.set(0, 0, defaultSize.getWidth(), defaultSize.getHeight());
if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) {
// We're here because either input parameters specified initial bounds, or the suggested
// bounds have the same size of the default freeform size. We should use the suggested
@@ -748,8 +758,8 @@
if (DEBUG) appendLog("freeform-size-orientation-match=" + inOutBounds);
} else {
// Meh, orientation doesn't match. Let's rotate inOutBounds in-place.
- centerBounds(displayArea, inOutBounds.height(), inOutBounds.width(),
- inOutBounds);
+ LaunchParamsUtil.centerBounds(displayArea, inOutBounds.height(),
+ inOutBounds.width(), inOutBounds);
if (DEBUG) appendLog("freeform-orientation-mismatch=" + inOutBounds);
}
} else {
@@ -758,9 +768,10 @@
// to the center of suggested bounds (or the displayArea if no suggested bounds). The
// default size might be too big to center to source activity bounds in displayArea, so
// we may need to move it back to the displayArea.
- centerBounds(displayArea, mTmpBounds.width(), mTmpBounds.height(),
+ adjustBoundsToFitInDisplayArea(displayArea, layout, mTmpBounds);
+ inOutBounds.setEmpty();
+ LaunchParamsUtil.centerBounds(displayArea, mTmpBounds.width(), mTmpBounds.height(),
inOutBounds);
- adjustBoundsToFitInDisplayArea(displayArea, inOutBounds);
if (DEBUG) appendLog("freeform-size-mismatch=" + inOutBounds);
}
@@ -806,130 +817,13 @@
return orientation;
}
- private void getDefaultFreeformSize(@NonNull ActivityRecord activityRecord,
- @NonNull TaskDisplayArea displayArea,
- @NonNull ActivityInfo.WindowLayout layout, int orientation, @NonNull Rect bounds) {
- // Default size, which is letterboxing/pillarboxing in displayArea. That's to say the large
- // dimension of default size is the small dimension of displayArea size, and the small
- // dimension of default size is calculated to keep the same aspect ratio as the
- // displayArea's. Here we use stable bounds of displayArea because that indicates the area
- // that isn't occupied by system widgets (e.g. sysbar and navbar).
- final ActivityInfo info = activityRecord.info;
- final Rect stableBounds = mTmpStableBounds;
- displayArea.getStableRect(stableBounds);
- final int portraitHeight = Math.min(stableBounds.width(), stableBounds.height());
- final int otherDimension = Math.max(stableBounds.width(), stableBounds.height());
- final int portraitWidth = (portraitHeight * portraitHeight) / otherDimension;
- final int defaultWidth = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? portraitHeight
- : portraitWidth;
- final int defaultHeight = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? portraitWidth
- : portraitHeight;
-
- // Get window size based on Nexus 5x screen, we assume that this is enough to show content
- // of activities.
- final float density = (float) displayArea.getConfiguration().densityDpi / DENSITY_DEFAULT;
- final int phonePortraitWidth = (int) (DEFAULT_PORTRAIT_PHONE_WIDTH_DP * density + 0.5f);
- final int phonePortraitHeight = (int) (DEFAULT_PORTRAIT_PHONE_HEIGHT_DP * density + 0.5f);
- final int phoneWidth = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? phonePortraitHeight
- : phonePortraitWidth;
- final int phoneHeight = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? phonePortraitWidth
- : phonePortraitHeight;
-
- // Minimum layout requirements.
- final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth;
- final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight;
-
- // Aspect ratio requirements.
- final float minAspectRatio = activityRecord.getMinAspectRatio();
- final float maxAspectRatio = info.getMaxAspectRatio();
-
- final int width = Math.min(defaultWidth, Math.max(phoneWidth, layoutMinWidth));
- final int height = Math.min(defaultHeight, Math.max(phoneHeight, layoutMinHeight));
- final float aspectRatio = (float) Math.max(width, height) / (float) Math.min(width, height);
-
- // Adjust the width and height to the aspect ratio requirements.
- int adjWidth = width;
- int adjHeight = height;
- if (minAspectRatio >= 1 && aspectRatio < minAspectRatio) {
- // The aspect ratio is below the minimum, adjust it to the minimum.
- if (orientation == SCREEN_ORIENTATION_LANDSCAPE) {
- // Fix the width, scale the height.
- adjHeight = (int) (adjWidth / minAspectRatio + 0.5f);
- } else {
- // Fix the height, scale the width.
- adjWidth = (int) (adjHeight / minAspectRatio + 0.5f);
- }
- } else if (maxAspectRatio >= 1 && aspectRatio > maxAspectRatio) {
- // The aspect ratio exceeds the maximum, adjust it to the maximum.
- if (orientation == SCREEN_ORIENTATION_LANDSCAPE) {
- // Fix the width, scale the height.
- adjHeight = (int) (adjWidth / maxAspectRatio + 0.5f);
- } else {
- // Fix the height, scale the width.
- adjWidth = (int) (adjHeight / maxAspectRatio + 0.5f);
- }
- }
-
- bounds.set(0, 0, adjWidth, adjHeight);
- bounds.offset(stableBounds.left, stableBounds.top);
- }
-
- /**
- * Gets centered bounds of width x height. If inOutBounds is not empty, the result bounds
- * centers at its center or displayArea's app bounds center if inOutBounds is empty.
- */
- private void centerBounds(@NonNull TaskDisplayArea displayArea, int width, int height,
- @NonNull Rect inOutBounds) {
- if (inOutBounds.isEmpty()) {
- displayArea.getStableRect(inOutBounds);
- }
- final int left = inOutBounds.centerX() - width / 2;
- final int top = inOutBounds.centerY() - height / 2;
- inOutBounds.set(left, top, left + width, top + height);
- }
-
private void adjustBoundsToFitInDisplayArea(@NonNull TaskDisplayArea displayArea,
- @NonNull Rect inOutBounds) {
- final Rect stableBounds = mTmpStableBounds;
- displayArea.getStableRect(stableBounds);
-
- if (stableBounds.width() < inOutBounds.width()
- || stableBounds.height() < inOutBounds.height()) {
- // There is no way for us to fit the bounds in the displayArea without changing width
- // or height. Just move the start to align with the displayArea.
- final int layoutDirection =
- mSupervisor.mRootWindowContainer.getConfiguration().getLayoutDirection();
- final int left = layoutDirection == View.LAYOUT_DIRECTION_RTL
- ? stableBounds.right - inOutBounds.right + inOutBounds.left
- : stableBounds.left;
- inOutBounds.offsetTo(left, stableBounds.top);
- return;
- }
-
- final int dx;
- if (inOutBounds.right > stableBounds.right) {
- // Right edge is out of displayArea.
- dx = stableBounds.right - inOutBounds.right;
- } else if (inOutBounds.left < stableBounds.left) {
- // Left edge is out of displayArea.
- dx = stableBounds.left - inOutBounds.left;
- } else {
- // Vertical edges are all in displayArea.
- dx = 0;
- }
-
- final int dy;
- if (inOutBounds.top < stableBounds.top) {
- // Top edge is out of displayArea.
- dy = stableBounds.top - inOutBounds.top;
- } else if (inOutBounds.bottom > stableBounds.bottom) {
- // Bottom edge is out of displayArea.
- dy = stableBounds.bottom - inOutBounds.bottom;
- } else {
- // Horizontal edges are all in displayArea.
- dy = 0;
- }
- inOutBounds.offset(dx, dy);
+ @NonNull ActivityInfo.WindowLayout layout,
+ @NonNull Rect inOutBounds) {
+ final int layoutDirection = mSupervisor.mRootWindowContainer.getConfiguration()
+ .getLayoutDirection();
+ LaunchParamsUtil.adjustBoundsToFitInDisplayArea(displayArea, layoutDirection, layout,
+ inOutBounds);
}
/**
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 4d29c4d..ec3962c 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -98,6 +98,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -107,7 +108,7 @@
* Represents a logical transition.
* @see TransitionController
*/
-class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
+class Transition implements BLASTSyncEngine.TransactionReadyListener {
private static final String TAG = "Transition";
private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
@@ -158,6 +159,7 @@
private @TransitionFlags int mFlags;
private final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
+ private final Token mToken;
private RemoteTransition mRemoteTransition = null;
/** Only use for clean-up after binder death! */
@@ -165,9 +167,9 @@
private SurfaceControl.Transaction mFinishTransaction = null;
/**
- * Contains change infos for both participants and all ancestors. We have to track ancestors
- * because they are all promotion candidates and thus we need their start-states
- * to be captured.
+ * Contains change infos for both participants and all remote-animatable ancestors. The
+ * ancestors can be the promotion candidates so their start-states need to be captured.
+ * @see #getAnimatableParent
*/
final ArrayMap<WindowContainer, ChangeInfo> mChanges = new ArrayMap<>();
@@ -220,10 +222,27 @@
mFlags = flags;
mController = controller;
mSyncEngine = syncEngine;
+ mToken = new Token(this);
controller.mTransitionTracer.logState(this);
}
+ @Nullable
+ static Transition fromBinder(@Nullable IBinder token) {
+ if (token == null) return null;
+ try {
+ return ((Token) token).mTransition.get();
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Invalid transition token: " + token, e);
+ return null;
+ }
+ }
+
+ @NonNull
+ IBinder getToken() {
+ return mToken;
+ }
+
void addFlag(int flag) {
mFlags |= flag;
}
@@ -345,7 +364,7 @@
return mFinishTransaction;
}
- private boolean isCollecting() {
+ boolean isCollecting() {
return mState == STATE_COLLECTING || mState == STATE_STARTED;
}
@@ -399,8 +418,9 @@
mSyncId, wc);
// "snapshot" all parents (as potential promotion targets). Do this before checking
// if this is already a participant in case it has since been re-parented.
- for (WindowContainer curr = wc.getParent(); curr != null && !mChanges.containsKey(curr);
- curr = curr.getParent()) {
+ for (WindowContainer<?> curr = getAnimatableParent(wc);
+ curr != null && !mChanges.containsKey(curr);
+ curr = getAnimatableParent(curr)) {
mChanges.put(curr, new ChangeInfo(curr));
if (isReadyGroup(curr)) {
mReadyTracker.addGroup(curr);
@@ -733,6 +753,11 @@
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
System.identityHashCode(this));
}
+ // Close the transactions now. They were originally copied to Shell in case we needed to
+ // apply them due to a remote failure. Since we don't need to apply them anymore, free them
+ // immediately.
+ if (mStartTransaction != null) mStartTransaction.close();
+ if (mFinishTransaction != null) mFinishTransaction.close();
mStartTransaction = mFinishTransaction = null;
if (mState < STATE_PLAYING) {
throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
@@ -874,6 +899,7 @@
mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
+ cleanUpInternal();
}
void abort() {
@@ -916,15 +942,9 @@
dc.getPendingTransaction().merge(transaction);
mSyncId = -1;
mOverrideOptions = null;
+ cleanUpInternal();
return;
}
- // Ensure that wallpaper visibility is updated with the latest wallpaper target.
- for (int i = mParticipants.size() - 1; i >= 0; --i) {
- final WindowContainer<?> wc = mParticipants.valueAt(i);
- if (isWallpaper(wc) && wc.getDisplayContent() != null) {
- wc.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
- }
- }
mState = STATE_PLAYING;
mStartTransaction = transaction;
@@ -1034,7 +1054,9 @@
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Calling onTransitionReady: %s", info);
mController.getTransitionPlayer().onTransitionReady(
- this, info, transaction, mFinishTransaction);
+ mToken, info, transaction, mFinishTransaction);
+ // Since we created root-leash but no longer reference it from core, release it now
+ info.releaseAnimSurfaces();
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
System.identityHashCode(this));
@@ -1067,7 +1089,17 @@
if (mFinishTransaction != null) {
mFinishTransaction.apply();
}
- mController.finishTransition(this);
+ mController.finishTransition(mToken);
+ }
+
+ private void cleanUpInternal() {
+ // Clean-up any native references.
+ for (int i = 0; i < mChanges.size(); ++i) {
+ final ChangeInfo ci = mChanges.valueAt(i);
+ if (ci.mSnapshot != null) {
+ ci.mSnapshot.release();
+ }
+ }
}
/** @see RecentsAnimationController#attachNavigationBarToApp */
@@ -1269,6 +1301,16 @@
return sb.toString();
}
+ /** Returns the parent that the remote animator can animate or control. */
+ private static WindowContainer<?> getAnimatableParent(WindowContainer<?> wc) {
+ WindowContainer<?> parent = wc.getParent();
+ while (parent != null
+ && (!parent.canCreateRemoteAnimationTarget() && !parent.isOrganized())) {
+ parent = parent.getParent();
+ }
+ return parent;
+ }
+
private static boolean reportIfNotTop(WindowContainer wc) {
// Organized tasks need to be reported anyways because Core won't show() their surfaces
// and we can't rely on onTaskAppeared because it isn't in sync.
@@ -1492,7 +1534,8 @@
intermediates.clear();
boolean foundParentInTargets = false;
// Collect the intermediate parents between target and top changed parent.
- for (WindowContainer<?> p = wc.getParent(); p != null; p = p.getParent()) {
+ for (WindowContainer<?> p = getAnimatableParent(wc); p != null;
+ p = getAnimatableParent(p)) {
final ChangeInfo parentChange = changes.get(p);
if (parentChange == null || !parentChange.hasChanged(p)) break;
if (p.mRemoteToken == null) {
@@ -1850,10 +1893,6 @@
return isCollecting() && mSyncId >= 0;
}
- static Transition fromBinder(IBinder binder) {
- return (Transition) binder;
- }
-
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
@@ -2345,4 +2384,18 @@
}
}
}
+
+ private static class Token extends Binder {
+ final WeakReference<Transition> mTransition;
+
+ Token(Transition transition) {
+ mTransition = new WeakReference<>(transition);
+ }
+
+ @Override
+ public String toString() {
+ return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
+ + mTransition.get() + "}";
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index e4d39b9..d3d1c16 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -458,8 +458,9 @@
info = new ActivityManager.RunningTaskInfo();
startTask.fillTaskInfo(info);
}
- mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo(
- transition.mType, info, remoteTransition, displayChange));
+ mTransitionPlayer.requestStartTransition(transition.getToken(),
+ new TransitionRequestInfo(transition.mType, info, remoteTransition,
+ displayChange));
transition.setRemoteTransition(remoteTransition);
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting transition", e);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 908fdbd..920b1ba 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -113,12 +113,6 @@
private boolean mShouldUpdateZoom;
- /**
- * Temporary storage for taking a screenshot of the wallpaper.
- * @see #screenshotWallpaperLocked()
- */
- private WindowState mTmpTopWallpaper;
-
@Nullable private Point mLargestDisplaySize = null;
private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult();
@@ -962,21 +956,16 @@
}
WindowState getTopVisibleWallpaper() {
- mTmpTopWallpaper = null;
-
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
- token.forAllWindows(w -> {
- final WindowStateAnimator winAnim = w.mWinAnimator;
- if (winAnim != null && winAnim.getShown() && winAnim.mLastAlpha > 0f) {
- mTmpTopWallpaper = w;
- return true;
+ for (int i = token.getChildCount() - 1; i >= 0; i--) {
+ final WindowState w = token.getChildAt(i);
+ if (w.mWinAnimator.getShown() && w.mWinAnimator.mLastAlpha > 0f) {
+ return w;
}
- return false;
- }, true /* traverseTopToBottom */);
+ }
}
-
- return mTmpTopWallpaper;
+ return null;
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fa1bc54..80357eb 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2995,10 +2995,9 @@
// screen empty. Show background color to cover that.
showBackdrop = getDisplayContent().mChangingContainers.size() > 1;
} else {
- // Check whether or not to show backdrop for open/close transition.
- final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
- final Animation a = animAttr != 0
- ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
+ // Check whether the app has requested to show backdrop for open/close
+ // transition.
+ final Animation a = appTransition.getNextAppRequestedAnimation(enter);
showBackdrop = a != null && a.getShowBackdrop();
}
backdropColor = appTransition.getNextAppTransitionBackgroundColor();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9db5170..68b853d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2587,6 +2587,12 @@
&& win.mSyncSeqId > lastSyncSeqId) {
maybeSyncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1;
win.markRedrawForSyncReported();
+ if (win.mSyncState == WindowContainer.SYNC_STATE_WAITING_FOR_DRAW
+ && winAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN
+ && maybeSyncSeqId < 0) {
+ // Do not wait for a drawn window which won't report draw.
+ win.onSyncFinishedDrawing();
+ }
} else {
maybeSyncSeqId = -1;
}
@@ -8872,14 +8878,14 @@
}
@Override
- public List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName) {
+ public List<DisplayInfo> getPossibleDisplayInfo(int displayId) {
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- if (packageName == null || !isRecentsComponent(packageName, callingUid)) {
- Slog.e(TAG, "Unable to verify uid for package " + packageName
- + " for getPossibleMaximumWindowMetrics");
+ if (!mAtmService.isCallerRecents(callingUid)) {
+ Slog.e(TAG, "Unable to verify uid for getPossibleDisplayInfo"
+ + " on uid " + callingUid);
return new ArrayList<>();
}
@@ -8897,31 +8903,6 @@
return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId);
}
- /**
- * Returns {@code true} when the calling package is the recents component.
- */
- boolean isRecentsComponent(@NonNull String callingPackageName, int callingUid) {
- String recentsPackage;
- try {
- String recentsComponent = mContext.getResources().getString(
- R.string.config_recentsComponentName);
- if (recentsComponent == null) {
- return false;
- }
- recentsPackage = ComponentName.unflattenFromString(recentsComponent).getPackageName();
- } catch (Resources.NotFoundException e) {
- Slog.e(TAG, "Unable to verify if recents component", e);
- return false;
- }
- try {
- return callingUid == mContext.getPackageManager().getPackageUid(callingPackageName, 0)
- && callingPackageName.equals(recentsPackage);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Unable to verify if recents component", e);
- return false;
- }
- }
-
void grantEmbeddedWindowFocus(Session session, IBinder focusToken, boolean grantFocus) {
synchronized (mGlobalLock) {
final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 32a110e..d85bd83 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -39,6 +39,7 @@
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
@@ -48,6 +49,7 @@
import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -146,6 +148,8 @@
@VisibleForTesting
final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>();
+ private final Rect mTmpBounds = new Rect();
+
WindowOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
mGlobalLock = atm.mGlobalLock;
@@ -303,7 +307,7 @@
nextTransition.setAllReady();
}
});
- return nextTransition;
+ return nextTransition.getToken();
}
transition = mTransitionController.createTransition(type);
}
@@ -312,7 +316,7 @@
if (needsSetReady) {
transition.setAllReady();
}
- return transition;
+ return transition.getToken();
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -399,9 +403,6 @@
*/
void applyTaskFragmentTransactionLocked(@NonNull WindowContainerTransaction wct,
@WindowManager.TransitionType int type, boolean shouldApplyIndependently) {
- if (!isValidTransaction(wct)) {
- return;
- }
enforceTaskFragmentOrganizerPermission("applyTaskFragmentTransaction()",
Objects.requireNonNull(wct.getTaskFragmentOrganizer()),
Objects.requireNonNull(wct));
@@ -455,7 +456,7 @@
// calls startSyncSet.
() -> mTransitionController.moveToCollecting(nextTransition),
() -> {
- if (isValidTransaction(wct)) {
+ if (mTaskFragmentOrganizerController.isValidTransaction(wct)) {
applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
mTransitionController.requestStartTransition(nextTransition,
null /* startTask */, null /* remoteTransition */,
@@ -710,7 +711,7 @@
}
private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
- int effects = 0;
+ int effects = applyChanges(tr, c, null /* errorCallbackToken */);
final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
@@ -725,6 +726,10 @@
effects = TRANSACT_EFFECTS_LIFECYCLE;
}
+ if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
+ tr.setDragResizing(c.getDragResizing(), DRAG_RESIZE_MODE_FREEFORM);
+ }
+
final int childWindowingMode = c.getActivityWindowingMode();
if (childWindowingMode > -1) {
tr.setActivityWindowingMode(childWindowingMode);
@@ -767,6 +772,7 @@
private int applyDisplayAreaChanges(DisplayArea displayArea,
WindowContainerTransaction.Change c) {
final int[] effects = new int[1];
+ effects[0] = applyChanges(displayArea, c, null /* errorCallbackToken */);
if ((c.getChangeMask()
& WindowContainerTransaction.Change.CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
@@ -787,6 +793,27 @@
return effects[0];
}
+ private int applyTaskFragmentChanges(@NonNull TaskFragment taskFragment,
+ @NonNull WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
+ if (taskFragment.isEmbeddedTaskFragmentInPip()) {
+ // No override from organizer for embedded TaskFragment in a PIP Task.
+ return 0;
+ }
+
+ // When the TaskFragment is resized, we may want to create a change transition for it, for
+ // which we want to defer the surface update until we determine whether or not to start
+ // change transition.
+ mTmpBounds.set(taskFragment.getBounds());
+ taskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
+ final int effects = applyChanges(taskFragment, c, errorCallbackToken);
+ if (taskFragment.shouldStartChangeTransition(mTmpBounds)) {
+ taskFragment.initializeChangeTransition(mTmpBounds);
+ }
+ taskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
+ mTmpBounds.set(0, 0, 0, 0);
+ return effects;
+ }
+
private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,
int syncId, @Nullable Transition transition, boolean isInLockTaskMode,
@NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,
@@ -975,6 +1002,14 @@
tf1.setAdjacentTaskFragment(tf2);
effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ // Clear the focused app if the focused app is no longer visible after reset the
+ // adjacent TaskFragments.
+ if (tf2 == null && tf1.getDisplayContent().mFocusedApp != null
+ && tf1.hasChild(tf1.getDisplayContent().mFocusedApp)
+ && !tf1.shouldBeVisible(null /* starting */)) {
+ tf1.getDisplayContent().setFocusedApp(null);
+ }
+
final Bundle bundle = hop.getLaunchOptions();
final WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams =
bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentParams(
@@ -1088,6 +1123,22 @@
effects |= sanitizeAndApplyHierarchyOp(wc, hop);
break;
}
+ case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT: {
+ final IBinder fragmentToken = hop.getContainer();
+ final IBinder companionToken = hop.getCompanionContainer();
+ final TaskFragment fragment = mLaunchTaskFragments.get(fragmentToken);
+ final TaskFragment companion = companionToken != null ? mLaunchTaskFragments.get(
+ companionToken) : null;
+ if (fragment == null || !fragment.isAttached()) {
+ final Throwable exception = new IllegalArgumentException(
+ "Not allowed to set companion on invalid fragment tokens");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, fragment, type,
+ exception);
+ break;
+ }
+ fragment.setCompanionTaskFragment(companion);
+ break;
+ }
default: {
// The other operations may change task order so they are skipped while in lock
// task mode. The above operations are still allowed because they don't move
@@ -1452,20 +1503,15 @@
private int applyWindowContainerChange(WindowContainer wc,
WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
sanitizeWindowContainer(wc);
- if (wc.asTaskFragment() != null && wc.asTaskFragment().isEmbeddedTaskFragmentInPip()) {
- // No override from organizer for embedded TaskFragment in a PIP Task.
- return 0;
+ if (wc.asDisplayArea() != null) {
+ return applyDisplayAreaChanges(wc.asDisplayArea(), c);
+ } else if (wc.asTask() != null) {
+ return applyTaskChanges(wc.asTask(), c);
+ } else if (wc.asTaskFragment() != null) {
+ return applyTaskFragmentChanges(wc.asTaskFragment(), c, errorCallbackToken);
+ } else {
+ return applyChanges(wc, c, errorCallbackToken);
}
-
- int effects = applyChanges(wc, c, errorCallbackToken);
-
- if (wc instanceof DisplayArea) {
- effects |= applyDisplayAreaChanges(wc.asDisplayArea(), c);
- } else if (wc instanceof Task) {
- effects |= applyTaskChanges(wc.asTask(), c);
- }
-
- return effects;
}
@Override
@@ -1571,18 +1617,6 @@
return (cfgChanges & CONTROLLABLE_CONFIGS) == 0;
}
- private boolean isValidTransaction(@NonNull WindowContainerTransaction t) {
- if (t.getTaskFragmentOrganizer() != null && !mTaskFragmentOrganizerController
- .isOrganizerRegistered(t.getTaskFragmentOrganizer())) {
- // Transaction from an unregistered organizer should not be applied. This can happen
- // when the organizer process died before the transaction is applied.
- Slog.e(TAG, "Caller organizer=" + t.getTaskFragmentOrganizer()
- + " is no longer registered");
- return false;
- }
- return true;
- }
-
/**
* Makes sure that the transaction only contains operations that are allowed for the
* {@link WindowContainerTransaction#getTaskFragmentOrganizer()}.
@@ -1630,6 +1664,12 @@
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
enforceTaskFragmentOrganized(func, hop.getNewParent(), organizer);
break;
+ case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT:
+ enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
+ if (hop.getCompanionContainer() != null) {
+ enforceTaskFragmentOrganized(func, hop.getCompanionContainer(), organizer);
+ }
+ break;
case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
if (hop.getAdjacentRoot() != null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5398969..45606f9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1537,10 +1537,11 @@
mWmService.makeWindowFreezingScreenIfNeededLocked(this);
// If the orientation is changing, or we're starting or ending a drag resizing action,
- // then we need to hold off on unfreezing the display until this window has been
- // redrawn; to do that, we need to go through the process of getting informed by the
- // application when it has finished drawing.
- if (getOrientationChanging() || dragResizingChanged) {
+ // or we're resizing an embedded Activity, then we need to hold off on unfreezing the
+ // display until this window has been redrawn; to do that, we need to go through the
+ // process of getting informed by the application when it has finished drawing.
+ if (getOrientationChanging() || dragResizingChanged
+ || isEmbeddedActivityResizeChanged()) {
if (dragResizingChanged) {
ProtoLog.v(WM_DEBUG_RESIZE,
"Resize start waiting for draw, "
@@ -4147,6 +4148,20 @@
return mActivityRecord == null || mActivityRecord.isFullyTransparentBarAllowed(frame);
}
+ /**
+ * Whether this window belongs to a resizing embedded activity.
+ */
+ private boolean isEmbeddedActivityResizeChanged() {
+ if (mActivityRecord == null || !isVisibleRequested()) {
+ // No need to update if the window is in the background.
+ return false;
+ }
+
+ final TaskFragment embeddedTaskFragment = mActivityRecord.getOrganizedTaskFragment();
+ return embeddedTaskFragment != null
+ && mDisplayContent.mChangingContainers.contains(embeddedTaskFragment);
+ }
+
boolean isDragResizeChanged() {
return mDragResizing != computeDragResizing();
}
@@ -6024,7 +6039,7 @@
final long duration =
SystemClock.elapsedRealtime() - mActivityRecord.mRelaunchStartTime;
Slog.i(TAG, "finishDrawing of relaunch: " + this + " " + duration + "ms");
- mActivityRecord.mRelaunchStartTime = 0;
+ mActivityRecord.finishOrAbortReplacingWindow();
}
if (mActivityRecord != null && mAttrs.type == TYPE_APPLICATION_STARTING) {
mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger()
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index cfb8014..fa4a9de 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -1490,6 +1490,39 @@
}
@Test
+ public void testSwitchUser() {
+ mockManageUsersGranted();
+ mockModifyGameModeGranted();
+
+ mockDeviceConfigBattery();
+ final Context context = InstrumentationRegistry.getContext();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext,
+ mTestLooper.getLooper(), context.getFilesDir());
+ startUser(gameManagerService, USER_ID_1);
+ startUser(gameManagerService, USER_ID_2);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+ checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+ GameManager.GAME_MODE_BATTERY);
+ assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
+ GameManager.GAME_MODE_BATTERY);
+
+ mockDeviceConfigAll();
+ switchUser(gameManagerService, USER_ID_1, USER_ID_2);
+ assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_2),
+ GameManager.GAME_MODE_STANDARD);
+ checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+ GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_2);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+
+ switchUser(gameManagerService, USER_ID_2, USER_ID_1);
+ checkReportedModes(gameManagerService, GameManager.GAME_MODE_STANDARD,
+ GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_2);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+ }
+
+ @Test
public void testResetInterventions_onDeviceConfigReset() throws Exception {
mockModifyGameModeGranted();
String configStringBefore =
diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
index c325778..ee09074 100644
--- a/services/tests/servicestests/src/com/android/server/DockObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.os.Looper;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
@@ -74,6 +75,11 @@
.isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED);
}
+ void setDeviceProvisioned(boolean provisioned) {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+ provisioned ? 1 : 0);
+ }
+
@Before
public void setUp() {
if (Looper.myLooper() == null) {
@@ -131,4 +137,25 @@
assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5",
Intent.EXTRA_DOCK_STATE_HE_DESK);
}
+
+ @Test
+ public void testDockIntentBroadcast_deviceNotProvisioned()
+ throws ExecutionException, InterruptedException {
+ DockObserver observer = new DockObserver(mInterceptingContext);
+ // Set the device as not provisioned.
+ setDeviceProvisioned(false);
+ observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ updateExtconDockState(observer, "DOCK=1");
+ TestableLooper.get(this).processAllMessages();
+ // Verify no broadcast was sent as device was not provisioned.
+ futureIntent.assertNotReceived();
+
+ // Ensure we send the broadcast when the device is provisioned.
+ setDeviceProvisioned(true);
+ TestableLooper.get(this).processAllMessages();
+ assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+ .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 7acb6d6..64af296 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -27,18 +27,18 @@
import android.media.AudioSystem;
import android.media.VolumeInfo;
import android.os.test.TestLooper;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import junit.framework.Assert;
+
import org.junit.Before;
import org.junit.Test;
public class AudioDeviceVolumeManagerTest {
private static final String TAG = "AudioDeviceVolumeManagerTest";
- private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
- AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
-
private Context mContext;
private String mPackageName;
private AudioSystemAdapter mSpyAudioSystem;
@@ -84,14 +84,20 @@
final AudioDeviceAttributes usbDevice = new AudioDeviceAttributes(
/*native type*/ AudioSystem.DEVICE_OUT_USB_DEVICE, /*address*/ "bla");
- mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName, TAG);
+ mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName);
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
- mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName, TAG);
+ mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName);
mTestLooper.dispatchAll();
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
+
+ final VolumeInfo vi = mAudioService.getDeviceVolume(volMin, usbDevice, mPackageName);
+ Assert.assertEquals("getDeviceVolume doesn't return expected value in " + vi
+ + " after setting " + volMid,
+ (volMid.getMaxVolumeIndex() - volMid.getMinVolumeIndex()) / 2,
+ (vi.getMaxVolumeIndex() - vi.getMinVolumeIndex()) / 2);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index 0cff4f1..bb00634 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -125,12 +125,9 @@
mProbe.destroy();
mProbe.enable();
- AtomicInteger lux = new AtomicInteger(10);
- mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
-
verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
verifyNoMoreInteractions(mSensorManager);
- assertThat(lux.get()).isLessThan(0);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0);
}
@Test
@@ -323,15 +320,27 @@
}
@Test
- public void testNoNextLuxWhenDestroyed() {
+ public void testDestroyAllowsAwaitLuxExactlyOnce() {
+ final float lastValue = 5.5f;
mProbe.destroy();
- AtomicInteger lux = new AtomicInteger(-20);
+ AtomicInteger lux = new AtomicInteger(10);
mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
- assertThat(lux.get()).isEqualTo(-1);
- verify(mSensorManager, never()).registerListener(
+ verify(mSensorManager).registerListener(
mSensorEventListenerCaptor.capture(), any(), anyInt());
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{lastValue}));
+
+ assertThat(lux.get()).isEqualTo(Math.round(lastValue));
+ verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
+
+ lux.set(22);
+ mProbe.enable();
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+ mProbe.enable();
+
+ assertThat(lux.get()).isEqualTo(Math.round(lastValue));
verifyNoMoreInteractions(mSensorManager);
}
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 18dd264..fb0cdfa 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -21,6 +21,7 @@
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
@@ -31,6 +32,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -48,6 +51,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
@@ -76,6 +80,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.test.FakeSettingsProvider;
@@ -1855,16 +1860,83 @@
@Test
public void testNotifyDefaultDisplayDeviceUpdated() {
- DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class);
- when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{});
- when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{});
- when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{});
- when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{});
+ Resources resources = mock(Resources.class);
+ when(mContext.getResources()).thenReturn(resources);
+ when(resources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+ .thenReturn(75);
+ when(resources.getInteger(R.integer.config_defaultRefreshRate))
+ .thenReturn(45);
+ when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{5});
+ when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{10});
+ when(
+ resources.getIntArray(R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{250});
+ when(
+ resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{7000});
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[]{60.0f, 90.0f}, 0);
+ // 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
+ // Verify all the loaded values are from DisplayDeviceConfig
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75,
+ 0.0);
+ assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+ new int[]{250});
+ assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+ new int[]{7000});
+ assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+ new int[]{5});
+ 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.getDefaultRefreshRate()).thenReturn(50);
+ when(displayDeviceConfig.getDefaultPeakRefreshRate()).thenReturn(55);
+ when(displayDeviceConfig.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
+ when(displayDeviceConfig.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
+ when(displayDeviceConfig.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
+ when(displayDeviceConfig.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
director.defaultDisplayDeviceUpdated(displayDeviceConfig);
- verify(displayDeviceConfig).getDefaultRefreshRate();
- verify(displayDeviceConfig).getDefaultPeakRefreshRate();
+
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 55,
+ 0.0);
+ assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+ new int[]{210});
+ assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+ new int[]{2100});
+ assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+ new int[]{25});
+ assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+ new int[]{30});
+
+ // Notify that the default display is updated, such that DeviceConfig has new values
+ FakeDeviceConfig config = mInjector.getDeviceConfig();
+ config.setDefaultPeakRefreshRate(60);
+ config.setLowAmbientBrightnessThresholds(new int[]{20});
+ config.setLowDisplayBrightnessThresholds(new int[]{10});
+ config.setHighDisplayBrightnessThresholds(new int[]{255});
+ config.setHighAmbientBrightnessThresholds(new int[]{8000});
+
+ director.defaultDisplayDeviceUpdated(displayDeviceConfig);
+
+ assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 50, 0.0);
+ assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60,
+ 0.0);
+ assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(),
+ new int[]{255});
+ assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(),
+ new int[]{8000});
+ assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(),
+ new int[]{10});
+ assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(),
+ new int[]{20});
}
private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
@@ -1954,6 +2026,12 @@
String.valueOf(fps));
}
+ void setDefaultPeakRefreshRate(int fps) {
+ putPropertyAndNotify(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_PEAK_REFRESH_RATE_DEFAULT,
+ String.valueOf(fps));
+ }
+
void setHighDisplayBrightnessThresholds(int[] brightnessThresholds) {
String thresholds = toPropertyValue(brightnessThresholds);
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 2094c93..cc68ba8 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -22,8 +22,6 @@
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED;
import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
-import static com.android.server.display.LogicalDisplay.DISPLAY_PHASE_DISABLED;
-import static com.android.server.display.LogicalDisplay.DISPLAY_PHASE_ENABLED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
@@ -55,8 +53,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.display.layout.Layout;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,7 +85,6 @@
@Mock Resources mResourcesMock;
@Mock IPowerManager mIPowerManagerMock;
@Mock IThermalService mIThermalServiceMock;
- @Mock DeviceStateToLayoutMap mDeviceStateToLayoutMapMock;
@Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
@@ -135,13 +130,11 @@
when(mResourcesMock.getIntArray(
com.android.internal.R.array.config_deviceStatesOnWhichToSleep))
.thenReturn(new int[]{0});
- when(mDeviceStateToLayoutMapMock.get(-1)).thenReturn(new Layout());
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
- mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
- mDeviceStateToLayoutMapMock);
+ mListenerMock, new DisplayManagerService.SyncRoot(), mHandler);
}
@@ -420,58 +413,6 @@
/* isBootCompleted= */true));
}
- @Test
- public void testDeviceStateLocked() {
- DisplayDevice device1 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- DisplayDevice device2 = createDisplayDevice(Display.TYPE_INTERNAL, 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
-
- Layout layout = new Layout();
- layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true);
- layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false);
- when(mDeviceStateToLayoutMapMock.get(0)).thenReturn(layout);
-
- layout = new Layout();
- layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, false, false);
- layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, true, true);
- when(mDeviceStateToLayoutMapMock.get(1)).thenReturn(layout);
- when(mDeviceStateToLayoutMapMock.get(2)).thenReturn(layout);
-
- LogicalDisplay display1 = add(device1);
- assertEquals(info(display1).address, info(device1).address);
- assertEquals(DEFAULT_DISPLAY, id(display1));
-
- LogicalDisplay display2 = add(device2);
- assertEquals(info(display2).address, info(device2).address);
- // We can only have one default display
- assertEquals(DEFAULT_DISPLAY, id(display1));
-
- mLogicalDisplayMapper.setDeviceStateLocked(0, false);
- mLooper.moveTimeForward(1000);
- mLooper.dispatchAll();
- assertEquals(DISPLAY_PHASE_ENABLED,
- mLogicalDisplayMapper.getDisplayLocked(device1).getPhase());
- assertEquals(DISPLAY_PHASE_DISABLED,
- mLogicalDisplayMapper.getDisplayLocked(device2).getPhase());
-
- mLogicalDisplayMapper.setDeviceStateLocked(1, false);
- mLooper.moveTimeForward(1000);
- mLooper.dispatchAll();
- assertEquals(DISPLAY_PHASE_DISABLED,
- mLogicalDisplayMapper.getDisplayLocked(device1).getPhase());
- assertEquals(DISPLAY_PHASE_ENABLED,
- mLogicalDisplayMapper.getDisplayLocked(device2).getPhase());
-
- mLogicalDisplayMapper.setDeviceStateLocked(2, false);
- mLooper.moveTimeForward(1000);
- mLooper.dispatchAll();
- assertEquals(DISPLAY_PHASE_DISABLED,
- mLogicalDisplayMapper.getDisplayLocked(device1).getPhase());
- assertEquals(DISPLAY_PHASE_ENABLED,
- mLogicalDisplayMapper.getDisplayLocked(device2).getPhase());
- }
-
/////////////////
// Helper Methods
/////////////////
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 90b19a4..f289866 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -65,6 +65,7 @@
private final ArgumentCaptor<DeviceState[]> mDeviceStateArrayCaptor = ArgumentCaptor.forClass(
DeviceState[].class);
private final ArgumentCaptor<Integer> mIntegerCaptor = ArgumentCaptor.forClass(Integer.class);
+ private static final int MAX_HINGE_ANGLE_EXCLUSIVE = 360;
private Context mContext;
private SensorManager mSensorManager;
@@ -268,11 +269,7 @@
assertEquals(1, mIntegerCaptor.getValue().intValue());
}
- @Test
- public void create_sensor() throws Exception {
- Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
- when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
-
+ private DeviceStateProviderImpl create_sensorBasedProvider(Sensor sensor) {
String configString = "<device-state-config>\n"
+ " <device-state>\n"
+ " <identifier>1</identifier>\n"
@@ -310,14 +307,22 @@
+ " <name>" + sensor.getName() + "</name>\n"
+ " <value>\n"
+ " <min-inclusive>180</min-inclusive>\n"
+ + " <max>" + MAX_HINGE_ANGLE_EXCLUSIVE + "</max>\n"
+ " </value>\n"
+ " </sensor>\n"
+ " </conditions>\n"
+ " </device-state>\n"
+ "</device-state-config>\n";
DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
- DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+ return DeviceStateProviderImpl.createFromConfig(mContext,
config);
+ }
+
+ @Test
+ public void create_sensor() throws Exception {
+ Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+ when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+ DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
provider.setListener(listener);
@@ -371,6 +376,40 @@
}
@Test
+ public void test_invalidSensorValues() throws Exception {
+ // onStateChanged() should not be triggered by invalid sensor values.
+
+ Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+ when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+ DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
+
+ DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+ provider.setListener(listener);
+ Mockito.clearInvocations(listener);
+
+ // First, switch to a non-default state.
+ SensorEvent event1 = mock(SensorEvent.class);
+ event1.sensor = sensor;
+ FieldSetter.setField(event1, event1.getClass().getField("values"), new float[]{90});
+ provider.onSensorChanged(event1);
+ verify(listener).onStateChanged(mIntegerCaptor.capture());
+ assertEquals(2, mIntegerCaptor.getValue().intValue());
+
+ Mockito.clearInvocations(listener);
+
+ // Then, send an invalid sensor event, verify that onStateChanged() is not triggered.
+ SensorEvent event2 = mock(SensorEvent.class);
+ event2.sensor = sensor;
+ FieldSetter.setField(event2, event2.getClass().getField("values"),
+ new float[]{MAX_HINGE_ANGLE_EXCLUSIVE});
+
+ provider.onSensorChanged(event2);
+
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
+ }
+
+ @Test
public void create_invalidSensor() throws Exception {
Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of());
@@ -417,9 +456,8 @@
new DeviceState(1, "CLOSED", 0 /* flags */),
new DeviceState(2, "HALF_OPENED", 0 /* flags */)
}, mDeviceStateArrayCaptor.getValue());
- // onStateChanged() should be called because the provider could not find the sensor.
- verify(listener).onStateChanged(mIntegerCaptor.capture());
- assertEquals(1, mIntegerCaptor.getValue().intValue());
+ // onStateChanged() should not be called because the provider could not find the sensor.
+ verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
}
private static Sensor newSensor(String name, String type) throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index f5ed41a..6325008 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -375,6 +375,18 @@
mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
}
+ private void setBatteryLevel(int batteryLevel) {
+ when(mBatteryManagerInternalMock.getBatteryLevel())
+ .thenReturn(batteryLevel);
+ mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+ }
+
+ private void setBatteryHealth(int batteryHealth) {
+ when(mBatteryManagerInternalMock.getBatteryHealth())
+ .thenReturn(batteryHealth);
+ mBatteryReceiver.onReceive(mContextSpy, new Intent(Intent.ACTION_BATTERY_CHANGED));
+ }
+
private void setAttentiveTimeout(int attentiveTimeoutMillis) {
Settings.Secure.putInt(
mContextSpy.getContentResolver(), Settings.Secure.ATTENTIVE_TIMEOUT,
@@ -399,6 +411,12 @@
.thenReturn(disable);
}
+ private void setDreamsBatteryLevelDrainConfig(int threshold) {
+ when(mResourcesSpy.getInteger(
+ com.android.internal.R.integer.config_dreamsBatteryLevelDrainCutoff)).thenReturn(
+ threshold);
+ }
+
private void advanceTime(long timeMs) {
mClock.fastForward(timeMs);
mTestLooper.dispatchAll();
@@ -619,17 +637,42 @@
}
/**
- * Tests that dreaming continues when undocking and configured to do so.
+ * Tests that dreaming stops when undocking and not configured to keep dreaming.
*/
@Test
- public void testWakefulnessDream_shouldKeepDreamingWhenUndocked() {
+ public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenNotConfigured() {
+ // Make sure "unplug turns on screen" is configured to true.
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+ .thenReturn(true);
+ when(mDreamManagerInternalMock.keepDreamingWhenUndockedDefault()).thenReturn(false);
+
createService();
startSystem();
- when(mResourcesSpy.getBoolean(
- com.android.internal.R.bool.config_keepDreamingWhenUndocking))
+ when(mBatteryManagerInternalMock.getPlugType())
+ .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+ setPluggedIn(true);
+
+ forceAwake(); // Needs to be awake first before it can dream.
+ forceDream();
+ when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+ setPluggedIn(false);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ }
+
+ /**
+ * Tests that dreaming continues when undocking and configured to do so.
+ */
+ @Test
+ public void testWakefulnessDream_shouldKeepDreamingWhenUndocked_whenConfigured() {
+ // Make sure "unplug turns on screen" is configured to true.
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
.thenReturn(true);
- mService.readConfigurationLocked();
+ when(mDreamManagerInternalMock.keepDreamingWhenUndockedDefault()).thenReturn(true);
+
+ createService();
+ startSystem();
when(mBatteryManagerInternalMock.getPlugType())
.thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
@@ -643,6 +686,37 @@
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
}
+ /**
+ * Tests that dreaming stops when undocking while showing a dream that prevents it.
+ */
+ @Test
+ public void testWakefulnessDream_shouldStopDreamingWhenUndocked_whenDreamPrevents() {
+ // Make sure "unplug turns on screen" is configured to true.
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+ .thenReturn(true);
+ when(mDreamManagerInternalMock.keepDreamingWhenUndockedDefault()).thenReturn(true);
+
+ createService();
+ startSystem();
+
+ ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+ ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+ verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+ dreamManagerStateListener.capture());
+
+ when(mBatteryManagerInternalMock.getPlugType())
+ .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
+ setPluggedIn(true);
+
+ forceAwake(); // Needs to be awake first before it can dream.
+ forceDream();
+ dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false);
+ when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+ setPluggedIn(false);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ }
+
@Test
public void testWakefulnessDoze_goToSleep() {
createService();
@@ -882,6 +956,41 @@
}
@Test
+ public void testBatteryDrainDuringDream() {
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
+
+ setMinimumScreenOffTimeoutConfig(100);
+ setDreamsBatteryLevelDrainConfig(5);
+ createService();
+ startSystem();
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ setBatteryLevel(100);
+ setPluggedIn(true);
+
+ forceAwake(); // Needs to be awake first before it can dream.
+ forceDream();
+ advanceTime(10); // Allow async calls to happen
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
+ setBatteryLevel(90);
+ advanceTime(10); // Allow async calls to happen
+ assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+
+ // If battery overheat protection is enabled, we shouldn't count battery drain
+ setBatteryHealth(BatteryManager.BATTERY_HEALTH_OVERHEAT);
+ setBatteryLevel(70);
+ advanceTime(10); // Allow async calls to happen
+ assertThat(mService.getDreamsBatteryLevelDrain()).isEqualTo(10);
+ }
+
+ @Test
public void testSetDozeOverrideFromDreamManager_triggersSuspendBlocker() {
final String suspendBlockerName = "PowerManagerService.Display";
final String tag = "acq_causes_wakeup";
@@ -1820,6 +1929,50 @@
}
@Test
+ public void testMultiDisplay_defaultDozing_addNewDisplayDefaultGoesBackToDoze() {
+ final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = nonDefaultDisplayGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ createService();
+ startSystem();
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+
+ forceDozing();
+ advanceTime(500);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_DOZING);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+ verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+ advanceTime(500);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_DOZING);
+ verify(mDreamManagerInternalMock, times(2)).startDream(eq(true), anyString());
+ }
+
+ @Test
public void testLastSleepTime_notUpdatedWhenDreaming() {
createService();
startSystem();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 7df4b57..3f3b052 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -7549,6 +7549,65 @@
}
@Test
+ public void testAddAutomaticZenRule_systemCallTakesPackageFromOwner() throws Exception {
+ mService.isSystemUid = true;
+ ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ mService.setZenHelper(mockZenModeHelper);
+ ComponentName owner = new ComponentName("android", "ProviderName");
+ ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+ boolean isEnabled = true;
+ AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+ zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+ // verify that zen mode helper gets passed in a package name of "android"
+ verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+ }
+
+ @Test
+ public void testAddAutomaticZenRule_systemAppIdCallTakesPackageFromOwner() throws Exception {
+ // The multi-user case: where the calling uid doesn't match the system uid, but the calling
+ // *appid* is the system.
+ mService.isSystemUid = false;
+ mService.isSystemAppId = true;
+ ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ mService.setZenHelper(mockZenModeHelper);
+ ComponentName owner = new ComponentName("android", "ProviderName");
+ ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+ boolean isEnabled = true;
+ AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+ zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+ mBinderService.addAutomaticZenRule(rule, "com.android.settings");
+
+ // verify that zen mode helper gets passed in a package name of "android"
+ verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString());
+ }
+
+ @Test
+ public void testAddAutomaticZenRule_nonSystemCallTakesPackageFromArg() throws Exception {
+ mService.isSystemUid = false;
+ mService.isSystemAppId = false;
+ ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class);
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ mService.setZenHelper(mockZenModeHelper);
+ ComponentName owner = new ComponentName("android", "ProviderName");
+ ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
+ boolean isEnabled = true;
+ AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
+ zenPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, isEnabled);
+ mBinderService.addAutomaticZenRule(rule, "another.package");
+
+ // verify that zen mode helper gets passed in the package name from the arg, not the owner
+ verify(mockZenModeHelper).addAutomaticZenRule(
+ eq("another.package"), eq(rule), anyString());
+ }
+
+ @Test
public void testAreNotificationsEnabledForPackage() throws Exception {
mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
mUid);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 8cf74fb..61a6985 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -32,6 +32,7 @@
public class TestableNotificationManagerService extends NotificationManagerService {
int countSystemChecks = 0;
boolean isSystemUid = true;
+ boolean isSystemAppId = true;
int countLogSmartSuggestionsVisible = 0;
Set<Integer> mChannelToastsSent = new HashSet<>();
@@ -58,6 +59,12 @@
}
@Override
+ protected boolean isCallingAppIdSystem() {
+ countSystemChecks++;
+ return isSystemUid || isSystemAppId;
+ }
+
+ @Override
protected boolean isCallerSystemOrPhone() {
countSystemChecks++;
return isSystemUid;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 4550b56..2ccdcaa 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1672,6 +1672,36 @@
}
@Test
+ public void testAddAutomaticZenRule_claimedSystemOwner() {
+ // Make sure anything that claims to have a "system" owner but not actually part of the
+ // system package still gets limited on number of rules
+ for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
+ ScheduleInfo si = new ScheduleInfo();
+ si.startHour = i;
+ AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
+ new ComponentName("android", "ScheduleConditionProvider" + i),
+ null, // configuration activity
+ ZenModeConfig.toScheduleConditionId(si),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ assertNotNull(id);
+ }
+ try {
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ new ComponentName("android", "ScheduleConditionProviderFinal"),
+ null, // configuration activity
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ fail("allowed too many rules to be created");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
+ }
+
+ @Test
public void testAddAutomaticZenRule_CA() {
AutomaticZenRule zenRule = new AutomaticZenRule("name",
null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 8a0a4f7..d3e638f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2809,7 +2809,7 @@
final Task task = activity.getTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
topActivity.setVisible(false);
- task.positionChildAt(topActivity, POSITION_TOP);
+ task.positionChildAt(POSITION_TOP, topActivity, false /* includeParents */);
activity.addStartingWindow(mPackageName, android.R.style.Theme, null, true, true, false,
true, false, false, false);
waitUntilHandlersIdle();
@@ -3091,6 +3091,17 @@
assertTrue(activity.mVisibleRequested);
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
+
+ // There should still be animation (add to opening) if keyguard is going away while the
+ // screen is off because it will be visible after screen is turned on by unlocking.
+ mDisplayContent.mOpeningApps.remove(activity);
+ mDisplayContent.mClosingApps.remove(activity);
+ activity.commitVisibility(false /* visible */, false /* performLayout */);
+ mDisplayContent.getDisplayPolicy().screenTurnedOff();
+ final KeyguardController controller = mSupervisor.getKeyguardController();
+ doReturn(true).when(controller).isKeyguardGoingAway(anyInt());
+ activity.setVisibility(true);
+ assertTrue(mDisplayContent.mOpeningApps.contains(activity));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 496f681..6fe2d2c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -1131,6 +1131,26 @@
}
@Test
+ public void testRecycleTaskWakeUpWhenDreaming() {
+ doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+ doReturn(true).when(mWm.mAtmService).isDreaming();
+ final ActivityStarter starter = prepareStarter(0 /* flags */);
+ final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ starter.mStartActivity = target;
+ target.mVisibleRequested = false;
+ target.setTurnScreenOn(true);
+ // Assume the flag was consumed by relayout.
+ target.setCurrentLaunchCanTurnScreenOn(false);
+ startActivityInner(starter, target, null /* source */, null /* options */,
+ null /* inTask */, null /* inTaskFragment */);
+ // The flag should be set again when resuming (from recycleTask) the target as top.
+ assertTrue(target.currentLaunchCanTurnScreenOn());
+ // In real case, dream activity has a higher priority (TaskDisplayArea#getPriority) that
+ // will be put at a higher z-order. So it relies on wakeUp() to be dismissed.
+ verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString());
+ }
+
+ @Test
public void testTargetTaskInSplitScreen() {
final ActivityStarter starter =
prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 0332c4b..43e79f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -25,6 +25,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
@@ -56,6 +57,7 @@
import static org.mockito.Mockito.verify;
import android.annotation.Nullable;
+import android.graphics.Rect;
import android.gui.DropInputMode;
import android.os.Binder;
import android.os.IBinder;
@@ -918,7 +920,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -935,11 +937,77 @@
}
@Test
+ public void testOverrideTaskFragmentAdapter_noOverrideWithOnlyTaskFragmentFillingTask() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord closingActivity = createActivityRecord(task);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+ // Make sure the TaskFragment is not embedded.
+ assertFalse(taskFragment.isEmbeddedWithBoundsOverride());
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(closingActivity);
+ prepareActivityForAppTransition(openingActivity);
+ final int uid = 12345;
+ closingActivity.info.applicationInfo.uid = uid;
+ openingActivity.info.applicationInfo.uid = uid;
+ task.effectiveUid = uid;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity,
+ null /* changingTaskFragment */);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation is not run by the remote handler because the activity is filling the Task.
+ assertFalse(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
+ public void testOverrideTaskFragmentAdapter_overrideWithTaskFragmentNotFillingTask() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord closingActivity = createActivityRecord(task);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
+
+ // Create a TaskFragment with embedded activity.
+ final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+
+ // Make sure the TaskFragment is embedded.
+ taskFragment.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ final Rect embeddedBounds = new Rect(task.getBounds());
+ embeddedBounds.right = embeddedBounds.left + embeddedBounds.width() / 2;
+ taskFragment.setBounds(embeddedBounds);
+ assertTrue(taskFragment.isEmbeddedWithBoundsOverride());
+ final ActivityRecord openingActivity = taskFragment.getTopMostActivity();
+ prepareActivityForAppTransition(closingActivity);
+ prepareActivityForAppTransition(openingActivity);
+ final int uid = 12345;
+ closingActivity.info.applicationInfo.uid = uid;
+ openingActivity.info.applicationInfo.uid = uid;
+ task.effectiveUid = uid;
+ spyOn(mDisplayContent.mAppTransition);
+
+ // Prepare and start transition.
+ prepareAndTriggerAppTransition(openingActivity, closingActivity,
+ null /* changingTaskFragment */);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+
+ // Animation run by the remote handler.
+ assertTrue(remoteAnimationRunner.isAnimationStarted());
+ }
+
+ @Test
public void testOverrideTaskFragmentAdapter_overrideWithNonEmbeddedActivity() {
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing non-embedded activity.
final ActivityRecord closingActivity = createActivityRecord(task);
@@ -964,7 +1032,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing TaskFragment with embedded activity.
final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -991,7 +1059,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing activity in Task1.
final ActivityRecord closingActivity = createActivityRecord(mDisplayContent);
@@ -1015,7 +1083,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Closing TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -1043,7 +1111,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with embedded activity.
final TaskFragment taskFragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
@@ -1069,7 +1137,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with embedded activities, one is trusted embedded, and the other
// one is untrusted embedded.
@@ -1128,7 +1196,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with only trusted embedded activity
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -1168,7 +1236,7 @@
final Task task = createTask(mDisplayContent);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TestRemoteAnimationRunner remoteAnimationRunner = new TestRemoteAnimationRunner();
- setupTaskFragmentRemoteAnimation(organizer, task.mTaskId, remoteAnimationRunner);
+ setupTaskFragmentRemoteAnimation(organizer, remoteAnimationRunner);
// Create a TaskFragment with only trusted embedded activity
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -1259,7 +1327,7 @@
}
/** Registers remote animation for the organizer. */
- private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer, int taskId,
+ private void setupTaskFragmentRemoteAnimation(TaskFragmentOrganizer organizer,
TestRemoteAnimationRunner remoteAnimationRunner) {
final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
remoteAnimationRunner, 10, 1);
@@ -1268,9 +1336,10 @@
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_OPEN, adapter);
definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CLOSE, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_OPEN, adapter);
+ definition.addRemoteAnimation(TRANSIT_OLD_ACTIVITY_CLOSE, adapter);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
- mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, taskId,
- definition);
+ mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
}
private static ITaskFragmentOrganizer getITaskFragmentOrganizer(
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 32c95fa..8cfe503 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -49,6 +49,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
@@ -67,11 +68,14 @@
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.view.animation.Animation;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentOrganizer;
import androidx.test.filters.SmallTest;
+import com.android.internal.policy.TransitionAnimation;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -495,6 +499,80 @@
assertEquals(startBounds, taskFragment.mSurfaceFreezer.mFreezeBounds);
}
+ @Test
+ public void testGetNextAppTransitionBackgroundColor() {
+ assumeFalse(WindowManagerService.sEnableShellTransitions);
+
+ // No override by default.
+ assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+
+ // Override with a custom color.
+ mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+ final int testColor = 123;
+ mDc.mAppTransition.overridePendingAppTransition("testPackage", 0 /* enterAnim */,
+ 0 /* exitAnim */, testColor, null /* startedCallback */, null /* endedCallback */,
+ false /* overrideTaskTransaction */);
+
+ assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+ assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Override with ActivityEmbedding remote animation. Background color should be kept.
+ mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
+ false /* sync */, true /* isActivityEmbedding */);
+
+ assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+ assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Background color should not be cleared anymore after #clear().
+ mDc.mAppTransition.clear();
+ assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
+ assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+ }
+
+ @Test
+ public void testGetNextAppRequestedAnimation() {
+ assumeFalse(WindowManagerService.sEnableShellTransitions);
+ final String packageName = "testPackage";
+ final int enterAnimResId = 1;
+ final int exitAnimResId = 2;
+ final int testColor = 123;
+ final Animation enterAnim = mock(Animation.class);
+ final Animation exitAnim = mock(Animation.class);
+ final TransitionAnimation transitionAnimation = mDc.mAppTransition.mTransitionAnimation;
+ spyOn(transitionAnimation);
+ doReturn(enterAnim).when(transitionAnimation)
+ .loadAppTransitionAnimation(packageName, enterAnimResId);
+ doReturn(exitAnim).when(transitionAnimation)
+ .loadAppTransitionAnimation(packageName, exitAnimResId);
+
+ // No override by default.
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+
+ // Override with a custom animation.
+ mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
+ mDc.mAppTransition.overridePendingAppTransition(packageName, enterAnimResId, exitAnimResId,
+ testColor, null /* startedCallback */, null /* endedCallback */,
+ false /* overrideTaskTransaction */);
+
+ assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+ assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Override with ActivityEmbedding remote animation. Custom animation should be kept.
+ mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
+ false /* sync */, true /* isActivityEmbedding */);
+
+ assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+ assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
+
+ // Custom animation should not be cleared anymore after #clear().
+ mDc.mAppTransition.clear();
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
+ assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
+ }
+
private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
boolean mCancelled = false;
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 1246d1e..1be9de7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -35,6 +35,7 @@
import junit.framework.Assert;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -72,6 +73,7 @@
mLetterboxConfigurationPersister.start();
}
+ @After
public void tearDown() throws InterruptedException {
deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
waitForCompletion(mPersisterQueue);
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 181e81d..06eea29 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -25,6 +25,7 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
@@ -69,6 +70,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.times;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -84,6 +86,7 @@
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
+import android.view.InsetsSource;
import android.view.InsetsVisibilities;
import android.view.WindowManager;
@@ -103,6 +106,9 @@
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.List;
/**
* Tests for Size Compatibility mode.
@@ -2369,6 +2375,48 @@
}
@Test
+ public void testLetterboxDetailsForTaskBar_letterboxNotOverlappingTaskBar() {
+ mAtm.mDevEnableNonResizableMultiWindow = true;
+ final int screenHeight = 2200;
+ final int screenWidth = 1400;
+ final int taskbarHeight = 200;
+ setUpDisplaySizeWithApp(screenWidth, screenHeight);
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+
+ // Move first activity to split screen which takes half of the screen.
+ organizer.mPrimary.setBounds(0, screenHeight / 2, screenWidth, screenHeight);
+ organizer.putTaskToPrimary(mTask, true);
+
+ final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+ navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
+
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
+
+ final WindowState w1 = addWindowToActivity(mActivity);
+ w1.mAboveInsetsState.addSource(navSource);
+
+ // Prepare unresizable activity with max aspect ratio
+ prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
+
+ // Refresh the letterboxes
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ final ArgumentCaptor<Rect> cropCapturer = ArgumentCaptor.forClass(Rect.class);
+ verify(mTransaction, times(2)).setWindowCrop(
+ eq(w1.getSurfaceControl()),
+ cropCapturer.capture()
+ );
+ final List<Rect> capturedCrops = cropCapturer.getAllValues();
+
+ final int expectedHeight = screenHeight / 2 - taskbarHeight;
+ assertEquals(2, capturedCrops.size());
+ assertEquals(expectedHeight, capturedCrops.get(0).bottom);
+ assertEquals(expectedHeight, capturedCrops.get(1).bottom);
+ }
+
+ @Test
public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
mAtm.mDevEnableNonResizableMultiWindow = true;
setUpDisplaySizeWithApp(2800, 1000);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index d3aa073..df7b3cd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -74,15 +74,15 @@
int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, mockWC);
- // Make sure a traversal is requested
- verify(mWm.mWindowPlacerLocked, times(1)).requestTraversal();
+ // The traversal is not requested because ready is not set.
+ verify(mWm.mWindowPlacerLocked, times(0)).requestTraversal();
bse.onSurfacePlacement();
verify(listener, times(0)).onTransactionReady(anyInt(), any());
bse.setReady(id);
// Make sure a traversal is requested
- verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
+ verify(mWm.mWindowPlacerLocked).requestTraversal();
bse.onSurfacePlacement();
verify(listener, times(1)).onTransactionReady(eq(id), notNull());
@@ -103,14 +103,14 @@
int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, mockWC);
bse.setReady(id);
- // Make sure traversals requested (one for add and another for setReady)
- verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
+ // Make sure traversals requested.
+ verify(mWm.mWindowPlacerLocked).requestTraversal();
bse.onSurfacePlacement();
verify(listener, times(0)).onTransactionReady(anyInt(), any());
mockWC.onSyncFinishedDrawing();
- // Make sure a (third) traversal is requested.
- verify(mWm.mWindowPlacerLocked, times(3)).requestTraversal();
+ // Make sure the second traversal is requested.
+ verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
bse.onSurfacePlacement();
verify(listener, times(1)).onTransactionReady(eq(id), notNull());
}
@@ -127,8 +127,8 @@
int id = startSyncSet(bse, listener);
bse.addToSyncSet(id, mockWC);
bse.setReady(id);
- // Make sure traversals requested (one for add and another for setReady)
- verify(mWm.mWindowPlacerLocked, times(2)).requestTraversal();
+ // Make sure traversals requested.
+ verify(mWm.mWindowPlacerLocked).requestTraversal();
bse.onSurfacePlacement();
verify(listener, times(0)).onTransactionReady(anyInt(), any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 4202f46..2b49314 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -62,10 +62,12 @@
import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
+import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -403,7 +405,7 @@
final TaskFragmentTransaction.Change change = changes.get(0);
assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
assertEquals(task.mTaskId, change.getTaskId());
- assertEquals(activity.intent, change.getActivityIntent());
+ assertIntentsEqualForOrganizer(activity.intent, change.getActivityIntent());
assertNotEquals(activity.token, change.getActivityToken());
mTransaction.reparentActivityToTaskFragment(mFragmentToken, change.getActivityToken());
assertApplyTransactionAllowed(mTransaction);
@@ -415,14 +417,70 @@
}
@Test
+ public void testOnActivityReparentedToTask_untrustedEmbed_notReported() {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
+ DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final Task task = createTask(mDisplayContent);
+ task.addChild(mTaskFragment, POSITION_TOP);
+ final ActivityRecord activity = createActivityRecord(task);
+
+ // Make sure the activity is embedded in untrusted mode.
+ activity.info.applicationInfo.uid = uid + 1;
+ doReturn(pid + 1).when(activity).getPid();
+ task.effectiveUid = uid;
+ doReturn(EMBEDDING_ALLOWED).when(task).isAllowedToEmbedActivity(activity, uid);
+ doReturn(false).when(task).isAllowedToEmbedActivityInTrustedMode(activity, uid);
+ doReturn(true).when(task).isAllowedToEmbedActivityInUntrustedMode(activity);
+
+ // Notify organizer if it was embedded before entered Pip.
+ // Create a temporary token since the activity doesn't belong to the same process.
+ clearInvocations(mOrganizer);
+ activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
+ mController.onActivityReparentedToTask(activity);
+ mController.dispatchPendingEvents();
+
+ // Disallow organizer to reparent activity that is untrusted embedded.
+ verify(mOrganizer, never()).onTransactionReady(mTransactionCaptor.capture());
+ }
+
+ @Test
+ public void testOnActivityReparentedToTask_trimReportedIntent() {
+ // Make sure the activity pid/uid is the same as the organizer caller.
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final ActivityRecord activity = createActivityRecord(mDisplayContent);
+ final Task task = activity.getTask();
+ activity.info.applicationInfo.uid = uid;
+ doReturn(pid).when(activity).getPid();
+ task.effectiveUid = uid;
+ activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
+
+ // Test the Intent trim in #assertIntentTrimmed
+ activity.intent.setComponent(new ComponentName("TestPackage", "TestClass"))
+ .setPackage("TestPackage")
+ .setAction("TestAction")
+ .setData(mock(Uri.class))
+ .putExtra("Test", 123)
+ .setFlags(10);
+
+ mController.onActivityReparentedToTask(activity);
+ mController.dispatchPendingEvents();
+
+ assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
+ }
+
+ @Test
public void testRegisterRemoteAnimations() {
- mController.registerRemoteAnimations(mIOrganizer, TASK_ID, mDefinition);
+ mController.registerRemoteAnimations(mIOrganizer, mDefinition);
- assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
+ assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
- mController.unregisterRemoteAnimations(mIOrganizer, TASK_ID);
+ mController.unregisterRemoteAnimations(mIOrganizer);
- assertNull(mController.getRemoteAnimationDefinition(mIOrganizer, TASK_ID));
+ assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
}
@Test
@@ -764,6 +822,21 @@
}
@Test
+ public void testOnTransactionHandled_skipTransactionForUnregisterOrganizer() {
+ mController.unregisterOrganizer(mIOrganizer);
+ final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
+ final IBinder fragmentToken = new Binder();
+
+ // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
+ createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+ mController.onTransactionHandled(new Binder(), mTransaction,
+ getTransitionType(mTransaction), false /* shouldApplyIndependently */);
+
+ // Nothing should happen as the organizer is not registered.
+ assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
+ }
+
+ @Test
public void testOrganizerRemovedWithPendingEvents() {
final TaskFragment tf0 = new TaskFragmentBuilder(mAtm)
.setCreateParentTask()
@@ -1425,7 +1498,8 @@
final TaskFragmentTransaction.Change change = changes.remove(0);
assertEquals(TYPE_ACTIVITY_REPARENTED_TO_TASK, change.getType());
assertEquals(taskId, change.getTaskId());
- assertEquals(intent, change.getActivityIntent());
+ assertIntentsEqualForOrganizer(intent, change.getActivityIntent());
+ assertIntentTrimmed(change.getActivityIntent());
assertEquals(activityToken, change.getActivityToken());
}
@@ -1452,4 +1526,17 @@
mockParent.lastActiveTime = 100;
doReturn(true).when(mockParent).shouldBeVisible(any());
}
+
+ private static void assertIntentsEqualForOrganizer(@NonNull Intent expected,
+ @NonNull Intent actual) {
+ assertEquals(expected.getComponent(), actual.getComponent());
+ assertEquals(expected.getPackage(), actual.getPackage());
+ assertEquals(expected.getAction(), actual.getAction());
+ }
+
+ private static void assertIntentTrimmed(@NonNull Intent intent) {
+ assertNull(intent.getData());
+ assertNull(intent.getExtras());
+ assertEquals(0, intent.getFlags());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 83f1789..97e5755 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -23,6 +23,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.os.Process.FIRST_APPLICATION_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -118,10 +119,13 @@
doReturn(true).when(mTaskFragment).isVisibleRequested();
clearInvocations(mTransaction);
+ mTaskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
mTaskFragment.setBounds(endBounds);
+ assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds));
+ mTaskFragment.initializeChangeTransition(startBounds);
+ mTaskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
// Surface reset when prepare transition.
- verify(mTaskFragment).initializeChangeTransition(startBounds);
verify(mTransaction).setPosition(mLeash, 0, 0);
verify(mTransaction).setWindowCrop(mLeash, 0, 0);
@@ -166,7 +170,7 @@
mTaskFragment.setBounds(endBounds);
- verify(mTaskFragment, never()).initializeChangeTransition(any());
+ assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds));
}
/**
@@ -544,4 +548,31 @@
activity0.moveFocusableActivityToTop("test");
assertEquals(activity0, mDisplayContent.mFocusedApp);
}
+
+ @Test
+ public void testIsVisibleWithAdjacent_reportOrientationUnspecified() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment tf0 = createTaskFragmentWithParentTask(task);
+ final TaskFragment tf1 = createTaskFragmentWithParentTask(task);
+ tf0.setAdjacentTaskFragment(tf1);
+ tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ task.setBounds(0, 0, 1200, 1000);
+ tf0.setBounds(0, 0, 600, 1000);
+ tf1.setBounds(600, 0, 1200, 1000);
+ final ActivityRecord activity0 = tf0.getTopMostActivity();
+ final ActivityRecord activity1 = tf1.getTopMostActivity();
+ doReturn(true).when(activity0).isVisibleRequested();
+ doReturn(true).when(activity1).isVisibleRequested();
+
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf0.getOrientation(SCREEN_ORIENTATION_UNSET));
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf1.getOrientation(SCREEN_ORIENTATION_UNSET));
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, task.getOrientation(SCREEN_ORIENTATION_UNSET));
+
+ activity0.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf0.getOrientation(SCREEN_ORIENTATION_UNSET));
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf1.getOrientation(SCREEN_ORIENTATION_UNSET));
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, task.getOrientation(SCREEN_ORIENTATION_UNSET));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index a6c5fd8..1188f49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -85,6 +85,11 @@
private static final Rect DISPLAY_STABLE_BOUNDS = new Rect(/* left */ 100,
/* top */ 200, /* right */ 1620, /* bottom */ 680);
+ private static final Rect SMALL_DISPLAY_BOUNDS = new Rect(/* left */ 0, /* top */ 0,
+ /* right */ 1000, /* bottom */ 500);
+ private static final Rect SMALL_DISPLAY_STABLE_BOUNDS = new Rect(/* left */ 100,
+ /* top */ 50, /* right */ 900, /* bottom */ 450);
+
private ActivityRecord mActivity;
private TaskLaunchParamsModifier mTarget;
@@ -571,6 +576,29 @@
}
@Test
+ public void testBoundsInOptionsInfersFullscreenWithBoundsOnFreeformSupportFullscreenDisplay() {
+ final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FULLSCREEN);
+ mAtm.mTaskSupervisor.mService.mSupportsFreeformWindowManagement = true;
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ final Rect expectedBounds = new Rect(0, 0, 100, 100);
+ options.setLaunchBounds(expectedBounds);
+
+ mCurrent.mPreferredTaskDisplayArea = fullscreenDisplay.getDefaultTaskDisplayArea();
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setOptions(options).calculate());
+
+ // Setting bounds shouldn't lead to freeform windowing mode on fullscreen display by
+ // default (even with freeform support), but we need to check here if the bounds is set even
+ // with fullscreen windowing mode in case it's restored later.
+ assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
+ assertEquals(expectedBounds, mResult.mBounds);
+ }
+
+ @Test
public void testInheritsFreeformModeFromSourceOnFullscreenDisplay() {
final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
WINDOWING_MODE_FULLSCREEN);
@@ -952,6 +980,8 @@
WINDOWING_MODE_FULLSCREEN);
final ActivityRecord source = createSourceActivity(fullscreenDisplay);
source.getTask().setWindowingMode(WINDOWING_MODE_FREEFORM);
+ // Set some bounds to avoid conflict with the other activity.
+ source.setBounds(100, 100, 200, 200);
final ActivityOptions options = ActivityOptions.makeBasic();
final Rect expected = new Rect(0, 0, 150, 150);
@@ -1321,6 +1351,20 @@
}
@Test
+ public void testDefaultFreeformSizeShrinksOnSmallDisplay() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM, SMALL_DISPLAY_BOUNDS, SMALL_DISPLAY_STABLE_BOUNDS);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setOptions(options)
+ .calculate());
+
+ assertEquals(new Rect(414, 77, 587, 423), mResult.mBounds);
+ }
+
+ @Test
public void testDefaultFreeformSizeRespectsMinAspectRatio() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
@@ -1510,16 +1554,15 @@
options.setLaunchDisplayId(freeformDisplay.mDisplayId);
mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
- mCurrent.mBounds.set(100, 300, 1820, 1380);
+ mCurrent.mBounds.set(0, 0, 3000, 2000);
mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder().setOptions(options).calculate());
- assertTrue("Result bounds should start from app bounds's origin, but it's "
- + mResult.mBounds,
- mResult.mBounds.left == 100 && mResult.mBounds.top == 200);
+ // Must shrink to fit the display while reserving aspect ratio.
+ assertEquals(new Rect(127, 227, 766, 653), mResult.mBounds);
}
@Test
@@ -1535,18 +1578,19 @@
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setMinWidth(500).setMinHeight(500).build();
mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
- mCurrent.mBounds.set(100, 300, 1820, 1380);
+ mCurrent.mBounds.set(0, 0, 2000, 3000);
mActivity.info.applicationInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
assertEquals(RESULT_CONTINUE,
- new CalculateRequestBuilder().setOptions(options).calculate());
+ new CalculateRequestBuilder().setOptions(options).setLayout(layout).calculate());
- assertTrue("Result bounds should start from top-right corner of app bounds, but "
- + "it's " + mResult.mBounds,
- mResult.mBounds.left == -100 && mResult.mBounds.top == 200);
+ // Must shrink to fit the display while reserving aspect ratio.
+ assertEquals(new Rect(1093, 227, 1593, 727), mResult.mBounds);
}
@Test
@@ -1721,7 +1765,7 @@
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder().setOptions(options).calculate());
- assertEquals(new Rect(100, 200, 400, 500), mResult.mBounds);
+ assertEquals(new Rect(127, 227, 427, 527), mResult.mBounds);
}
@Test
@@ -1774,13 +1818,18 @@
}
private TestDisplayContent createNewDisplayContent(int windowingMode) {
+ return createNewDisplayContent(windowingMode, DISPLAY_BOUNDS, DISPLAY_STABLE_BOUNDS);
+ }
+
+ private TestDisplayContent createNewDisplayContent(int windowingMode, Rect displayBounds,
+ Rect displayStableBounds) {
final TestDisplayContent display = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
display.getDefaultTaskDisplayArea().setWindowingMode(windowingMode);
- display.setBounds(DISPLAY_BOUNDS);
+ display.setBounds(displayBounds);
display.getConfiguration().densityDpi = DENSITY_DEFAULT;
display.getConfiguration().orientation = ORIENTATION_LANDSCAPE;
configInsetsState(display.getInsetsStateController().getRawInsetsState(), display,
- DISPLAY_STABLE_BOUNDS);
+ displayStableBounds);
return display;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 68ac1d6..0c8e89a8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -36,6 +36,7 @@
import static android.view.Surface.ROTATION_90;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
@@ -82,6 +83,7 @@
import android.util.Xml;
import android.view.Display;
import android.view.DisplayInfo;
+import android.window.TaskFragmentOrganizer;
import androidx.test.filters.MediumTest;
@@ -1469,6 +1471,26 @@
tf0, parentTask.getTaskFragment(TaskFragment::isOrganizedTaskFragment));
}
+ @Test
+ public void testReorderActivityToFront() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ doNothing().when(task).onActivityVisibleRequestedChanged();
+ final ActivityRecord activity = task.getTopMostActivity();
+
+ final TaskFragment fragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord embeddedActivity = fragment.getTopMostActivity();
+ task.moveActivityToFront(activity);
+ assertEquals("Activity must be moved to front", activity, task.getTopMostActivity());
+
+ doNothing().when(fragment).sendTaskFragmentInfoChanged();
+ task.moveActivityToFront(embeddedActivity);
+ assertEquals("Activity must be moved to front", embeddedActivity,
+ task.getTopMostActivity());
+ assertEquals("Activity must not be embedded", embeddedActivity,
+ task.getTopChild());
+ }
+
private Task getTestTask() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
return task.getBottomMostTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 54bcbd9..999523f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -476,6 +476,8 @@
wallpaperWindow.mHasSurface = true;
doReturn(true).when(mDisplayContent).isAttached();
transition.collect(mDisplayContent);
+ assertFalse("The change of non-interesting window container should be skipped",
+ transition.mChanges.containsKey(mDisplayContent.getParent()));
mDisplayContent.getWindowConfiguration().setRotation(
(mDisplayContent.getWindowConfiguration().getRotation() + 1) % 4);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 6333508..aab70b5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -369,18 +369,9 @@
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
token.finishSync(t, false /* cancel */);
transit.onTransactionReady(transit.getSyncId(), t);
- dc.mTransitionController.finishTransition(transit);
+ dc.mTransitionController.finishTransition(transit.getToken());
assertFalse(wallpaperWindow.isVisible());
assertFalse(token.isVisible());
-
- // Assume wallpaper was visible. When transaction is ready without wallpaper target,
- // wallpaper should be requested to be invisible.
- token.setVisibility(true);
- transit = dc.mTransitionController.createTransition(TRANSIT_CLOSE);
- dc.mTransitionController.collect(token);
- transit.onTransactionReady(transit.getSyncId(), t);
- assertFalse(token.isVisibleRequested());
- assertTrue(token.isVisible());
}
private static void prepareSmallerSecondDisplay(DisplayContent dc, int width, int height) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 24fc93a..8deb282 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1179,7 +1179,7 @@
assertTrue(rootTask2.isOrganized());
// Verify a back pressed does not call the organizer
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1190,7 +1190,7 @@
rootTask.mRemoteToken.toWindowContainerToken(), true);
// Verify now that the back press does call the organizer
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1201,7 +1201,7 @@
rootTask.mRemoteToken.toWindowContainerToken(), false);
// Verify now that the back press no longer calls the organizer
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1407,7 +1407,7 @@
mWm.mWindowPlacerLocked.deferLayout();
rootTask.removeImmediately();
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(record.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(record.token,
new IRequestFinishCallback.Default());
waitUntilHandlersIdle();
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 9bcc136..04d8734 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -95,6 +95,8 @@
import android.view.InsetsVisibilities;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer;
import androidx.test.filters.SmallTest;
@@ -798,6 +800,39 @@
}
@Test
+ public void testEmbeddedActivityResizing_clearAllDrawn() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
+ final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity,
+ "App window");
+ doReturn(true).when(embeddedActivity).isVisible();
+ embeddedActivity.mVisibleRequested = true;
+ makeWindowVisible(win);
+ win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
+ // Set the bounds twice:
+ // 1. To make sure there is no orientation change after #reportResized, which can also cause
+ // #clearAllDrawn.
+ // 2. Make #isLastConfigReportedToClient to be false after #reportResized, so it can process
+ // to check if we need redraw.
+ embeddedTf.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ embeddedTf.setBounds(0, 0, 1000, 2000);
+ win.reportResized();
+ embeddedTf.setBounds(500, 0, 1000, 2000);
+
+ // Clear all drawn when the embedded TaskFragment is in mDisplayContent.mChangingContainers.
+ win.updateResizingWindowIfNeeded();
+ verify(embeddedActivity, never()).clearAllDrawn();
+
+ mDisplayContent.mChangingContainers.add(embeddedTf);
+ win.updateResizingWindowIfNeeded();
+ verify(embeddedActivity).clearAllDrawn();
+ }
+
+ @Test
public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
win0.mActivityRecord.mVisibleRequested = false;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index b99fd16..894ba3e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1731,7 +1731,7 @@
}
void startTransition() {
- mOrganizer.startTransition(mLastTransit, null);
+ mOrganizer.startTransition(mLastTransit.getToken(), null);
}
void onTransactionReady(SurfaceControl.Transaction t) {
@@ -1744,7 +1744,7 @@
}
public void finish() {
- mController.finishTransition(mLastTransit);
+ mController.finishTransition(mLastTransit.getToken());
}
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
new file mode 100644
index 0000000..d5eea1f
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordAudioStreamManager.java
@@ -0,0 +1,233 @@
+/*
+ * 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.server.voiceinteraction;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+
+import static com.android.server.voiceinteraction.HotwordDetectionConnection.DEBUG;
+
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
+import android.media.permission.Identity;
+import android.os.ParcelFileDescriptor;
+import android.service.voice.HotwordAudioStream;
+import android.service.voice.HotwordDetectedResult;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+final class HotwordAudioStreamManager {
+
+ private static final String TAG = "HotwordAudioStreamManager";
+ private static final String OP_MESSAGE = "Streaming hotword audio to VoiceInteractionService";
+ private static final String TASK_ID_PREFIX = "HotwordDetectedResult@";
+ private static final String THREAD_NAME_PREFIX = "Copy-";
+
+ private final AppOpsManager mAppOpsManager;
+ private final Identity mVoiceInteractorIdentity;
+ private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+ HotwordAudioStreamManager(@NonNull AppOpsManager appOpsManager,
+ @NonNull Identity voiceInteractorIdentity) {
+ mAppOpsManager = appOpsManager;
+ mVoiceInteractorIdentity = voiceInteractorIdentity;
+ }
+
+ /**
+ * Starts copying the audio streams in the given {@link HotwordDetectedResult}.
+ * <p>
+ * The returned {@link HotwordDetectedResult} is identical the one that was passed in, except
+ * that the {@link ParcelFileDescriptor}s within {@link HotwordDetectedResult#getAudioStreams()}
+ * are replaced with descriptors from pipes managed by {@link HotwordAudioStreamManager}. The
+ * returned value should be passed on to the client (i.e., the voice interactor).
+ * </p>
+ *
+ * @throws IOException If there was an error creating the managed pipe.
+ */
+ @NonNull
+ public HotwordDetectedResult startCopyingAudioStreams(@NonNull HotwordDetectedResult result)
+ throws IOException {
+ List<HotwordAudioStream> audioStreams = result.getAudioStreams();
+ if (audioStreams.isEmpty()) {
+ return result;
+ }
+
+ List<HotwordAudioStream> newAudioStreams = new ArrayList<>(audioStreams.size());
+ List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> sourcesAndSinks = new ArrayList<>(
+ audioStreams.size());
+ for (HotwordAudioStream audioStream : audioStreams) {
+ ParcelFileDescriptor[] clientPipe = ParcelFileDescriptor.createReliablePipe();
+ ParcelFileDescriptor clientAudioSource = clientPipe[0];
+ ParcelFileDescriptor clientAudioSink = clientPipe[1];
+ HotwordAudioStream newAudioStream =
+ audioStream.buildUpon().setAudioStreamParcelFileDescriptor(
+ clientAudioSource).build();
+ newAudioStreams.add(newAudioStream);
+
+ ParcelFileDescriptor serviceAudioSource =
+ audioStream.getAudioStreamParcelFileDescriptor();
+ sourcesAndSinks.add(new Pair<>(serviceAudioSource, clientAudioSink));
+ }
+
+ String resultTaskId = TASK_ID_PREFIX + System.identityHashCode(result);
+ mExecutorService.execute(new HotwordDetectedResultCopyTask(resultTaskId, sourcesAndSinks));
+
+ return result.buildUpon().setAudioStreams(newAudioStreams).build();
+ }
+
+ private class HotwordDetectedResultCopyTask implements Runnable {
+ private final String mResultTaskId;
+ private final List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> mSourcesAndSinks;
+ private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+ HotwordDetectedResultCopyTask(String resultTaskId,
+ List<Pair<ParcelFileDescriptor, ParcelFileDescriptor>> sourcesAndSinks) {
+ mResultTaskId = resultTaskId;
+ mSourcesAndSinks = sourcesAndSinks;
+ }
+
+ @Override
+ public void run() {
+ Thread.currentThread().setName(THREAD_NAME_PREFIX + mResultTaskId);
+ int size = mSourcesAndSinks.size();
+ List<SingleAudioStreamCopyTask> tasks = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ Pair<ParcelFileDescriptor, ParcelFileDescriptor> sourceAndSink =
+ mSourcesAndSinks.get(i);
+ ParcelFileDescriptor serviceAudioSource = sourceAndSink.first;
+ ParcelFileDescriptor clientAudioSink = sourceAndSink.second;
+ String streamTaskId = mResultTaskId + "@" + i;
+ tasks.add(new SingleAudioStreamCopyTask(streamTaskId, serviceAudioSource,
+ clientAudioSink));
+ }
+
+ if (mAppOpsManager.startOpNoThrow(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
+ mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+ mVoiceInteractorIdentity.attributionTag, OP_MESSAGE) == MODE_ALLOWED) {
+ try {
+ // TODO(b/244599891): Set timeout, close after inactivity
+ mExecutorService.invokeAll(tasks);
+ } catch (InterruptedException e) {
+ Slog.e(TAG, mResultTaskId + ": Task was interrupted", e);
+ bestEffortPropagateError(e.getMessage());
+ } finally {
+ mAppOpsManager.finishOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD,
+ mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+ mVoiceInteractorIdentity.attributionTag);
+ }
+ } else {
+ bestEffortPropagateError(
+ "Failed to obtain RECORD_AUDIO_HOTWORD permission for "
+ + SoundTriggerSessionPermissionsDecorator.toString(
+ mVoiceInteractorIdentity));
+ }
+ }
+
+ private void bestEffortPropagateError(@NonNull String errorMessage) {
+ try {
+ for (Pair<ParcelFileDescriptor, ParcelFileDescriptor> sourceAndSink :
+ mSourcesAndSinks) {
+ ParcelFileDescriptor serviceAudioSource = sourceAndSink.first;
+ ParcelFileDescriptor clientAudioSink = sourceAndSink.second;
+ serviceAudioSource.closeWithError(errorMessage);
+ clientAudioSink.closeWithError(errorMessage);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, mResultTaskId + ": Failed to propagate error", e);
+ }
+ }
+ }
+
+ private static class SingleAudioStreamCopyTask implements Callable<Void> {
+ // TODO: Make this buffer size customizable from updateState()
+ private static final int COPY_BUFFER_LENGTH = 2_560;
+
+ private final String mStreamTaskId;
+ private final ParcelFileDescriptor mAudioSource;
+ private final ParcelFileDescriptor mAudioSink;
+
+ SingleAudioStreamCopyTask(String streamTaskId, ParcelFileDescriptor audioSource,
+ ParcelFileDescriptor audioSink) {
+ mStreamTaskId = streamTaskId;
+ mAudioSource = audioSource;
+ mAudioSink = audioSink;
+ }
+
+ @Override
+ public Void call() throws Exception {
+ Thread.currentThread().setName(THREAD_NAME_PREFIX + mStreamTaskId);
+
+ // Note: We are intentionally NOT using try-with-resources here. If we did,
+ // the ParcelFileDescriptors will be automatically closed WITHOUT errors before we go
+ // into the IOException-catch block. We want to propagate the error while closing the
+ // PFDs.
+ InputStream fis = null;
+ OutputStream fos = null;
+ try {
+ fis = new ParcelFileDescriptor.AutoCloseInputStream(mAudioSource);
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(mAudioSink);
+ byte[] buffer = new byte[COPY_BUFFER_LENGTH];
+ while (true) {
+ if (Thread.interrupted()) {
+ Slog.e(TAG,
+ mStreamTaskId + ": SingleAudioStreamCopyTask task was interrupted");
+ break;
+ }
+
+ int bytesRead = fis.read(buffer);
+ if (bytesRead < 0) {
+ Slog.i(TAG, mStreamTaskId + ": Reached end of audio stream");
+ break;
+ }
+ if (bytesRead > 0) {
+ if (DEBUG) {
+ // TODO(b/244599440): Add proper logging
+ Slog.d(TAG, mStreamTaskId + ": Copied " + bytesRead
+ + " bytes from audio stream. First 20 bytes=" + Arrays.toString(
+ Arrays.copyOfRange(buffer, 0, 20)));
+ }
+ fos.write(buffer, 0, bytesRead);
+ }
+ // TODO(b/244599891): Close PFDs after inactivity
+ }
+ } catch (IOException e) {
+ mAudioSource.closeWithError(e.getMessage());
+ mAudioSink.closeWithError(e.getMessage());
+ Slog.e(TAG, mStreamTaskId + ": Failed to copy audio stream", e);
+ } finally {
+ if (fis != null) {
+ fis.close();
+ }
+ if (fos != null) {
+ fos.close();
+ }
+ }
+
+ return null;
+ }
+ }
+
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index a6e1a32..6f7d80c 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -19,7 +19,6 @@
import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
-import static android.service.voice.HotwordDetectedResult.EXTRA_PROXIMITY_METERS;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
@@ -59,6 +58,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.attention.AttentionManagerInternal;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
@@ -137,6 +137,7 @@
// The error codes are used for onError callback
private static final int HOTWORD_DETECTION_SERVICE_DIED = -1;
private static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION = -2;
+ private static final int CALLBACK_ONDETECTED_STREAM_COPY_ERROR = -4;
// Hotword metrics
private static final int METRICS_INIT_UNKNOWN_TIMEOUT =
@@ -168,6 +169,8 @@
// TODO: This may need to be a Handler(looper)
private final ScheduledExecutorService mScheduledExecutorService =
Executors.newSingleThreadScheduledExecutor();
+ private final AppOpsManager mAppOpsManager;
+ private final HotwordAudioStreamManager mHotwordAudioStreamManager;
@Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false);
private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
@@ -189,7 +192,7 @@
@Nullable AttentionManagerInternal mAttentionManagerInternal = null;
final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
- this::setProximityMeters;
+ this::setProximityValue;
volatile HotwordDetectionServiceIdentity mIdentity;
@@ -228,6 +231,9 @@
mContext = context;
mVoiceInteractionServiceUid = voiceInteractionServiceUid;
mVoiceInteractorIdentity = voiceInteractorIdentity;
+ mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+ mHotwordAudioStreamManager = new HotwordAudioStreamManager(mAppOpsManager,
+ mVoiceInteractorIdentity);
mDetectionComponentName = serviceName;
mUser = userId;
mCallback = callback;
@@ -481,14 +487,20 @@
mSoftwareCallback.onError();
return;
}
- saveProximityMetersToBundle(result);
- mSoftwareCallback.onDetected(result, null, null);
- if (result != null) {
- Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
- + " bits from hotword trusted process");
- if (mDebugHotwordLogging) {
- Slog.i(TAG, "Egressed detected result: " + result);
- }
+ saveProximityValueToBundle(result);
+ HotwordDetectedResult newResult;
+ try {
+ newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
+ } catch (IOException e) {
+ // TODO: Write event
+ mSoftwareCallback.onError();
+ return;
+ }
+ mSoftwareCallback.onDetected(newResult, null, null);
+ Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+ + " bits from hotword trusted process");
+ if (mDebugHotwordLogging) {
+ Slog.i(TAG, "Egressed detected result: " + newResult);
}
}
}
@@ -586,7 +598,7 @@
externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
return;
}
- saveProximityMetersToBundle(result);
+ saveProximityValueToBundle(result);
externalCallback.onKeyphraseDetected(recognitionEvent, result);
if (result != null) {
Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
@@ -660,20 +672,27 @@
try {
enforcePermissionsForDataDelivery();
} catch (SecurityException e) {
+ Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
return;
}
- saveProximityMetersToBundle(result);
- externalCallback.onKeyphraseDetected(recognitionEvent, result);
- if (result != null) {
- Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
- + " bits from hotword trusted process");
- if (mDebugHotwordLogging) {
- Slog.i(TAG, "Egressed detected result: " + result);
- }
+ saveProximityValueToBundle(result);
+ HotwordDetectedResult newResult;
+ try {
+ newResult = mHotwordAudioStreamManager.startCopyingAudioStreams(result);
+ } catch (IOException e) {
+ // TODO: Write event
+ externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR);
+ return;
+ }
+ externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
+ Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+ + " bits from hotword trusted process");
+ if (mDebugHotwordLogging) {
+ Slog.i(TAG, "Egressed detected result: " + newResult);
}
}
}
@@ -757,6 +776,7 @@
}
private void restartProcessLocked() {
+ // TODO(b/244598068): Check HotwordAudioStreamManager first
Slog.v(TAG, "Restarting hotword detection process");
ServiceConnection oldConnection = mRemoteHotwordDetectionService;
HotwordDetectionServiceIdentity previousIdentity = mIdentity;
@@ -991,16 +1011,24 @@
callback.onError();
return;
}
- callback.onDetected(triggerResult, null /* audioFormat */,
+ HotwordDetectedResult newResult;
+ try {
+ newResult =
+ mHotwordAudioStreamManager.startCopyingAudioStreams(
+ triggerResult);
+ } catch (IOException e) {
+ // TODO: Write event
+ callback.onError();
+ return;
+ }
+ callback.onDetected(newResult, null /* audioFormat */,
null /* audioStream */);
- if (triggerResult != null) {
- Slog.i(TAG, "Egressed "
- + HotwordDetectedResult.getUsageSize(triggerResult)
- + " bits from hotword trusted process");
- if (mDebugHotwordLogging) {
- Slog.i(TAG,
- "Egressed detected result: " + triggerResult);
- }
+ Slog.i(TAG, "Egressed "
+ + HotwordDetectedResult.getUsageSize(newResult)
+ + " bits from hotword trusted process");
+ if (mDebugHotwordLogging) {
+ Slog.i(TAG,
+ "Egressed detected result: " + newResult);
}
}
});
@@ -1215,15 +1243,15 @@
});
}
- private void saveProximityMetersToBundle(HotwordDetectedResult result) {
+ private void saveProximityValueToBundle(HotwordDetectedResult result) {
synchronized (mLock) {
if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
- result.getExtras().putDouble(EXTRA_PROXIMITY_METERS, mProximityMeters);
+ result.setProximity(mProximityMeters);
}
}
}
- private void setProximityMeters(double proximityMeters) {
+ private void setProximityValue(double proximityMeters) {
synchronized (mLock) {
mProximityMeters = proximityMeters;
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index fabab25..f90fbb2 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -620,7 +620,7 @@
private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback,
int detectorType, boolean isCreated, int voiceInteractionServiceUid) {
if (callback != null) {
- HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, true,
+ HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, isCreated,
voiceInteractionServiceUid);
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 83bc0fc0..741721d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2000,6 +2000,17 @@
"nr_advanced_threshold_bandwidth_khz_int";
/**
+ * Indicating whether to include LTE cell bandwidths when determining whether the aggregated
+ * cell bandwidth meets the required threshold for NR advanced.
+ *
+ * @see TelephonyDisplayInfo#OVERRIDE_NETWORK_TYPE_NR_ADVANCED
+ *
+ * @hide
+ */
+ public static final String KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL =
+ "include_lte_for_nr_advanced_threshold_bandwidth_bool";
+
+ /**
* Boolean indicating if operator name should be shown in the status bar
* @hide
*/
@@ -8555,6 +8566,16 @@
public static final String KEY_VONR_ENABLED_BOOL = "vonr_enabled_bool";
/**
+ * Boolean indicating the default VoNR user preference setting.
+ * If true, the VoNR setting will be enabled. If false, it will be disabled initially.
+ *
+ * Enabled by default.
+ *
+ * @hide
+ */
+ public static final String KEY_VONR_ON_BY_DEFAULT_BOOL = "vonr_on_by_default_bool";
+
+ /**
* Determine whether unthrottle data retry when tracking area code (TAC/LAC) from cell changes
*
* @hide
@@ -8991,6 +9012,7 @@
sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0);
+ sDefaults.putBoolean(KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, false);
sDefaults.putIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
new int[]{CARRIER_NR_AVAILABILITY_NSA, CARRIER_NR_AVAILABILITY_SA});
sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
@@ -9247,6 +9269,7 @@
sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, true);
sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false);
+ sDefaults.putBoolean(KEY_VONR_ON_BY_DEFAULT_BOOL, true);
sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
"source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
+ "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"});
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 06cfd67..6e3cfac 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -107,7 +107,7 @@
if ((mMccStr != null && mMncStr == null) || (mMccStr == null && mMncStr != null)) {
AnomalyReporter.reportAnomaly(
- UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+ UUID.fromString("e257ae06-ac0a-44c0-ba63-823b9f07b3e4"),
"CellIdentity Missing Half of PLMN ID");
}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index e0145e6..28307d2 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -1020,6 +1020,17 @@
}
/**
+ * Return the encoding type of a received SMS message, which is specified using ENCODING_*
+ * GSM: defined in android.telephony.SmsConstants
+ * CDMA: defined in android.telephony.cdma.UserData
+ *
+ * @hide
+ */
+ public int getReceivedEncodingType() {
+ return mWrappedSmsMessage.getReceivedEncodingType();
+ }
+
+ /**
* Determines whether or not to use CDMA format for MO SMS.
* If SMS over IMS is supported, then format is based on IMS SMS format,
* otherwise format is based on current phone type.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 105fc4c..ecd8c7a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -12273,7 +12273,7 @@
Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
} catch (NullPointerException e) {
AnomalyReporter.reportAnomaly(
- UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+ UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"),
"getServiceStateForSubscriber " + subId + " NPE");
}
return null;
diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
index 6d46ed3..0cc1e98 100644
--- a/telephony/java/com/android/internal/telephony/SmsMessageBase.java
+++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
@@ -16,6 +16,8 @@
package com.android.internal.telephony;
+import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
+
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.telephony.SmsMessage;
@@ -94,6 +96,15 @@
protected boolean mMwiDontStore;
/**
+ * The encoding type of a received SMS message, which is specified using ENCODING_*
+ * GSM: defined in android.telephony.SmsConstants
+ * CDMA: defined in android.telephony.cdma.UserData
+ *
+ * @hide
+ */
+ protected int mReceivedEncodingType = ENCODING_UNKNOWN;
+
+ /**
* Indicates status for messages stored on the ICC.
*/
protected int mStatusOnIcc = -1;
@@ -512,4 +523,8 @@
return mRecipientAddress.getAddressString();
}
+
+ public int getReceivedEncodingType() {
+ return mReceivedEncodingType;
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index f636276..b51ba31 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -780,6 +780,7 @@
mUserData = mBearerData.userData.payload;
mUserDataHeader = mBearerData.userData.userDataHeader;
mMessageBody = mBearerData.userData.payloadStr;
+ mReceivedEncodingType = mBearerData.userData.msgEncoding;
}
if (mOriginatingAddress != null) {
@@ -860,6 +861,9 @@
Rlog.w(LOG_TAG, "BearerData.decode() returned null");
return null;
}
+ if (bData.userData != null) {
+ mReceivedEncodingType = bData.userData.msgEncoding;
+ }
if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index b51e8d3d..a555650 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -1396,28 +1396,28 @@
} else {
switch ((mDataCodingScheme >> 2) & 0x3) {
case 0: // GSM 7 bit default alphabet
- encodingType = ENCODING_7BIT;
- break;
+ encodingType = ENCODING_7BIT;
+ break;
case 2: // UCS 2 (16bit)
- encodingType = ENCODING_16BIT;
- break;
+ encodingType = ENCODING_16BIT;
+ break;
case 1: // 8 bit data
- //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
- //that's stored in 8-bit unpacked format) characters.
- if (r.getBoolean(com.android.internal.
- R.bool.config_sms_decode_gsm_8bit_data)) {
- encodingType = ENCODING_8BIT;
- break;
- }
+ // Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet
+ // string that's stored in 8-bit unpacked format) characters.
+ if (r.getBoolean(com.android.internal
+ .R.bool.config_sms_decode_gsm_8bit_data)) {
+ encodingType = ENCODING_8BIT;
+ break;
+ }
case 3: // reserved
- Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
- + (mDataCodingScheme & 0xff));
- encodingType = r.getInteger(
- com.android.internal.R.integer.default_reserved_data_coding_scheme);
- break;
+ Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
+ + (mDataCodingScheme & 0xff));
+ encodingType = r.getInteger(
+ com.android.internal.R.integer.default_reserved_data_coding_scheme);
+ break;
}
}
} else if ((mDataCodingScheme & 0xf0) == 0xf0) {
@@ -1492,6 +1492,7 @@
encodingType == ENCODING_7BIT);
this.mUserData = p.getUserData();
this.mUserDataHeader = p.getUserDataHeader();
+ this.mReceivedEncodingType = encodingType;
/*
* Look for voice mail indication in TP_UDH TS23.040 9.2.3.24